diff --git a/doc/qtdesignstudio/images/add-updated-local-custom-property-editor.webp b/doc/qtdesignstudio/images/add-updated-local-custom-property-editor.webp new file mode 100644 index 00000000000..17994a1eddd Binary files /dev/null and b/doc/qtdesignstudio/images/add-updated-local-custom-property-editor.webp differ diff --git a/doc/qtdesignstudio/images/add-updated-local-custom-property.webp b/doc/qtdesignstudio/images/add-updated-local-custom-property.webp new file mode 100644 index 00000000000..9c1f2ea9287 Binary files /dev/null and b/doc/qtdesignstudio/images/add-updated-local-custom-property.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-bindings.webp b/doc/qtdesignstudio/images/qmldesigner-bindings.webp new file mode 100644 index 00000000000..492311bbb37 Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-bindings.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-components-after-binding.webp b/doc/qtdesignstudio/images/qmldesigner-components-after-binding.webp new file mode 100644 index 00000000000..a87d1c3ded6 Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-components-after-binding.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-components-before-binding.webp b/doc/qtdesignstudio/images/qmldesigner-components-before-binding.webp new file mode 100644 index 00000000000..a53a1ed30b6 Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-components-before-binding.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Autometic.webp b/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Autometic.webp new file mode 100644 index 00000000000..41391ac8a7d Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Autometic.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Manual.webp b/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Manual.webp new file mode 100644 index 00000000000..3433051d1ee Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-connections-ConditionalAction-Manual.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections-advanced.webp b/doc/qtdesignstudio/images/qmldesigner-connections-advanced.webp new file mode 100644 index 00000000000..1c59f96cc6a Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-connections-advanced.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections-editor.webp b/doc/qtdesignstudio/images/qmldesigner-connections-editor.webp new file mode 100644 index 00000000000..a74d4c09dfc Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-connections-editor.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections.png b/doc/qtdesignstudio/images/qmldesigner-connections.png deleted file mode 100644 index 605dc835784..00000000000 Binary files a/doc/qtdesignstudio/images/qmldesigner-connections.png and /dev/null differ diff --git a/doc/qtdesignstudio/images/qmldesigner-connections.webp b/doc/qtdesignstudio/images/qmldesigner-connections.webp new file mode 100644 index 00000000000..5a49a316fe6 Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-connections.webp differ diff --git a/doc/qtdesignstudio/images/qmldesigner-updated-bindings-editor.webp b/doc/qtdesignstudio/images/qmldesigner-updated-bindings-editor.webp new file mode 100644 index 00000000000..13ceac16b11 Binary files /dev/null and b/doc/qtdesignstudio/images/qmldesigner-updated-bindings-editor.webp differ diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index 454a7a0845d..39b0b05b048 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -239,15 +239,22 @@ \section2 Configuring Snapping - To edit snapping settings, select \inlineimage icons/snapping-3d-conf.png - in the \uicontrol 3D view toolbar. + To edit the snapping settings, select \inlineimage icons/snapping-3d-conf.png + in the \uicontrol 3D view toolbar to open the configure dialog. In the configure dialog, you can do the following: \list - \li Turn on and off snapping separately for the different transformations + \li Turn snapping on and off separately for the different transformations (move, rotate, scale). - \li Set snap intervals. - \li Toggle if the position snaps to absolute or relative values. + \li Set snap intervals for the transformations. + \note Changing the snap interval for the position also changes the grid line intervals. + \note All the grid lines might not be visible depending on the zoom level in the 3D view. + \li Select \uicontrol {Absolute Position} to snap to absolute values. Clear the checkbox + to use relative values.The absolute snapping aligns the object with the grid, while the + relative snapping moves the object in fixed intervals without changing its alignment. + For example, if you have an object that is slightly off the grid and you want to snap it + to the grid, use the absolute snapping. If you want to move the object by a certain + distance without affecting its orientation, use the relative snapping. \endlist \section1 Aligning Views and Cameras diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-editor-bindings.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-editor-bindings.qdoc index 613d8fd02f4..6d759f4de6b 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-editor-bindings.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-editor-bindings.qdoc @@ -25,30 +25,45 @@ You can create bindings between components in \uicontrol Bindings. - \image qmldesigner-bindings.png + \image qmldesigner-bindings.webp + + \section1 Creating Bindings Between Component Properties To bind a property of a component to the property of another component: \list 1 - \li Go to the \uicontrol Binding tab in the \l Connections view. + + \li Place two components in the \uicontrol {2D} view. + + \image qmldesigner-components-before-binding.webp + + \li Name the first component as \e {viewBox}. + \li Name the second component as \e {connectBox}. + \li Apply a thick \e black \uicontrol Border and a \e blue \uicontrol{Color} to the \e {viewBox} component. + \li Select the \e {connectBox} component. + \li Select \uicontrol Bindings from the \uicontrol Connections view. \li Select the \inlineimage icons/plus.png - (\uicontrol Add) button to add a binding for the currently selected - component. The component ID is displayed in the \uicontrol Item - column. - \li Double-click the value in the \uicontrol Property column to select - the property to bind to a source property. - \li Double-click the value in the \uicontrol {Source Item} column to - select the component whose property you want to use to determine the - behavior of the target component. - \li Double-click the value in the \uicontrol {Source Property} column - to select the property to bind the target property to. + (\uicontrol Add) button to add a binding to the currently selected + component. + + \image qmldesigner-updated-bindings-editor.webp + + \li From the pop-up \uicontrol {Bindings editor}, in the \uicontrol From section, + select \e {viewBox} as the parent component, then select its \uicontrol {border.color} + property. + \li In the \uicontrol To section you find the \e {connectBox} component already selected + as the target component. Select \uicontrol {color} from the \uicontrol {drop-down} + below to set its affected property. + \li You see the \uicontrol {border.color} of the \e {viewBox} component + instantly getting applied to the \uicontrol {color} of the \e {connectBox} + component. + + \image qmldesigner-components-after-binding.webp + \endlist - Right-click a binding and select \uicontrol {Open Binding Editor} in - the context menu to specify the binding as a JavaScript expression in - \uicontrol {Binding Editor}. For more information, see \l{Setting Bindings}. - - \image qmldesigner-binding-editor.png "Binding Editor" + All the \uicontrol Bindings connections have automated JavaScript expression in the + \uicontrol {Code view}. For more information, see \l{Setting Bindings}. For examples of creating property bindings, see: @@ -57,10 +72,6 @@ \li \l{Exporting Properties} \endlist - For more information, watch the following video: - - \youtube UfvA04CIXv0 - \include creator-logical-operators.qdocinc logical operators */ diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-editor-properties.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-editor-properties.qdoc index 8accd1df488..8ecfa8701b9 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-editor-properties.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-editor-properties.qdoc @@ -60,6 +60,32 @@ For more information, see \l{Setting Bindings}. + \section1 Adding a Custom Property to a Component from the Connections View + + You can add a custom property to a component from the \uicontrol {Connections} view. + Follow the process: + + \list 1 + \li Select the component you want to add a Custom property to in the + \uicontrol {2D} view or in the \uicontrol {Navigator} view. + \li Select \uicontrol {Properties} from the \uicontrol {Connections} view. + + \image add-updated-local-custom-property.webp + + \li Select the \inlineimage icons/plus.png + (\uicontrol Add) button to add a Custom property. + \li From the pop-up \uicontrol {Custom property editor}, select the \uicontrol {Type} + of the property you want to include. + + \image add-updated-local-custom-property-editor.webp + + \li Next, set the \uicontrol{Name} of the property. + \li Set a value to the Custom property in the \uicontrol {Value} field. + \endlist + + \note Select the \inlineimage icons/minus.png + (\uicontrol Remove) to delete a Custom Property. + \section1 Supported Property Types The following table describes the supported property types: diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc index 420226750dc..e175a84d4fd 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc @@ -38,88 +38,125 @@ For more information about signals and signal handlers, see \l{Signal and Handler Event System}. + \section1 Connecting Components to Signals in the Connection View + You can connect components to signals that are available to them in - \uicontrol Connections. + \uicontrol Connections. Then define \uicontrol Action for them. You can + put \b {logical conditions} on this \uicontrol Actions to control them + according to your needs. - \image qmldesigner-connections.png + \image qmldesigner-connections-advanced.webp - To connect components to signals: + Initiate a new connection for a component: \list 1 - \li Go to the \uicontrol Connections tab in the \l Connections view. + \li Select the component you want to connect in the \uicontrol Navigator or \uicontrol 2D + view. + \li Go to the \uicontrol Connections tab in the \uicontrol Connections view. \li Select the \inlineimage icons/plus.png (\uicontrol Add) button to add a connection. - \li Double-click the value in the \uicontrol Target column to add the - component to connect to a signal. - \li Double-click the value in the \uicontrol {Signal Handler} column to - select the signal that the connection will listen to from a list of - all signals available for the component. - \li Double-click the value in the \uicontrol Actions column to specify - the action to perform when the signal is emitted. You use JavaScript - to specify the actions. \endlist - Right-click a connection and select \uicontrol {Open Connection Editor} - in the context menu to specify the connection in - \uicontrol {Connection Editor}. + \image qmldesigner-connections-editor.webp - For examples of using the \uicontrol {Connections} view, see: - - \list - \li \l{Connecting Buttons to States} in \l{Log In UI - States} - \li \l{Connecting Buttons to State Changes} in \l{Washing Machine UI} - \endlist - - \section1 Adding Signal Handlers - - If a signal handler that you need is not listed in the - \uicontrol {Signal Handler} column, you can add it: + Connect component \uicontrol Signal to \uicontrol Action: \list 1 - \li Right-click a component in the \l Navigator or \l {2D} view - and select \uicontrol {Add New Signal Handler} in the context menu. - \li In the \uicontrol Signal field, select the signal to handle. - \image qmldesigner-implement-signal-handler.png "Implement Signal Handler dialog" - \li Select the radio buttons to filter the list to only display - frequently used signals or property changes. - \li Select \uicontrol OK. + \li Select the interaction \uicontrol Signal for the \uicontrol Target component with + which you want to connect an \uicontrol Action. + \li Select an \uicontrol Action that you want to implement when the selected + \uicontrol Signal for the \uicontrol Target component is initiated. + \li You get different properties or sub-sections associated with the selected + \uicontrol Action. Select \uicontrol {Item/Method/State/Property/Value} from related + sub-sections. This way you can formulate changes in the design by manipulating + components. + \li Select \uicontrol {Add Condition} to include a logic to the selected \uicontrol Action. + To do this, you can first select the component you want to put logic on, and then + select the conditional statements \e {(i.e. AND, OR, EQUAL, NOT EQUAL, GREATER, LESS, + GREATER OR EQUAL, LESS OR EQUAL)} and then select another component to compare + between them. + \li Optional. You can include an \uicontrol {Else Statement} by selecting the + \uicontrol {Else Statement} control and adding components and conditional + statements in a similar way to the previous step. + \li Optional. To use complex conditional statements, select the \uicontrol {Manual Edit} + control. After you have entered your JavaScript statements, close the window to apply + them. \endlist - The added signal handler is automatically \l{Adding Property Aliases} - {exported as a property}. + \note Select a connection to re-open the \uicontrol {Connection Editor} for any + previously created \uicontrol Connection. + \note Select \inlineimage icons/minus.png + (\uicontrol Remove) to delete a connection. - \section1 Adding Actions and Assignments + + \section1 Actions and Conditions You use the \uicontrol {Connection Editor} to create the JavaScript - expressions for \e actions and \e assignments. An \e action connects - an component to a signal, whereas an \e assignment fetches property values - from another component. + expressions for \e actions with \e conditions. An \e action connects + a component to a signal, whereas \e conditions fetch property values + from other components and compare them to each other. Based on that, you can later change + the components with JavaScript expressions. For more information about the logical operators that you can use to construct conditional expressions, see \l {Summary of Logical Operators}. - To create JavaScript expressions for actions: + \section2 Creating JavaScript Expressions - \list 1 - \li Select \uicontrol {Open Connection Editor} in the context menu - in \uicontrol {Connections}. - \image qtquick-connection-editor-action.png - \li Select \uicontrol Action as the type of the connections component. - \li Select the component to connect to a signal. - \li Select the action to perform when the signal is emitted. + There are two ways to create JavaScript expressions for actions: + + \list + \li Follow the steps described above in Connect component \uicontrol Signal to + \uicontrol Action. For a list of \uicontrol Actions and their properties, see + \l {Action Properties}. + \image qmldesigner-connections-ConditionalAction-Autometic.webp + \li Open the \uicontrol {Manual Code Edit} window from the + \uicontrol {Connections} view and write JavaScript expressions with components + and logical expressions manually. \endlist - To create JavaScript expressions for assignments: + \section2 Action Properties + + \table + \header + \li Action + \li 1st Property + \li 2nd Property + \row + \li \uicontrol {Call Function} + \li \uicontrol {Item}: [Sets the component that is affected by the action of the + \b Target component's \b Signal.] + \li \uicontrol {Method}: [Sets the item component's method that is affected by the + \b Target component's \b Signal.] + \row + \li \uicontrol {Assign} + \li \uicontrol {From}: [Sets the component and its property from which the value + is copied when the \b Target component initiates the \b Signal.] + \li \uicontrol {To}: [Sets the component and its property to which the copied value + is assigned when the \b Target component initiates the \b Signal.] + \row + \li \uicontrol {Change State} + \li \uicontrol {State Group}: [Sets a \b {State Group} that is accessed when the + \b Target component initiates the \b Signal.] + \li \uicontrol {State}: [Sets a \b State within the assigned \b {State Group} + that is accessed when the \b Target component initiates the \b Signal.] + \row + \li \uicontrol {Set Property} + \li \uicontrol {Item}: [Sets the component that is affected by the action + of the \b Target component's \b Signal.] + \li \uicontrol {Property}: [Sets the property of the component that is + affected by the action of the \b Target component's \b Signal.] + \row + \li \uicontrol {Print Message} + \li \uicontrol {Message}: [Sets a text that is printed when the \b Signal + of the \b Target component initiates.] + \li N/A + \endtable + + \note If you create a conditional expression by selecting options from drop-down menus in + the \uicontrol {Connection} view, you can only create a single + level {if-else} expression. For nested level \e {if-elseif-else} expressions, + you have to use the \uicontrol {Manual Code Edit}. + + \image qmldesigner-connections-ConditionalAction-Manual.webp - \list 1 - \li Select \uicontrol {Open Connection Editor} in the context menu - in \uicontrol {Connections}. - \image qtquick-connection-editor-assignment.png - \li Select \uicontrol Assignment as the type of the connections - component. - \li Select the target component for the property assignment. - \li Select the property of the target component to assign a value to. - \li Select the source component for the property assignment. - \li Select the property of the source component to fetch the value from. - \endlist */ diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-view.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-view.qdoc index 93fbdc7ea7a..5cc2c4a6670 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-view.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-view.qdoc @@ -12,7 +12,7 @@ to create connections between components and the application, to bind component properties together, and to add custom properties for components. - \image qmldesigner-connections.png "The Connections view" + \image qmldesigner-connections.webp "The Connections view" The \l{glossary-component}{components} of the application UI and the application logic need to communicate with each other. For example, a diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml index 6a511c72524..edaf038ac8d 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml @@ -327,7 +327,7 @@ TreeViewDelegate { sourceSize.width: 48 sourceSize.height: 48 asynchronous: true - fillMode: Image.PreserveAspectFit + fillMode: Image.Pad source: thumbnailImage.__computeSource() function __computeSource() { diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsView.qml index 98b85d85c96..9326e6a5e35 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsView.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsView.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls +import StudioTheme as StudioTheme import AssetsLibraryBackend TreeView { @@ -53,8 +54,9 @@ TreeView { HoverHandler { id: hoverHandler } - ScrollBar.vertical: HelperWidgets.ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: root x: root.width - verticalScrollBar.width y: 0 diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml index 5be2dacb8d7..a92cdf065d8 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml @@ -84,8 +84,9 @@ Item { Text { id: threeDots - text: "..." - font.pixelSize: StudioTheme.Values.baseFontSize + text: StudioTheme.Constants.more_medium + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize color: textColor anchors.right: boundingRect.right anchors.verticalCenter: parent.verticalCenter diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml index 30128ecadf4..e6fddf33ee4 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml @@ -82,14 +82,14 @@ Item { spacing: 2 HelperWidgets.IconButton { - icon: StudioTheme.Constants.translationImport + icon: StudioTheme.Constants.downloadjson_large tooltip: qsTr("Import Json") onClicked: jsonImporter.open() } HelperWidgets.IconButton { - icon: StudioTheme.Constants.translationImport + icon: StudioTheme.Constants.downloadcsv_large tooltip: qsTr("Import CSV") onClicked: csvImporter.open() @@ -112,13 +112,13 @@ Item { } ListView { - id: collectionListView + id: sourceListView width: parent.width height: contentHeight model: root.model - delegate: CollectionItem { + delegate: ModelSourceItem { onDeleteItem: root.model.removeRow(index) } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/ModelSourceItem.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ModelSourceItem.qml new file mode 100644 index 00000000000..9496ee394a7 --- /dev/null +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ModelSourceItem.qml @@ -0,0 +1,331 @@ +// 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 HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme + +Item { + id: root + + implicitWidth: 300 + implicitHeight: wholeColumn.height + 6 + + property color textColor + property var collectionModel + + property bool expanded: false + + signal selectItem(int itemIndex) + signal deleteItem() + + Column { + id: wholeColumn + + Item { + id: boundingRect + + anchors.centerIn: root + width: root.width - 24 + height: nameHolder.height + clip: true + + MouseArea { + id: itemMouse + + anchors.fill: parent + acceptedButtons: Qt.LeftButton + propagateComposedEvents: true + hoverEnabled: true + + onClicked: (event) => { + if (!sourceIsSelected) { + sourceIsSelected = true + event.accepted = true + } + } + + onDoubleClicked: (event) => { + if (collectionListView.count > 0) + root.expanded = !root.expanded; + } + } + + Rectangle { + id: innerRect + anchors.fill: parent + } + + Row { + width: parent.width - threeDots.width + leftPadding: 20 + + Text { + id: expandButton + + property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle + + width: expandButton.style.squareControlSize.width + height: nameHolder.height + + text: StudioTheme.Constants.startNode + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: expandButton.style.baseIconFontSize + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: textColor + + rotation: root.expanded ? 90 : 0 + + Behavior on rotation { + SpringAnimation { spring: 2; damping: 0.2 } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + Qt.LeftButton + onClicked: (event) => { + root.expanded = !root.expanded + event.accepted = true + } + } + visible: collectionListView.count > 0 + } + + Text { + id: nameHolder + + text: sourceName + font.pixelSize: StudioTheme.Values.baseFontSize + color: textColor + leftPadding: 5 + topPadding: 8 + rightPadding: 8 + bottomPadding: 8 + elide: Text.ElideMiddle + verticalAlignment: Text.AlignVCenter + } + } + + Text { + id: threeDots + + text: StudioTheme.Constants.more_medium + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: textColor + anchors.right: boundingRect.right + anchors.verticalCenter: parent.verticalCenter + rightPadding: 12 + topPadding: nameHolder.topPadding + bottomPadding: nameHolder.bottomPadding + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + Qt.LeftButton + onClicked: (event) => { + collectionMenu.popup() + event.accepted = true + } + } + } + } + + ListView { + id: collectionListView + + width: parent.width + height: root.expanded ? contentHeight : 0 + model: collections + clip: true + + Behavior on height { + NumberAnimation {duration: 500} + } + + delegate: CollectionItem { + width: parent.width + onDeleteItem: root.model.removeRow(index) + } + } + } + + StudioControls.Menu { + id: collectionMenu + + StudioControls.MenuItem { + text: qsTr("Delete") + shortcut: StandardKey.Delete + onTriggered: deleteDialog.open() + } + + StudioControls.MenuItem { + text: qsTr("Rename") + shortcut: StandardKey.Replace + onTriggered: renameDialog.open() + } + } + + StudioControls.Dialog { + id: deleteDialog + + title: qsTr("Deleting source") + + contentItem: Column { + spacing: 2 + + Text { + text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?") + color: StudioTheme.Values.themeTextColor + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + spacing: 10 + + HelperWidgets.Button { + id: btnDelete + + text: qsTr("Delete") + onClicked: root.deleteItem(index) + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: deleteDialog.reject() + } + } + } + } + + StudioControls.Dialog { + id: renameDialog + + title: qsTr("Rename source") + + onAccepted: { + if (newNameField.text !== "") + sourceName = newNameField.text + } + + onOpened: { + newNameField.text = sourceName + } + + contentItem: Column { + spacing: 2 + + Text { + text: qsTr("Previous name: " + sourceName) + color: StudioTheme.Values.themeTextColor + } + + Row { + spacing: 10 + Text { + text: qsTr("New name:") + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: newNameField + + actionIndicator.visible: false + translationIndicator.visible: false + validator: newNameValidator + + Keys.onEnterPressed: renameDialog.accept() + Keys.onReturnPressed: renameDialog.accept() + Keys.onEscapePressed: renameDialog.reject() + + onTextChanged: { + btnRename.enabled = newNameField.text !== "" + } + } + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + spacing: 10 + + HelperWidgets.Button { + id: btnRename + + text: qsTr("Rename") + onClicked: renameDialog.accept() + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: renameDialog.reject() + } + } + } + } + + HelperWidgets.RegExpValidator { + id: newNameValidator + regExp: /^\w+$/ + } + + states: [ + State { + name: "default" + when: !sourceIsSelected && !itemMouse.containsMouse + + PropertyChanges { + target: innerRect + opacity: 0.4 + color: StudioTheme.Values.themeControlBackground + } + + PropertyChanges { + target: root + textColor: StudioTheme.Values.themeTextColor + } + }, + State { + name: "hovered" + when: !sourceIsSelected && itemMouse.containsMouse + + PropertyChanges { + target: innerRect + opacity: 0.5 + color: StudioTheme.Values.themeControlBackgroundHover + } + + PropertyChanges { + target: root + textColor: StudioTheme.Values.themeTextColor + } + }, + State { + name: "selected" + when: sourceIsSelected + + PropertyChanges { + target: innerRect + opacity: 0.6 + color: StudioTheme.Values.themeControlBackgroundInteraction + } + + PropertyChanges { + target: root + textColor: StudioTheme.Values.themeIconColorSelected + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml index 157d0d35177..749add63c43 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -22,8 +22,17 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { text: qsTr("From") ; tooltip: qsTr("The Property to assign from.")} - PopupLabel { text: qsTr("To"); tooltip: qsTr("The Property to assign to.") } + PopupLabel { + width: root.columnWidth + text: qsTr("From") + tooltip: qsTr("Sets the component and its property from which the value is copied.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("To") + tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.") + } } Row { diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml index 07a6fa7c8db..83db20646e0 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml @@ -3,8 +3,8 @@ import QtQuick import QtQuick.Controls -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme as StudioTheme import ConnectionsEditorEditorBackend @@ -15,6 +15,12 @@ ListView { property bool adsFocus: false + // Temporarily remove due to dockwidget focus issue + //onAdsFocusChanged: { + // if (!root.adsFocus) + // dialog.close() + //} + clip: true interactive: true highlightMoveDuration: 0 @@ -24,8 +30,9 @@ ListView { HoverHandler { id: hoverHandler } - ScrollBar.vertical: HelperWidgets.ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: root x: root.width - verticalScrollBar.width y: 0 @@ -62,11 +69,26 @@ ListView { property int rowWidth: root.rowSpace / root.numColumns property int rowRest: root.rowSpace % root.numColumns + function addBinding() { + ConnectionsEditorEditorBackend.bindingModel.add() + if (root.currentItem) + dialog.popup(root.currentItem.delegateMouseArea) + } + + function resetIndex() { + root.model.currentIndex = -1 + root.currentIndex = -1 + } + data: [ BindingsDialog { id: dialog visible: false backend: root.model.delegate + + onClosing: function(event) { + root.resetIndex() + } } ] @@ -80,6 +102,8 @@ ListView { required property string source required property string sourceProperty + property alias delegateMouseArea: mouseArea + width: ListView.view.width height: root.style.squareControlSize.height color: mouseArea.containsMouse ? @@ -175,9 +199,13 @@ ListView { HelperWidgets.ToolTipArea { id: toolTipArea - tooltip: qsTr("This is a test.") + tooltip: qsTr("Removes the binding.") anchors.fill: parent - onClicked: root.model.remove(itemDelegate.index) + onClicked: { + if (itemDelegate.ListView.isCurrentItem) + dialog.close() + root.model.remove(itemDelegate.index) + } } } } @@ -185,6 +213,5 @@ ListView { highlight: Rectangle { color: root.style.interaction - width: 600 } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index b880916e4d8..c7f0034b211 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -7,8 +7,8 @@ import StudioTheme 1.0 as StudioTheme import HelperWidgets 2.0 as HelperWidgets PopupDialog { - - property alias backend: form.backend + id: root + property alias backend: form.backend titleBar: Row { spacing: 30 // TODO @@ -21,7 +21,7 @@ PopupDialog { anchors.verticalCenter: parent.verticalCenter HelperWidgets.ToolTipArea { anchors.fill: parent - tooltip: qsTr("Choose the target for the signal.") + tooltip: qsTr("Sets the Component that is connected to a Signal.") } } @@ -36,10 +36,16 @@ PopupDialog { property int currentTypeIndex: backend.signal.id.currentIndex ?? 0 onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex } - } ConnectionsDialogForm { - id: form + id: form + + Connections { + target: root.backend + function onPopupShouldClose() { + root.close() + } + } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index f3c6fc00bcb..7f410f1c3ff 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -11,7 +11,7 @@ Column { id: root readonly property real horizontalSpacing: 10 - readonly property real verticalSpacing: 16 + readonly property real verticalSpacing: 12 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 property var backend @@ -27,8 +27,17 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { text: qsTr("Signal"); tooltip: qsTr("The name of the signal.") } - PopupLabel { text: qsTr("Action"); tooltip: qsTr("The type of the action.") } + PopupLabel { + width: root.columnWidth + text: qsTr("Signal") + tooltip: qsTr("Sets an interaction method that connects to the Target component.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("Action") + tooltip: qsTr("Sets an action that is associated with the selected Target component's Signal.") + } } Row { @@ -57,22 +66,48 @@ Column { onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend onActivated: backend.changeActionType(action.currentValue) - model: [ - { value: ConnectionModelStatementDelegate.CallFunction, text: qsTr("Call Function") }, - { value: ConnectionModelStatementDelegate.Assign, text: qsTr("Assign") }, - { value: ConnectionModelStatementDelegate.ChangeState, text: qsTr("Change State") }, - { value: ConnectionModelStatementDelegate.SetProperty, text: qsTr("Set Property") }, - { value: ConnectionModelStatementDelegate.PrintMessage, text: qsTr("Print Message") }, - { value: ConnectionModelStatementDelegate.Custom, text: qsTr("Unknown") } - ] + model: ListModel { + ListElement { + value: ConnectionModelStatementDelegate.CallFunction + text: qsTr("Call Function") + enabled: true + } + ListElement { + value: ConnectionModelStatementDelegate.Assign + text: qsTr("Assign") + enabled: true + } + ListElement { + value: ConnectionModelStatementDelegate.ChangeState + text: qsTr("Change State") + enabled: true + } + ListElement { + value: ConnectionModelStatementDelegate.SetProperty + text: qsTr("Set Property") + enabled: true + } + ListElement { + value: ConnectionModelStatementDelegate.PrintMessage + text: qsTr("Print Message") + enabled: true + } + ListElement { + value: ConnectionModelStatementDelegate.Custom + text: qsTr("Custom") + enabled: false + } + } } } StatementEditor { + width: root.width actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom horizontalSpacing: root.horizontalSpacing columnWidth: root.columnWidth statement: backend.okStatement + backend: root.backend spacing: root.verticalSpacing } @@ -80,6 +115,7 @@ Column { style: StudioTheme.Values.connectionPopupButtonStyle width: 160 buttonIcon: qsTr("Add Condition") + tooltip: qsTr("Sets a logical condition for the selected Signal. It works with the properties of the Target component.") iconSize: StudioTheme.Values.baseFontSize iconFont: StudioTheme.Constants.font anchors.horizontalCenter: parent.horizontalCenter @@ -92,6 +128,7 @@ Column { style: StudioTheme.Values.connectionPopupButtonStyle width: 160 buttonIcon: qsTr("Remove Condition") + tooltip: qsTr("Removes the logical condition for the Target component.") iconSize: StudioTheme.Values.baseFontSize iconFont: StudioTheme.Constants.font anchors.horizontalCenter: parent.horizontalCenter @@ -142,6 +179,7 @@ Column { style: StudioTheme.Values.connectionPopupButtonStyle width: 160 buttonIcon: qsTr("Add Else Statement") + tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.") iconSize: StudioTheme.Values.baseFontSize iconFont: StudioTheme.Constants.font anchors.horizontalCenter: parent.horizontalCenter @@ -155,6 +193,7 @@ Column { style: StudioTheme.Values.connectionPopupButtonStyle width: 160 buttonIcon: qsTr("Remove Else Statement") + tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.") iconSize: StudioTheme.Values.baseFontSize iconFont: StudioTheme.Constants.font anchors.horizontalCenter: parent.horizontalCenter @@ -166,42 +205,82 @@ Column { //Else Statement StatementEditor { + width: root.width actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom horizontalSpacing: root.horizontalSpacing columnWidth: root.columnWidth statement: backend.koStatement + backend: root.backend spacing: root.verticalSpacing visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition && backend.hasElse } + HelperWidgets.AbstractButton { + id: editorButton + buttonIcon: StudioTheme.Constants.codeEditor_medium + tooltip: qsTr("Write the conditions for the components and the signals manually.") + onClicked: expressionDialogLoader.show() + } + // Editor Rectangle { id: editor width: parent.width height: 150 - color: StudioTheme.Values.themeToolbarBackground + color: StudioTheme.Values.themeConnectionCodeEditor Text { - width: parent.width - 8 // twice the editor button margins - anchors.centerIn: parent - text: backend.source + id: code + anchors.fill: parent + anchors.margins: 4 + text: backend.indentedSource color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.myFontSize - wrapMode: Text.WordWrap + wrapMode: Text.Wrap + horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } - HelperWidgets.AbstractButton { - id: editorButton + Loader { + id: expressionDialogLoader + parent: editor + anchors.fill: parent + visible: false + active: visible - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: 4 + function show() { + expressionDialogLoader.visible = true + } - style: StudioTheme.Values.viewBarButtonStyle - buttonIcon: StudioTheme.Constants.edit_medium - tooltip: qsTr("Add something.") - onClicked: console.log("OPEN EDITOR") + sourceComponent: Item { + id: bindingEditorParent + + Component.onCompleted: { + bindingEditor.showWidget() + bindingEditor.text = backend.source + bindingEditor.showControls(false) + bindingEditor.setMultilne(true) + bindingEditor.updateWindowName() + } + + ActionEditor { + id: bindingEditor + + onRejected: { + hideWidget() + expressionDialogLoader.visible = false + } + + onAccepted: { + backend.setNewSource(bindingEditor.text) + hideWidget() + expressionDialogLoader.visible = false + } + } + } } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml index e48c0b48603..b783627c827 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml @@ -3,8 +3,8 @@ import QtQuick import QtQuick.Controls -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme as StudioTheme import ConnectionsEditorEditorBackend @@ -15,6 +15,12 @@ ListView { property bool adsFocus: false + // Temporarily remove due to dockwidget focus issue + //onAdsFocusChanged: { + // if (!root.adsFocus) + // dialog.close() + //} + clip: true interactive: true highlightMoveDuration: 0 @@ -24,8 +30,9 @@ ListView { HoverHandler { id: hoverHandler } - ScrollBar.vertical: HelperWidgets.ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: root x: root.width - verticalScrollBar.width y: 0 @@ -63,11 +70,27 @@ ListView { property int rowWidth: root.rowSpace / root.numColumns property int rowRest: root.rowSpace % root.numColumns + function addConnection() { + ConnectionsEditorEditorBackend.connectionModel.add() + if (root.currentItem) + dialog.popup(root.currentItem.delegateMouseArea) + } + + function resetIndex() { + root.model.currentIndex = -1 + root.currentIndex = -1 + dialog.backend.currentRow = -1 + } + data: [ ConnectionsDialog { id: dialog visible: false backend: root.model.delegate + + onClosing: function(event) { + root.resetIndex() + } } ] @@ -80,9 +103,13 @@ ListView { required property string target required property string action + property alias delegateMouseArea: mouseArea + + property bool hovered: mouseArea.containsMouse || toolTipArea.containsMouse + width: ListView.view.width height: root.style.squareControlSize.height - color: mouseArea.containsMouse ? + color: itemDelegate.hovered ? itemDelegate.ListView.isCurrentItem ? root.style.interactionHover : root.style.background.hover : "transparent" @@ -146,10 +173,12 @@ ListView { height: root.style.squareControlSize.height color: toolTipArea.containsMouse ? - itemDelegate.ListView.isCurrentItem ? root.style.interactionHover - : root.style.background.hover + itemDelegate.ListView.isCurrentItem ? root.style.interactionGlobalHover + : root.style.background.globalHover : "transparent" + visible: itemDelegate.hovered || itemDelegate.ListView.isCurrentItem + Text { anchors.fill: parent @@ -166,9 +195,13 @@ ListView { HelperWidgets.ToolTipArea { id: toolTipArea - tooltip: qsTr("This is a test.") + tooltip: qsTr("Removes the connection.") anchors.fill: parent - onClicked: root.model.remove(itemDelegate.index) + onClicked: { + if (itemDelegate.ListView.isCurrentItem) + dialog.close() + root.model.remove(itemDelegate.index) + } } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml b/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml index bd367debb75..54d8ee937f3 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml @@ -306,6 +306,7 @@ Rectangle { id: pill operatorModel: __operatorModel + maxTextWidth: root.width - 2 * StudioTheme.Values.flowMargin onRemove: function() { // If pill has focus due to selection or keyboard navigation diff --git a/share/qtcreator/qmldesigner/connectionseditor/Main.qml b/share/qtcreator/qmldesigner/connectionseditor/Main.qml index 74efe6dddfd..0b3752ac812 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/Main.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/Main.qml @@ -27,7 +27,7 @@ Rectangle { Rectangle { id: toolbar width: parent.width - height: StudioTheme.Values.doubleToolbarHeight + height: StudioTheme.Values.toolbarHeight color: StudioTheme.Values.themeToolbarBackground Column { @@ -38,13 +38,14 @@ Rectangle { anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin spacing: StudioTheme.Values.toolbarColumnSpacing - StudioControls.SearchBox { - id: searchBox - width: parent.width - style: StudioTheme.Values.searchControlStyle + // Temporarily remove search until functionality is provided by backend + //StudioControls.SearchBox { + // id: searchBox + // width: parent.width + // style: StudioTheme.Values.searchControlStyle - onSearchChanged: function(searchText) {} - } + // onSearchChanged: function(searchText) {} + //} Row { id: row @@ -56,7 +57,7 @@ Rectangle { id: connections buttonIcon: StudioTheme.Constants.connections_medium text: qsTr("Connections") - tooltip: qsTr("This is a tooltip.") + tooltip: qsTr("Sets logical connection between the components and the signals.") checked: true autoExclusive: true checkable: true @@ -66,7 +67,7 @@ Rectangle { id: bindings buttonIcon: StudioTheme.Constants.binding_medium text: qsTr("Bindings") - tooltip: qsTr("This is a tooltip.") + tooltip: qsTr("Sets the relation between the properties of two components to bind them together.") autoExclusive: true checkable: true } @@ -75,7 +76,7 @@ Rectangle { id: properties buttonIcon: StudioTheme.Constants.properties_medium text: qsTr("Properties") - tooltip: qsTr("This is a tooltip.") + tooltip: qsTr("Sets an additional property for the component.") autoExclusive: true checkable: true } @@ -91,14 +92,14 @@ Rectangle { id: addButton style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.add_medium - tooltip: qsTr("Add something.") + tooltip: qsTr("Adds a Connection, Binding, or Custom Property to the components.") onClicked: { if (connections.checked) - ConnectionsEditorEditorBackend.connectionModel.add() + connectionsListView.addConnection() else if (bindings.checked) - ConnectionsEditorEditorBackend.bindingModel.add() + bindingsListView.addBinding() else if (properties.checked) - ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() + propertiesListView.addProperty() } } } @@ -106,6 +107,7 @@ Rectangle { } ConnectionsListView { + id: connectionsListView visible: connections.checked width: parent.width height: parent.height - toolbar.height - column.spacing @@ -114,6 +116,7 @@ Rectangle { } BindingsListView { + id: bindingsListView visible: bindings.checked width: parent.width height: parent.height - toolbar.height - column.spacing @@ -122,6 +125,7 @@ Rectangle { } PropertiesListView { + id: propertiesListView visible: properties.checked width: parent.width height: parent.height - toolbar.height - column.spacing diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml b/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml index 936d06daee6..5ca53985d85 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml @@ -3,27 +3,52 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts +import StudioTheme as StudioTheme ItemDelegate { id: control - hoverEnabled: true - contentItem: Text { - leftPadding: 8 - rightPadding: 8 - text: control.text - font: control.font - opacity: enabled ? 1.0 : 0.3 - color: control.hovered ? "#111111" : "white" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + hoverEnabled: true + verticalPadding: 0 + rightPadding: 10 // ScrollBar thickness + + contentItem: RowLayout { + Text { + Layout.fillWidth: true + leftPadding: 8 + rightPadding: 8 + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.hovered ? control.style.text.selectedText : control.style.text.idle + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Item { + visible: control.childCount + width: 30 + height: 30 + + Text { + id: chevronLeft + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: control.style.baseIconFontSize + color: control.hovered ? control.style.text.selectedText : control.style.text.idle + text: StudioTheme.Constants.forward_medium + anchors.centerIn: parent + } + } } background: Rectangle { implicitWidth: 200 implicitHeight: 30 opacity: enabled ? 1 : 0.3 - color: control.hovered ? "#4DBFFF" : "transparent" + color: control.hovered ? control.style.interaction : "transparent" } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml b/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml index 366860b6757..549cb0fd7b5 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml @@ -8,6 +8,9 @@ import StudioTheme as StudioTheme T.TreeViewDelegate { id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + hoverEnabled: true implicitWidth: 200 implicitHeight: 30 @@ -28,8 +31,8 @@ T.TreeViewDelegate { Text { id: icon font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.smallIconFontSize - color: control.hovered ? "#111111" : "white" // TODO colors + font.pixelSize: control.style.smallIconFontSize + color: control.hovered ? control.style.text.selectedText : control.style.text.idle text: StudioTheme.Constants.sectionToggle rotation: control.expanded ? 0 : -90 anchors.centerIn: parent @@ -39,14 +42,14 @@ T.TreeViewDelegate { background: Rectangle { implicitWidth: 200 implicitHeight: 30 - color: control.hovered ? "#4DBFFF" : "transparent" + color: control.hovered ? control.style.interaction : "transparent" } contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 - color: control.hovered ? "#111111" : "white" + color: control.hovered ? control.style.text.selectedText : control.style.text.idle horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml b/share/qtcreator/qmldesigner/connectionseditor/Pill.qml index bbaefb16e4c..aa6cba03639 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/Pill.qml @@ -4,6 +4,7 @@ import QtQuick import StudioControls as StudioControls import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets FocusScope { id: root @@ -33,15 +34,12 @@ FocusScope { readonly property int margin: StudioTheme.Values.flowPillMargin property var operatorModel - width: { - if (root.isEditable()) { - if (root.isInvalid()) - return textInput.width + 1 + 2 * root.margin - else - return textInput.width + 1 - } - return textItem.contentWidth + icon.width + root.margin - } + property bool hovered: rootMouseArea.containsMouse + property bool selected: root.focus + + property int maxTextWidth: 600 + + width: row.width height: StudioTheme.Values.flowPillHeight onActiveFocusChanged: { @@ -57,26 +55,59 @@ FocusScope { root.remove() } - MouseArea { + HelperWidgets.ToolTipArea { id: rootMouseArea anchors.fill: parent hoverEnabled: true cursorShape: root.isEditable() ? Qt.IBeamCursor : Qt.ArrowCursor onClicked: root.forceActiveFocus() + tooltip: { + if (textItem.visible) { + if (textItem.truncated) + return textItem.text + else + return "" + } + + if (textMetrics.width > textInput.width) + return textInput.text + + return "" + } } Rectangle { id: pill anchors.fill: parent color: { - if (root.isShadow()) - return StudioTheme.Values.themeInteraction - if (root.isEditable()) + if (root.isIntermediate()) return "transparent" - return StudioTheme.Values.themePillBackground + if (root.isShadow()) + return StudioTheme.Values.themePillShadowBackground + + if (root.isInvalid() && root.selected) + return StudioTheme.Values.themeWarning + + if (root.hovered) { + if (root.isOperator()) + return StudioTheme.Values.themePillOperatorBackgroundHover + if (root.isLiteral()) + return StudioTheme.Values.themePillLiteralBackgroundHover + + return StudioTheme.Values.themePillDefaultBackgroundHover + } + + if (root.isLiteral()) + return StudioTheme.Values.themePillLiteralBackgroundIdle + + if (root.isOperator()) + return StudioTheme.Values.themePillOperatorBackgroundIdle + + return StudioTheme.Values.themePillDefaultBackgroundIdle } - border.color: root.isInvalid() ? StudioTheme.Values.themeWarning : "white" // TODO colors + border.color: root.isInvalid() ? StudioTheme.Values.themeWarning + : StudioTheme.Values.themePillOutline border.width: { if (root.isShadow()) return 0 @@ -84,7 +115,7 @@ FocusScope { return 1 if (root.isEditable()) return 0 - if (rootMouseArea.containsMouse || root.focus) + if (root.selected) return 1 return 0 @@ -93,31 +124,83 @@ FocusScope { Row { id: row - anchors.left: parent.left - anchors.leftMargin: root.margin - anchors.verticalCenter: parent.verticalCenter - visible: root.isOperator() || root.isProperty() || root.isShadow() + leftPadding: root.margin + rightPadding: icon.visible ? 0 : root.margin + + property int textWidth: Math.min(textMetrics.advanceWidth + 2, + root.maxTextWidth - row.leftPadding + - (icon.visible ? icon.width : root.margin)) + + TextMetrics { + id: textMetrics + text: textItem.visible ? textItem.text : textInput.text + font: textItem.font + } Text { id: textItem + width: row.textWidth + height: StudioTheme.Values.flowPillHeight + verticalAlignment: Text.AlignVCenter + textFormat: Text.PlainText + elide: Text.ElideMiddle font.pixelSize: StudioTheme.Values.baseFontSize - color: root.isShadow() ? StudioTheme.Values.themeTextSelectedTextColor - : StudioTheme.Values.themeTextColor + color: root.isShadow() ? StudioTheme.Values.themePillTextSelected + : StudioTheme.Values.themePillText text: root.isOperator() ? root.operatorModel.convertValueToName(root.value) : root.value - anchors.verticalCenter: parent.verticalCenter + visible: root.isOperator() || root.isProperty() || root.isShadow() + } + + TextInput { + id: textInput + x: root.isInvalid() ? root.margin : 0 + width: row.textWidth + height: StudioTheme.Values.flowPillHeight + topPadding: 1 + clip: true + font.pixelSize: StudioTheme.Values.baseFontSize + color: { + if (root.isIntermediate()) + return StudioTheme.Values.themePillTextEdit + if (root.isInvalid() && root.selected) + return StudioTheme.Values.themePillTextSelected + return StudioTheme.Values.themePillText + } + + selectedTextColor:StudioTheme.Values.themePillTextSelected + selectionColor: StudioTheme.Values.themeInteraction + + text: root.value + visible: !textItem.visible + enabled: root.isEditable() + + onEditingFinished: { + root.update(textInput.text) // emit + root.submit(textInput.cursorPosition) // emit + } + + Keys.onReleased: function (event) { + if (event.key === Qt.Key_Backspace) { + if (textInput.text !== "") + return + + root.remove() // emit + } + } } Item { id: icon - width: root.isShadow() ? root.margin : StudioTheme.Values.flowPillHeight + width: StudioTheme.Values.flowPillHeight height: StudioTheme.Values.flowPillHeight - visible: !root.isShadow() + visible: !root.isShadow() && !root.isIntermediate() Text { font.family: StudioTheme.Constants.iconFont.family font.pixelSize: StudioTheme.Values.smallIconFontSize - color: StudioTheme.Values.themeIconColor + color: root.isInvalid() && root.selected ? StudioTheme.Values.themePillTextSelected + : StudioTheme.Values.themePillText text: StudioTheme.Constants.close_small anchors.centerIn: parent } @@ -129,35 +212,5 @@ FocusScope { } } } - - TextInput { - id: textInput - - x: root.isInvalid() ? root.margin : 0 - height: StudioTheme.Values.flowPillHeight - topPadding: 1 - font.pixelSize: StudioTheme.Values.baseFontSize - color: (rootMouseArea.containsMouse || textInput.activeFocus) ? StudioTheme.Values.themeIconColor - : StudioTheme.Values.themeTextColor - text: root.value - visible: root.isEditable() - enabled: root.isEditable() - - //validator: RegularExpressionValidator { regularExpression: /^\S+/ } - - onEditingFinished: { - root.update(textInput.text) // emit - root.submit(textInput.cursorPosition) // emit - } - - Keys.onPressed: function (event) { - if (event.key === Qt.Key_Backspace) { - if (textInput.text !== "") - return - - root.remove() // emit - } - } - } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml index 9d17218b00a..da911854ada 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml @@ -14,11 +14,16 @@ Window { default property alias content: mainContent.children property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - width: 320 + width: 300 height: column.implicitHeight visible: true flags: Qt.FramelessWindowHint | Qt.Dialog - color: StudioTheme.Values.themePopoutBackground + + Rectangle { + anchors.fill: parent + color: StudioTheme.Values.themePopoutBackground + border.color: "#636363"//StudioTheme.Values.themeTextColor + } function ensureVerticalPosition() { if ((window.y + window.height) > (Screen.height - window.style.dialogScreenMargin)) { diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml index 6e5a117d8ce..6f8cb1ea06a 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml @@ -8,10 +8,10 @@ import StudioControls as StudioControls import StudioTheme as StudioTheme Text { - width: root.columnWidth color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.myFontSize property alias tooltip: area.tooltip + HelperWidgets.ToolTipArea { id: area anchors.fill: parent diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml index 26398e7acbb..8d62daacf8d 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -21,7 +21,7 @@ Column { PopupLabel { text: qsTr("Type") - tooltip: qsTr("The type of the property") + tooltip: qsTr("Sets the category of the Local Custom Property.") } StudioControls.TopLevelComboBox { id: type @@ -38,8 +38,17 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { text: qsTr("Name") ; tooltip: qsTr("The name of the property.")} - PopupLabel { text: qsTr("Value"); tooltip: qsTr("The value of the property.") } + PopupLabel { + width: root.columnWidth + text: qsTr("Name") + tooltip: qsTr("Sets a name for the Local Custom Property.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("Value") + tooltip: qsTr("Sets a valid Local Custom Property value.") + } } Row { diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml index 2f4382020b3..53d1edea95a 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml @@ -3,8 +3,8 @@ import QtQuick import QtQuick.Controls -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme as StudioTheme import ConnectionsEditorEditorBackend @@ -15,6 +15,12 @@ ListView { property bool adsFocus: false + // Temporarily remove due to dockwidget focus issue + //onAdsFocusChanged: { + // if (!root.adsFocus) + // dialog.close() + //} + clip: true interactive: true highlightMoveDuration: 0 @@ -24,8 +30,9 @@ ListView { HoverHandler { id: hoverHandler } - ScrollBar.vertical: HelperWidgets.ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: root x: root.width - verticalScrollBar.width y: 0 @@ -62,11 +69,26 @@ ListView { property int rowWidth: root.rowSpace / root.numColumns property int rowRest: root.rowSpace % root.numColumns + function addProperty() { + ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() + if (root.currentItem) + dialog.popup(root.currentItem.delegateMouseArea) + } + + function resetIndex() { + root.model.currentIndex = -1 + root.currentIndex = -1 + } + data: [ PropertiesDialog { id: dialog visible: false backend: root.model.delegate + + onClosing: function(event) { + root.resetIndex() + } } ] @@ -80,6 +102,8 @@ ListView { required property string type required property string value + property alias delegateMouseArea: mouseArea + width: ListView.view.width height: root.style.squareControlSize.height color: mouseArea.containsMouse ? @@ -89,7 +113,9 @@ ListView { MouseArea { id: mouseArea + anchors.fill: parent + hoverEnabled: true property int currentIndex: root.currentIndex @@ -177,9 +203,13 @@ ListView { HelperWidgets.ToolTipArea { id: toolTipArea - tooltip: qsTr("This is a test.") + tooltip: qsTr("Removes the property.") anchors.fill: parent - onClicked: root.model.remove(itemDelegate.index) + onClicked: { + if (itemDelegate.ListView.isCurrentItem) + dialog.close() + root.model.remove(itemDelegate.index) + } } } @@ -188,6 +218,5 @@ ListView { highlight: Rectangle { color: root.style.interaction - width: 600 } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml b/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml index 3e7e552a28f..f198d84a103 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml @@ -18,16 +18,24 @@ Column { property var statement - //implicitWidth: Math.max(16, container.childrenRect.width + container.childrenRect.x) - //implicitHeight: Math.max(16, container.childrenRect.height + container.childrenRect.y) + property var backend // Call Function Row { visible: root.actionType === ConnectionModelStatementDelegate.CallFunction spacing: root.horizontalSpacing - PopupLabel { text: qsTr("Item") ; tooltip: qsTr("The target item of the function.")} - PopupLabel { text: qsTr("Method") ; tooltip: qsTr("The name of the function.")} + PopupLabel { + width: root.columnWidth + text: qsTr("Item") + tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("Method") + tooltip: qsTr("Sets the item component's method that is affected by the Target component's Signal.") + } } Row { @@ -46,7 +54,7 @@ Column { onCurrentTypeIndexChanged: functionId.currentIndex = functionId.currentTypeIndex } - StudioControls.TopLevelComboBox { + StudioControls.TopLevelComboBox { id: functionName style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth @@ -63,8 +71,16 @@ Column { visible: root.actionType === ConnectionModelStatementDelegate.Assign spacing: root.horizontalSpacing - PopupLabel { text: qsTr("From") ; tooltip: qsTr("The Property to assign from.")} - PopupLabel { text: qsTr("To"); tooltip: qsTr("The Property to assign to.") } + PopupLabel { + width: root.columnWidth + text: qsTr("From") + tooltip: qsTr("Sets the component and its property from which the value is copied when the Target component initiates the Signal.") + } + PopupLabel { + width: root.columnWidth + text: qsTr("To") + tooltip: qsTr("Sets the component and its property to which the copied value is assigned when the Target component initiates the Signal.") + } } Row { @@ -132,8 +148,17 @@ Column { visible: root.actionType === ConnectionModelStatementDelegate.ChangeState spacing: root.horizontalSpacing - PopupLabel { text: qsTr("State Group"); tooltip: qsTr("The State Group.") } - PopupLabel { text: qsTr("State"); tooltip: qsTr("The State .") } + PopupLabel { + width: root.columnWidth + text: qsTr("State Group") + tooltip: qsTr("Sets a State Group that is accessed when the Target component initiates the Signal.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("State") + tooltip: qsTr("Sets a State within the assigned State Group that is accessed when the Target component initiates the Signal.") + } } Row { @@ -169,8 +194,17 @@ Column { visible: root.actionType === ConnectionModelStatementDelegate.SetProperty spacing: root.horizontalSpacing - PopupLabel { text: qsTr("Item"); tooltip: qsTr("The Item.")} - PopupLabel { text: qsTr("Property"); tooltip: qsTr("The property of the item.")} + PopupLabel { + width: root.columnWidth + text: qsTr("Item") + tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + } + + PopupLabel { + width: root.columnWidth + text: qsTr("Property") + tooltip: qsTr("Sets the property of the component that is affected by the action of the Target component's Signal.") + } } Row { @@ -203,8 +237,10 @@ Column { } PopupLabel { + width: root.columnWidth visible: root.actionType === ConnectionModelStatementDelegate.SetProperty text: qsTr("Value") + tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") } StudioControls.TextField { @@ -222,9 +258,10 @@ Column { // Print Message PopupLabel { + width: root.columnWidth visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage text: qsTr("Message") - tooltip: qsTr("The message that is printed.") + tooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } StudioControls.TextField { @@ -243,9 +280,7 @@ Column { PopupLabel { visible: root.actionType === ConnectionModelStatementDelegate.Custom text: qsTr("Custom Connections can only be edited with the binding editor") - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 30 + width: root.width horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap } diff --git a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml b/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml index 197b2c015aa..fad01c32c4d 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml @@ -71,7 +71,7 @@ Controls.Popup { width: stack.width height: 30 visible: root.listModel.parentName !== "" - color: backMouseArea.containsMouse ? "#4DBFFF" : "transparent" + color: backMouseArea.containsMouse ? StudioTheme.Values.themeInteraction : "transparent" MouseArea { id: backMouseArea @@ -80,7 +80,7 @@ Controls.Popup { onClicked: { stack.pop(Controls.StackView.Immediate) - root.listModel.goUp() //treeModel.pop() + root.listModel.goUp() } } @@ -95,7 +95,8 @@ Controls.Popup { id: chevronLeft font.family: StudioTheme.Constants.iconFont.family font.pixelSize: root.style.baseIconFontSize - color: backMouseArea.containsMouse ? "#111111" : "white" // TODO colors + color: backMouseArea.containsMouse ? StudioTheme.Values.themeTextSelectedTextColor + : StudioTheme.Values.themeTextColor text: StudioTheme.Constants.back_medium anchors.centerIn: parent } @@ -104,7 +105,8 @@ Controls.Popup { Text { anchors.verticalCenter: parent.verticalCenter text: root.listModel.parentName - color: backMouseArea.containsMouse ? "#111111" : "white" // TODO colors + color: backMouseArea.containsMouse ? StudioTheme.Values.themeTextSelectedTextColor + : StudioTheme.Values.themeTextColor horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter elide: Text.ElideRight @@ -133,7 +135,7 @@ Controls.Popup { boundsMovement: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds - Controls.ScrollBar.vertical: HelperWidgets.ScrollBar { + Controls.ScrollBar.vertical: StudioControls.TransientScrollBar { id: listScrollBar parent: listView x: listView.width - listScrollBar.width @@ -193,7 +195,7 @@ Controls.Popup { boundsMovement: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds - Controls.ScrollBar.vertical: HelperWidgets.ScrollBar { + Controls.ScrollBar.vertical: StudioControls.TransientScrollBar { id: treeScrollBar parent: treeView x: treeView.width - treeScrollBar.width @@ -272,12 +274,9 @@ Controls.Popup { width: textItem.contentWidth + 2 * StudioTheme.Values.flowPillMargin height: StudioTheme.Values.flowPillHeight - color: "#161616" + color: mouseArea.containsMouse ? StudioTheme.Values.themePillOperatorBackgroundHover + : StudioTheme.Values.themePillOperatorBackgroundIdle radius: StudioTheme.Values.flowPillRadius - border { - color: "white" - width: mouseArea.containsMouse ? 1 : 0 - } HelperWidgets.ToolTipArea { id: mouseArea @@ -291,7 +290,7 @@ Controls.Popup { Text { id: textItem font.pixelSize: StudioTheme.Values.baseFontSize - color: StudioTheme.Values.themeTextColor + color: StudioTheme.Values.themePillText text: delegate.name anchors.centerIn: parent } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index 1932af0eef9..306f5405b30 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -13,6 +13,11 @@ import ContentLibraryBackend Item { id: root + property bool adsFocus: false + // objectName is used by the dock widget to find this particular ScrollView + // and set the ads focus on it. + objectName: "__mainSrollView" + // Called also from C++ to close context menu on focus out function closeContextMenu() { @@ -96,6 +101,7 @@ Item { ContentLibraryMaterialsView { id: materialsView + adsFocus: root.adsFocus width: root.width searchBox: searchBox @@ -110,6 +116,7 @@ Item { ContentLibraryTexturesView { id: texturesView + adsFocus: root.adsFocus width: root.width model: ContentLibraryBackend.texturesModel sectionCategory: "ContentLib_Tex" @@ -120,6 +127,7 @@ Item { ContentLibraryTexturesView { id: environmentsView + adsFocus: root.adsFocus width: root.width model: ContentLibraryBackend.environmentsModel sectionCategory: "ContentLib_Env" @@ -130,6 +138,7 @@ Item { ContentLibraryEffectsView { id: effectsView + adsFocus: root.adsFocus width: root.width searchBox: searchBox diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml index 1fe8f52c134..7cb9a4721d8 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml @@ -91,6 +91,8 @@ HelperWidgets.ScrollView { text: { if (!ContentLibraryBackend.effectsModel.bundleExists) qsTr("No effects available.") + else if (!ContentLibraryBackend.rootView.isQt6Project) + qsTr("Content Library effects are not supported in Qt5 projects.") else if (!ContentLibraryBackend.rootView.hasQuick3DImport) qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 975e65e266b..f9678dcad80 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -99,6 +99,8 @@ HelperWidgets.ScrollView { text: { if (!materialsModel.matBundleExists) qsTr("No materials available. Make sure you have internet connection.") + else if (!ContentLibraryBackend.rootView.isQt6Project) + qsTr("Content Library materials are not supported in Qt5 projects.") else if (!ContentLibraryBackend.rootView.hasQuick3DImport) qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") else if (!materialsModel.hasRequiredQuick3DImport) diff --git a/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml b/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml index b246eb62daf..43460b7f719 100644 --- a/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml +++ b/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml @@ -20,13 +20,24 @@ Rectangle { border.color: StudioTheme.Values.themeControlOutline border.width: StudioTheme.Values.border - Connections { - target: rootView + function handlePosIntChanged() { + posIntSpin.value = posInt + } - // Spinboxes lose the initial binding if the value changes so we need these connections - onPosIntChanged: posIntSpin.realValue = rootView.posInt - onRotIntChanged: rotIntSpin.realValue = rootView.rotInt - onScaleIntChanged: scaleIntSpin.realValue = rootView.scaleInt + function handleRotIntChanged() { + rotIntSpin.value = rotInt + } + + function handleScaleIntChanged() { + scaleIntSpin.value = scaleInt + } + + // Connect context object signals to our handler functions + // Spinboxes lose the initial binding if the value changes so we need these handlers + Component.onCompleted: { + onPosIntChanged.connect(handlePosIntChanged); + onRotIntChanged.connect(handleRotIntChanged); + onScaleIntChanged.connect(handleScaleIntChanged); } ColumnLayout { @@ -94,7 +105,7 @@ Rectangle { Layout.column: 0 Layout.row: 1 Layout.minimumWidth: 100 - checked: rootView.posEnabled + checked: posEnabled actionIndicatorVisible: false hoverEnabled: true @@ -102,27 +113,26 @@ Rectangle { ToolTip.text: qsTr("Snap position.") ToolTip.delay: root.toolTipDelay - onToggled: rootView.posEnabled = checked + onToggled: posEnabled = checked } - StudioControls.RealSpinBox { + HelperWidgets.DoubleSpinBox { id: posIntSpin Layout.fillWidth: true Layout.column: 1 Layout.row: 1 Layout.leftMargin: 10 - realFrom: 1 - realTo: 10000 - realValue: rootView.posInt - realStepSize: 1 - actionIndicatorVisible: false + minimumValue: 1 + maximumValue: 10000 + value: posInt + stepSize: 1 + decimals: 0 - hoverEnabled: true - ToolTip.visible: hovered + ToolTip.visible: hover ToolTip.text: qsTr("Snap interval for move gizmo.") ToolTip.delay: root.toolTipDelay - onRealValueChanged: rootView.posInt = realValue + onValueChanged: posInt = value } StudioControls.CheckBox { @@ -130,7 +140,7 @@ Rectangle { Layout.column: 0 Layout.row: 2 Layout.minimumWidth: 100 - checked: rootView.rotEnabled + checked: rotEnabled actionIndicatorVisible: false hoverEnabled: true @@ -138,27 +148,26 @@ Rectangle { ToolTip.text: qsTr("Snap rotation.") ToolTip.delay: root.toolTipDelay - onToggled: rootView.rotEnabled = checked + onToggled: rotEnabled = checked } - StudioControls.RealSpinBox { + HelperWidgets.DoubleSpinBox { id: rotIntSpin Layout.fillWidth: true Layout.column: 1 Layout.row: 2 Layout.leftMargin: 10 - realFrom: 1 - realTo: 90 - realValue: rootView.rotInt - realStepSize: 1 - actionIndicatorVisible: false + minimumValue: 1 + maximumValue: 90 + value: rotInt + stepSize: 1 + decimals: 0 - hoverEnabled: true - ToolTip.visible: hovered + ToolTip.visible: hover ToolTip.text: qsTr("Snap interval in degrees for rotation gizmo.") ToolTip.delay: root.toolTipDelay - onRealValueChanged: rootView.rotInt = realValue + onValueChanged: rotInt = value } StudioControls.CheckBox { @@ -166,7 +175,7 @@ Rectangle { Layout.column: 0 Layout.row: 3 Layout.minimumWidth: 100 - checked: rootView.scaleEnabled + checked: scaleEnabled actionIndicatorVisible: false hoverEnabled: true @@ -174,27 +183,26 @@ Rectangle { ToolTip.text: qsTr("Snap scale.") ToolTip.delay: root.toolTipDelay - onToggled: rootView.scaleEnabled = checked + onToggled: scaleEnabled = checked } - StudioControls.RealSpinBox { + HelperWidgets.DoubleSpinBox { id: scaleIntSpin Layout.fillWidth: true Layout.column: 1 Layout.row: 3 Layout.leftMargin: 10 - realFrom: 1 - realTo: 100 - realValue: rootView.scaleInt - realStepSize: 1 - actionIndicatorVisible: false + minimumValue: 1 + maximumValue: 100 + value: scaleInt + stepSize: 1 + decimals: 0 - hoverEnabled: true - ToolTip.visible: hovered + ToolTip.visible: hover ToolTip.text: qsTr("Snap interval for scale gizmo in percentage of original scale.") ToolTip.delay: root.toolTipDelay - onRealValueChanged: rootView.scaleInt = realValue + onValueChanged: scaleInt = value } StudioControls.CheckBox { @@ -204,7 +212,7 @@ Rectangle { Layout.column: 0 Layout.row: 4 Layout.columnSpan: 3 - checked: rootView.absolute + checked: absolute actionIndicatorVisible: false hoverEnabled: true @@ -212,7 +220,7 @@ Rectangle { ToolTip.text: qsTr("Toggles if the position snaps to absolute values or relative to object position.") ToolTip.delay: root.toolTipDelay - onToggled: rootView.absolute = checked + onToggled: absolute = checked } Text { @@ -236,7 +244,7 @@ Rectangle { text: qsTr("Reset All") Layout.bottomMargin: 8 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - onClicked: rootView.resetDefaults() + onClicked: resetDefaults() } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml index 456539d13cf..49731b37130 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml @@ -25,7 +25,7 @@ Item { } EffectMakerPreview { - + mainRoot: root } Rectangle { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml index 10f6b516024..c23e95e5628 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml @@ -14,6 +14,12 @@ Column { width: parent.width + required property Item mainRoot + property var effectMakerModel: EffectMakerBackend.effectMakerModel + property alias source: source + // The delay in ms to wait until updating the effect + readonly property int updateDelay: 200 + Rectangle { // toolbar width: parent.width height: StudioTheme.Values.toolbarHeight @@ -25,40 +31,46 @@ Column { anchors.rightMargin: 5 anchors.leftMargin: 5 + PreviewImagesComboBox { + id: imagesComboBox + + mainRoot: root.mainRoot + } + Item { Layout.fillWidth: true } HelperWidgets.AbstractButton { - enabled: previewImage.scale > .4 + enabled: sourceImage.scale > .4 style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.zoomOut_medium tooltip: qsTr("Zoom out") onClicked: { - previewImage.scale -= .2 + sourceImage.scale -= .2 } } HelperWidgets.AbstractButton { - enabled: previewImage.scale < 2 + enabled: sourceImage.scale < 2 style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.zoomIn_medium tooltip: qsTr("Zoom In") onClicked: { - previewImage.scale += .2 + sourceImage.scale += .2 } } HelperWidgets.AbstractButton { - enabled: previewImage.scale !== 1 + enabled: sourceImage.scale !== 1 style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.fitAll_medium tooltip: qsTr("Zoom Fit") onClicked: { - previewImage.scale = 1 + sourceImage.scale = 1 } } @@ -99,29 +111,90 @@ Column { } Rectangle { // preview image - id: previewImageBg + id: preview color: "#dddddd" width: parent.width height: 200 clip: true - Image { - id: previewImage - - anchors.margins: 5 + Item { // Source item as a canvas (render target) for effect + id: source anchors.fill: parent - fillMode: Image.PreserveAspectFit - smooth: true + layer.enabled: true + layer.mipmap: true + layer.smooth: true - source: "images/qt_logo.png" // TODO: update image + Image { + id: sourceImage + anchors.margins: 5 + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: imagesComboBox.selectedImage + smooth: true - Behavior on scale { - NumberAnimation { - duration: 200 - easing.type: Easing.OutQuad + Behavior on scale { + NumberAnimation { + duration: 200 + easing.type: Easing.OutQuad + } } } } + + Item { + id: componentParent + width: source.width + height: source.height + anchors.centerIn: parent + scale: 1 //TODO should come from toolbar + // Cache the layer. This way heavy shaders rendering doesn't + // slow down code editing & rest of the UI. + layer.enabled: true + layer.smooth: true + } + + // Create a dummy parent to host the effect qml object + function createNewComponent() { + var oldComponent = componentParent.children[0]; + if (oldComponent) + oldComponent.destroy(); + + try { + const newObject = Qt.createQmlObject( + effectMakerModel.qmlComponentString, + componentParent, + "" + ); + effectMakerModel.resetEffectError(0); + } catch (error) { + let errorString = "QML: ERROR: "; + let errorLine = -1; + if (error.qmlErrors.length > 0) { + // Show the first QML error + let e = error.qmlErrors[0]; + errorString += e.lineNumber + ": " + e.message; + errorLine = e.lineNumber; + } + effectMakerModel.setEffectError(errorString, 0, errorLine); + } + } + + Connections { + target: effectMakerModel + function onShadersBaked() { + console.log("Shaders Baked!") + //updateTimer.restart(); // Disable for now + } + } + + Timer { + id: updateTimer + interval: updateDelay; + onTriggered: { + effectMakerModel.updateQmlComponent(); + createNewComponent(); + } + } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml index 9c52f687aac..b076e152cc0 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml @@ -30,27 +30,27 @@ StudioControls.ComboBox { var a = mainRoot.mapToGlobal(0, 0) var b = root.mapToItem(mainRoot, 0, 0) - effectNodesWindow.x = a.x + b.x + root.width - effectNodesWindow.width - effectNodesWindow.y = a.y + b.y + root.height - 1 + window.x = a.x + b.x + root.width - window.width + window.y = a.y + b.y + root.height - 1 - effectNodesWindow.show() - effectNodesWindow.requestActivate() + window.show() + window.requestActivate() } function onAboutToHide() { - effectNodesWindow.hide() + window.hide() } } Window { - id: effectNodesWindow + id: window width: row.width + 2 // 2: scrollView left and right 1px margins height: Math.min(800, Math.min(row.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar - flags: Qt.Popup | Qt.FramelessWindowHint + flags: Qt.Dialog | Qt.FramelessWindowHint - onActiveChanged: { - if (!active && !root.hover) + onActiveFocusItemChanged: { + if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) root.popup.close() } @@ -74,7 +74,7 @@ StudioControls.ComboBox { var a = mainRoot.mapToGlobal(0, 0) var b = root.mapToItem(mainRoot, 0, 0) - effectNodesWindow.x = a.x + b.x + root.width - row.width + window.x = a.x + b.x + root.width - row.width } padding: 10 diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml new file mode 100644 index 00000000000..a53a923ca6b --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml @@ -0,0 +1,140 @@ +// 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 QtQuickDesignerTheme +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme 1.0 as StudioTheme +import EffectMakerBackend + +StudioControls.ComboBox { + id: root + + actionIndicatorVisible: false + x: 5 + width: 60 + + model: [selectedImage] + + // hide default popup + popup.width: 0 + popup.height: 0 + + required property Item mainRoot + + property var images: ["images/preview0.png", + "images/preview1.png", + "images/preview2.png", + "images/preview3.png", + "images/preview4.png"] + property string selectedImage: images[0] + + Connections { + target: root.popup + + function onAboutToShow() { + var a = mainRoot.mapToGlobal(0, 0) + var b = root.mapToItem(mainRoot, 0, 0) + + window.x = a.x + b.x + root.width - window.width + window.y = a.y + b.y + root.height - 1 + + window.show() + window.requestActivate() + } + + function onAboutToHide() { + window.hide() + } + } + + contentItem: Item { + width: 30 + height: 30 + + Image { + source: root.selectedImage + fillMode: Image.PreserveAspectFit + smooth: true + anchors.fill: parent + anchors.margins: 1 + } + } + + Window { + id: window + + width: col.width + 2 // 2: scrollView left and right 1px margins + height: Math.min(800, Math.min(col.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar + flags: Qt.Dialog | Qt.FramelessWindowHint + + onActiveFocusItemChanged: { + if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) + root.popup.close() + } + + Rectangle { + anchors.fill: parent + color: StudioTheme.Values.themePanelBackground + border.color: StudioTheme.Values.themeInteraction + border.width: 1 + + HelperWidgets.ScrollView { + anchors.fill: parent + anchors.margins: 1 + clip: true + + Column { + id: col + + onWidthChanged: { + // Needed to update on first window showing, as row.width only gets + // correct value after the window is shown, so first showing is off + + var a = mainRoot.mapToGlobal(0, 0) + var b = root.mapToItem(mainRoot, 0, 0) + + window.x = a.x + b.x + root.width - col.width + } + + padding: 10 + spacing: 10 + + Repeater { + model: root.images + + Rectangle { + required property int index + required property var modelData + + color: "transparent" + border.color: root.selectedImage === modelData ? StudioTheme.Values.themeInteraction + : "transparent" + + width: 200 + height: 200 + + Image { + source: modelData + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + anchors.margins: 1 + } + + MouseArea { + anchors.fill: parent + + onClicked: { + root.selectedImage = root.images[index] + root.popup.close() + } + } + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview0.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview0.png new file mode 100644 index 00000000000..3d2552f30a9 Binary files /dev/null and b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview0.png differ diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview1.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview1.png new file mode 100644 index 00000000000..c8a58e8e57d Binary files /dev/null and b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview1.png differ diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview2.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview2.png new file mode 100644 index 00000000000..f5bbda85eb8 Binary files /dev/null and b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview2.png differ diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview3.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview3.png new file mode 100644 index 00000000000..3c964b8d4dd Binary files /dev/null and b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview3.png differ diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview4.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview4.png new file mode 100644 index 00000000000..288207c8428 Binary files /dev/null and b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/preview4.png differ diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/qt_logo.png b/share/qtcreator/qmldesigner/effectMakerQmlSources/images/qt_logo.png deleted file mode 100644 index 5181f1b5ab4..00000000000 Binary files a/share/qtcreator/qmldesigner/effectMakerQmlSources/images/qt_logo.png and /dev/null differ diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AddModuleView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AddModuleView.qml index 585c6a6d14c..6ed7052b8e5 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AddModuleView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AddModuleView.qml @@ -11,6 +11,8 @@ import ItemLibraryBackend Column { id: root + property alias adsFocus: listView.adsFocus + spacing: 5 signal back() diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml index 05d8d6db9c2..a90fbaaa15f 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml @@ -46,6 +46,12 @@ itemLibraryModel [ */ Item { id: itemsView + + property bool adsFocus: false + // objectName is used by the dock widget to find this particular ScrollView + // and set the ads focus on it. + objectName: "__mainSrollView" + property string importToRemove property string importToAdd property string componentSource @@ -217,6 +223,7 @@ Item { id: verticalView HelperWidgets.ScrollView { id: verticalScrollView + adsFocus: itemsView.adsFocus anchors.fill: parent clip: true interactive: !itemContextMenu.opened && !moduleContextMenu.opened && !ItemLibraryBackend.rootView.isDragging @@ -327,6 +334,7 @@ Item { leftPadding: 5 HelperWidgets.ScrollView { id: horizontalScrollView + adsFocus: itemsView.adsFocus width: 270 height: parent.height clip: true @@ -427,6 +435,7 @@ Item { } HelperWidgets.ScrollView { id: itemScrollView + adsFocus: itemsView.adsFocus width: itemsView.width - 275 height: itemsView.height interactive: !itemContextMenu.opened && !moduleContextMenu.opened && !ItemLibraryBackend.rootView.isDragging @@ -468,6 +477,7 @@ Item { Component { id: addModuleView AddModuleView { + adsFocus: itemsView.adsFocus onBack: isAddModuleView = false } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 9272d2321e4..2d7e8fe8fef 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -592,7 +592,9 @@ Item { anchors.centerIn: parent text: { - if (!materialBrowserModel.hasQuick3DImport) + if (!materialBrowserModel.isQt6Project) + qsTr("Material Browser is not supported in Qt5 projects.") + else if (!materialBrowserModel.hasQuick3DImport) qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") else if (!materialBrowserModel.hasMaterialLibrary) qsTr("Material Browser is disabled inside a non-visual component.") diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml index 005b4dfff1d..39b0929a442 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -35,7 +35,9 @@ PropertyEditorPane { Text { text: { - if (!hasQuick3DImport) + if (!isQt6Project) + qsTr("Material Editor is not supported in Qt5 projects.") + else if (!hasQuick3DImport) qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") else if (!hasMaterialLibrary) qsTr("Material Editor is disabled inside a non-visual component.") diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml index ef8e020a5d7..d85aab300d7 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import QtQuick import QtQuick.Layouts -import StudioControls as SC +import StudioControls as StudioControls import StudioTheme as StudioTheme import BackendApi @@ -22,8 +22,8 @@ Item { anchors.fill: parent Item { - x: DialogValues.detailsPanePadding // left padding - width: parent.width - DialogValues.detailsPanePadding * 2 // right padding + x: DialogValues.detailsPanePadding * 2 // left padding + width: parent.width - DialogValues.detailsPanePadding * 3 // right padding height: parent.height Column { @@ -44,6 +44,7 @@ Item { } Flickable { + id: flickable width: parent.width height: parent.height - detailsHeading.height - DialogValues.defaultPadding - savePresetButton.height @@ -52,14 +53,27 @@ Item { boundsBehavior: Flickable.StopAtBounds clip: true - ScrollBar.vertical: SC.VerticalScrollBar {} + HoverHandler { id: hoverHandler } + + ScrollBar.vertical: StudioControls.TransientScrollBar { + id: verticalScrollBar + style: StudioTheme.Values.viewStyle + parent: flickable + x: flickable.width - verticalScrollBar.width + y: 0 + height: flickable.availableHeight + orientation: Qt.Vertical + + show: (hoverHandler.hovered || flickable.focus || verticalScrollBar.inUse) + && verticalScrollBar.isNeeded + } Column { id: scrollContent width: parent.width - DialogValues.detailsPanePadding spacing: DialogValues.defaultPadding - SC.TextField { + StudioControls.TextField { id: projectNameTextField actionIndicatorVisible: false translationIndicatorVisible: false @@ -85,7 +99,7 @@ Item { RowLayout { // Project location width: parent.width - SC.TextField { + StudioControls.TextField { Layout.fillWidth: true id: projectLocationTextField actionIndicatorVisible: false @@ -102,7 +116,7 @@ Item { value: projectLocationTextField.text } - SC.AbstractButton { + StudioControls.AbstractButton { implicitWidth: 30 iconSize: 20 visible: true @@ -114,7 +128,7 @@ Item { if (newLocation) projectLocationTextField.text = newLocation } - } // SC.AbstractButton + } } // Project location RowLayout Item { width: parent.width; height: DialogValues.narrowSpacing(7) } @@ -171,7 +185,7 @@ Item { } // Text } // RowLayout - SC.CheckBox { + StudioControls.CheckBox { id: defaultLocationCheckbox actionIndicatorVisible: false text: qsTr("Use as default project location") @@ -187,7 +201,7 @@ Item { Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor } - SC.ComboBox { // Screen Size ComboBox + StudioControls.ComboBox { // Screen Size ComboBox id: screenSizeComboBox actionIndicatorVisible: false currentIndex: -1 @@ -253,7 +267,7 @@ Item { } // content items - SC.RealSpinBox { + StudioControls.RealSpinBox { id: widthField actionIndicatorVisible: false implicitWidth: 70 @@ -274,7 +288,7 @@ Item { value: widthField.realValue } - SC.RealSpinBox { + StudioControls.RealSpinBox { id: heightField actionIndicatorVisible: false implicitWidth: 70 @@ -368,7 +382,7 @@ Item { color: DialogValues.dividerlineColor } - SC.CheckBox { + StudioControls.CheckBox { id: useQtVirtualKeyboard actionIndicatorVisible: false text: qsTr("Use Qt Virtual Keyboard") @@ -389,7 +403,7 @@ Item { color: DialogValues.textColor } - SC.ComboBox { // Target Qt Version ComboBox + StudioControls.ComboBox { // Target Qt Version ComboBox id: qtVersionComboBox actionIndicatorVisible: false implicitWidth: 82 @@ -421,7 +435,7 @@ Item { } // ScrollView } // Column - SC.AbstractButton { + StudioControls.AbstractButton { id: savePresetButton width: StudioTheme.Values.singleControlColumnWidth buttonIcon: qsTr("Save Custom Preset") @@ -459,7 +473,7 @@ Item { color: DialogValues.textColor } - SC.TextField { + StudioControls.TextField { id: presetNameTextField actionIndicatorVisible: false translationIndicatorVisible: false diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PresetView.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PresetView.qml index c0903bc03b8..e72cee1840f 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PresetView.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PresetView.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import QtQuick import QtQuick.Layouts -import StudioControls as SC +import StudioControls as StudioControls import StudioTheme as StudioTheme import BackendApi @@ -23,12 +23,17 @@ ScrollView { property bool selectLast: false ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical: SC.VerticalScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { + id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: scrollView - x: scrollView.width + (DialogValues.gridMargins - - StudioTheme.Values.scrollBarThickness) * 0.5 + x: scrollView.width + (DialogValues.gridMargins - verticalScrollBar.width) * 0.5 y: scrollView.topPadding height: scrollView.availableHeight + orientation: Qt.Vertical + + show: (scrollView.hovered || scrollView.focus || verticalScrollBar.inUse) + && verticalScrollBar.isNeeded } contentWidth: gridView.contentItem.childrenRect.width diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml index d89d2d5157e..b87add5c510 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml @@ -6,7 +6,7 @@ import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts -import StudioControls as SC +import StudioControls as StudioControls import StudioTheme as StudioTheme import BackendApi @@ -59,7 +59,7 @@ Item { } } - SC.ComboBox { // Style Filter ComboBox + StudioControls.ComboBox { // Style Filter ComboBox id: styleComboBox actionIndicatorVisible: false currentIndex: 0 @@ -93,10 +93,17 @@ Item { width: parent.width ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical: SC.VerticalScrollBar { - id: styleScrollBar - x: stylesList.width + (DialogValues.stylesPanePadding - - StudioTheme.Values.scrollBarThickness) * 0.5 + ScrollBar.vertical: StudioControls.TransientScrollBar { + id: verticalScrollBar + style: StudioTheme.Values.viewStyle + parent: scrollView + x: scrollView.width + (DialogValues.gridMargins - verticalScrollBar.width) * 0.5 + y: scrollView.topPadding + height: scrollView.availableHeight + orientation: Qt.Vertical + + show: (scrollView.hovered || scrollView.focus || verticalScrollBar.inUse) + && verticalScrollBar.isNeeded } ListView { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml index a6ee7eb476a..03e42a974fe 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml @@ -9,72 +9,93 @@ AutoTypes { Type { typeNames: ["int"] + module: "QML" sourceFile: "IntEditorTemplate.template" } Type { typeNames: ["real", "double", "qreal"] + module: "QML" sourceFile: "RealEditorTemplate.template" } Type { typeNames: ["string", "QString"] + module: "QML" sourceFile: "StringEditorTemplate.template" } Type { - typeNames: ["QUrl", "url"] + typeNames: ["url", "QUrl"] + module: "QML" sourceFile: "UrlEditorTemplate.template" } Type { typeNames: ["bool", "boolean"] + module: "QML" sourceFile: "BooleanEditorTemplate.template" } Type { typeNames: ["color", "QColor"] + module: "QtQuick" sourceFile: "ColorEditorTemplate.template" } Type { typeNames: ["Text"] + module: "QtQuick" sourceFile: "TextEditorTemplate.template" separateSection: true } Type { typeNames: ["font", "QFont"] + module: "QtQuick" sourceFile: "FontEditorTemplate.template" separateSection: true } Type { typeNames: ["Rectangle"] + module: "QtQuick" sourceFile: "RectangleEditorTemplate.template" separateSection: true } Type { typeNames: ["Image"] + module: "QtQuick" sourceFile: "ImageEditorTemplate.template" separateSection: true } Type { - typeNames: ["TextureInput", "Texture"] + typeNames: ["TextureInput"] + module: "QtQuick3D" + sourceFile: "3DItemFilterComboBoxEditorTemplate.template" + needsTypeArg: true + } + + Type { + typeNames: ["Texture"] + module: "QtQuick3D" sourceFile: "3DItemFilterComboBoxEditorTemplate.template" needsTypeArg: true } Type { typeNames: ["vector2d"] + module: "QtQuick3D" sourceFile: "Vector2dEditorTemplate.template" } Type { typeNames: ["vector3d"] + module: "QtQuick3D" sourceFile: "Vector3dEditorTemplate.template" } Type { typeNames: ["vector4d"] + module: "QtQuick3D" sourceFile: "Vector4dEditorTemplate.template" } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AdvancedSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AdvancedSection.qml index e9be2285c52..f88defbff56 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AdvancedSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AdvancedSection.qml @@ -33,7 +33,7 @@ Section { PropertyLabel { text: qsTr("Smooth") - tooltip: qsTr("Uses smooth filtering when the image is scaled or transformed.") + tooltip: qsTr("Toggles if the smoothing is performed using linear interpolation method. Keeping it unchecked would follow non-smooth method using nearest neighbor. It is mostly applicable on image based items. ") blockedByTemplate: !backendValues.smooth.isAvailable } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/BorderImageSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/BorderImageSpecifics.qml index 0862b7e6d87..63f3906658d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/BorderImageSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/BorderImageSpecifics.qml @@ -216,24 +216,6 @@ Column { ExpandingSpacer {} } - PropertyLabel { - text: qsTr("Smooth") - tooltip: qsTr("Toggles if the image should be filtered smoothly when transformed.") - blockedByTemplate: !backendValues.smooth.isAvailable - } - - SecondColumnLayout { - CheckBox { - text: backendValues.smooth.valueToString - implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - backendValue: backendValues.smooth - enabled: backendValue.isAvailable - } - - ExpandingSpacer {} - } - PropertyLabel { text: qsTr("Cache") tooltip: qsTr("Toggles if the image is saved to the cache memory.") diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/FrameSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/FrameSpecifics.qml index 8b2728bcff8..3b490548b6b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/FrameSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/FrameSpecifics.qml @@ -20,7 +20,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/PaneSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/PaneSpecifics.qml index 899e1e6b56c..7f08a3d7fd5 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/PaneSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/PaneSpecifics.qml @@ -18,7 +18,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ScrollViewSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ScrollViewSpecifics.qml index 9dc06b7969b..1ca84213c6f 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ScrollViewSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ScrollViewSpecifics.qml @@ -72,7 +72,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/StackViewSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/StackViewSpecifics.qml index 427c838da51..eadcea60de0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/StackViewSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/StackViewSpecifics.qml @@ -16,7 +16,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/SwipeViewSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/SwipeViewSpecifics.qml index d5a300223db..8051dc8af15 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/SwipeViewSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/SwipeViewSpecifics.qml @@ -59,7 +59,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ToolBarSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ToolBarSpecifics.qml index 8c0388cb89c..17c05a62560 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ToolBarSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Controls/ToolBarSpecifics.qml @@ -43,7 +43,7 @@ Column { InsetSection {} FontSection { - caption: qsTr("Font Inheritance") + caption: qsTr("Font") expanded: false } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/NumberAnimationSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/NumberAnimationSpecifics.qml index 390795ff795..f370391b17e 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/NumberAnimationSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/NumberAnimationSpecifics.qml @@ -27,7 +27,7 @@ Column { implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth maximumValue: 9999999 - minimumValue: -1 + minimumValue: -9999999 backendValue: backendValues.from } @@ -44,7 +44,7 @@ Column { implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth maximumValue: 9999999 - minimumValue: -1 + minimumValue: -9999999 backendValue: backendValues.to } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml index e0927b9ba7b..a5dc28f6f86 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml @@ -19,7 +19,7 @@ Rectangle { anchors.fill: parent Controls.Label { - text: qsTr("Select a component in the 2D, Navigator, or Code view to see its properties.") + text: qsTr("Select a component to see its properties.") font.pixelSize: StudioTheme.Values.myFontSize * 1.5 color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExpressionTextField.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExpressionTextField.qml index 0888fdd1c7f..b968768043a 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExpressionTextField.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExpressionTextField.qml @@ -201,21 +201,22 @@ StudioControls.TextField { listView.model = list } - Keys.onSpacePressed: function(event) { - if (event.modifiers & Qt.ControlModifier) { - var list = autoComplete(textField.text, textField.cursorPosition, true, textField.completeOnlyTypes) - textField.prefix = textField.text.substring(0, textField.cursorPosition) - if (list.length && list[list.length - 1] === textField.prefix) - list.pop() + // Currently deactivated as it is causing a crash when calling autoComplete() + //Keys.onSpacePressed: function(event) { + // if (event.modifiers & Qt.ControlModifier) { + // var list = autoComplete(textField.text, textField.cursorPosition, true, textField.completeOnlyTypes) + // textField.prefix = textField.text.substring(0, textField.cursorPosition) + // if (list.length && list[list.length - 1] === textField.prefix) + // list.pop() - listView.model = list - textField.dotCompletion = false + // listView.model = list + // textField.dotCompletion = false - event.accepted = true; - } else { - event.accepted = false - } - } + // event.accepted = true + // } else { + // event.accepted = false + // } + //} Keys.onReturnPressed: function(event) { event.accepted = false diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImageSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImageSection.qml index 520a47f0ba3..d0ce0a109ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImageSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImageSection.qml @@ -224,23 +224,5 @@ Section { ExpandingSpacer {} } - - PropertyLabel { - text: qsTr("Smooth") - tooltip: qsTr("Uses smooth filtering when the image is scaled or transformed.") - blockedByTemplate: !backendValues.smooth.isAvailable - } - - SecondColumnLayout { - CheckBox { - text: backendValues.smooth.valueToString - implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - backendValue: backendValues.smooth - enabled: backendValue.isAvailable - } - - ExpandingSpacer {} - } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml index f33d0dd35bc..f17d2025aa2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml @@ -2,7 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -//import QtQuick.Controls as C +import QtQuick.Controls +import StudioControls as StudioControls import StudioTheme 1.0 as StudioTheme Flickable { @@ -26,8 +27,9 @@ Flickable { HoverHandler { id: hoverHandler } - ScrollBar.horizontal: ScrollBar { + ScrollBar.horizontal: StudioControls.TransientScrollBar { id: horizontalScrollBar + style: StudioTheme.Values.viewStyle parent: flickable x: 0 y: flickable.height - horizontalScrollBar.height @@ -40,8 +42,9 @@ Flickable { otherInUse: verticalScrollBar.inUse } - ScrollBar.vertical: ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalScrollBar + style: StudioTheme.Values.viewStyle parent: flickable x: flickable.width - verticalScrollBar.width y: 0 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index 8b06c20e5c4..485640bcf84 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -162,8 +162,6 @@ Row { id: delegateRoot width: comboBox.popup.width - comboBox.popup.leftPadding - comboBox.popup.rightPadding - - (comboBox.popupScrollBar.visible ? comboBox.popupScrollBar.contentItem.implicitWidth + 2 - : 0) // TODO Magic number height: StudioTheme.Values.height - 2 * StudioTheme.Values.border padding: 0 hoverEnabled: true @@ -176,32 +174,18 @@ Row { onClicked: comboBox.selectItem(delegateRoot.DelegateModel.itemsIndex) - indicator: Item { - id: itemDelegateIconArea - width: delegateRoot.height - height: delegateRoot.height - - Label { - id: itemDelegateIcon - text: StudioTheme.Constants.tickIcon - color: delegateRoot.highlighted ? StudioTheme.Values.themeTextSelectedTextColor - : StudioTheme.Values.themeTextColor - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.spinControlIconSizeMulti - visible: comboBox.currentIndex === delegateRoot.DelegateModel.itemsIndex ? true - : false - anchors.fill: parent - renderType: Text.NativeRendering - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - contentItem: Text { - leftPadding: itemDelegateIconArea.width + leftPadding: 8 text: name - color: delegateRoot.highlighted ? StudioTheme.Values.themeTextSelectedTextColor - : StudioTheme.Values.themeTextColor + color: { + if (!delegateRoot.enabled) + return comboBox.style.text.disabled + + if (comboBox.currentIndex === delegateRoot.DelegateModel.itemsIndex) + return comboBox.style.text.selectedText + + return comboBox.style.text.idle + } font: comboBox.font elide: Text.ElideRight verticalAlignment: Text.AlignVCenter @@ -212,8 +196,21 @@ Row { y: 0 width: delegateRoot.width height: delegateRoot.height - color: delegateRoot.highlighted ? StudioTheme.Values.themeInteraction - : "transparent" + color: { + if (!delegateRoot.enabled) + return "transparent" + + if (delegateRoot.hovered && comboBox.currentIndex === delegateRoot.DelegateModel.itemsIndex) + return comboBox.style.interactionHover + + if (comboBox.currentIndex === delegateRoot.DelegateModel.itemsIndex) + return comboBox.style.interaction + + if (delegateRoot.hovered) + return comboBox.style.background.hover + + return "transparent" + } } ToolTip { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 1e475a665c2..9c19a45e2ef 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -59,7 +59,6 @@ PropertyEditorPane 2.0 PropertyEditorPane.qml PropertyLabel 2.0 PropertyLabel.qml PaddingSection 2.0 PaddingSection.qml RoundedPanel 2.0 RoundedPanel.qml -ScrollBar 2.0 ScrollBar.qml ScrollView 2.0 ScrollView.qml SecondColumnLayout 2.0 SecondColumnLayout.qml Section 2.0 Section.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml index abbb32744b5..0c68d5195bd 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml @@ -130,14 +130,13 @@ T.ComboBox { id: itemDelegate width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding - - (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth - + 2 : 0) // TODO Magic number height: control.style.controlSize.height - 2 * control.style.borderWidth padding: 0 enabled: model.enabled === undefined ? true : model.enabled contentItem: Text { - leftPadding: itemDelegateIconArea.width + leftPadding: 8 + rightPadding: verticalScrollBar.style.scrollBarThicknessHover text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) @@ -146,34 +145,16 @@ T.ComboBox { if (!itemDelegate.enabled) return control.style.text.disabled - return itemDelegate.highlighted ? control.style.text.selectedText - : control.style.text.idle + if (control.currentIndex === index) + return control.style.text.selectedText + + return control.style.text.idle } font: control.font elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } - Item { - id: itemDelegateIconArea - width: itemDelegate.height - height: itemDelegate.height - - T.Label { - id: itemDelegateIcon - text: StudioTheme.Constants.tickIcon - color: itemDelegate.highlighted ? control.style.text.selectedText - : control.style.text.idle - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: control.style.smallIconFontSize - visible: control.currentIndex === index - anchors.fill: parent - renderType: Text.NativeRendering - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - highlighted: control.highlightedIndex === index background: Rectangle { @@ -182,7 +163,21 @@ T.ComboBox { y: 0 width: itemDelegate.width height: itemDelegate.height - color: itemDelegate.highlighted ? control.style.interaction : "transparent" + color: { + if (!itemDelegate.enabled) + return "transparent" + + if (itemDelegate.hovered && control.currentIndex === index) + return control.style.interactionHover + + if (control.currentIndex === index) + return control.style.interaction + + if (itemDelegate.hovered) + return control.style.background.hover + + return "transparent" + } } } @@ -211,9 +206,19 @@ T.ComboBox { model: control.popup.visible ? control.delegateModel : null currentIndex: control.highlightedIndex boundsBehavior: Flickable.StopAtBounds - ScrollBar.vertical: ScrollBar { - id: comboBoxPopupScrollBar - visible: listView.height < listView.contentHeight + + HoverHandler { id: hoverHandler } + + ScrollBar.vertical: TransientScrollBar { + id: verticalScrollBar + parent: listView + x: listView.width - verticalScrollBar.width + y: 0 + height: listView.availableHeight + orientation: Qt.Vertical + + show: (hoverHandler.hovered || verticalScrollBar.inUse) + && verticalScrollBar.isNeeded } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml index 5593f46db00..8531d791348 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml @@ -277,32 +277,12 @@ Item { onClicked: control.selectItem(delegateRoot.DelegateModel.itemsIndex) - indicator: Item { - id: itemDelegateIconArea - width: delegateRoot.height - height: delegateRoot.height - - T.Label { - id: itemDelegateIcon - text: StudioTheme.Constants.tickIcon - color: delegateRoot.highlighted ? control.style.text.selectedText - : control.style.text.idle - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: control.style.smallIconFontSize - visible: control.currentIndex === delegateRoot.DelegateModel.itemsIndex ? true - : false - anchors.fill: parent - renderType: Text.NativeRendering - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - contentItem: Text { - leftPadding: itemDelegateIconArea.width + leftPadding: 8 text: name - color: delegateRoot.highlighted ? control.style.text.selectedText - : control.style.text.idle + color: control.currentIndex === delegateRoot.DelegateModel.itemsIndex + ? control.style.text.selectedText + : control.style.text.idle font: textInput.font elide: Text.ElideRight verticalAlignment: Text.AlignVCenter @@ -313,7 +293,22 @@ Item { y: 0 width: delegateRoot.width height: delegateRoot.height - color: delegateRoot.highlighted ? control.style.interaction : "transparent" + color: { + if (!itemDelegate.enabled) + return "transparent" + + if (itemDelegate.hovered + && control.currentIndex === delegateRoot.DelegateModel.itemsIndex) + return control.style.interactionHover + + if (control.currentIndex === delegateRoot.DelegateModel.itemsIndexx) + return control.style.interaction + + if (itemDelegate.hovered) + return control.style.background.hover + + return "transparent" + } } } @@ -669,9 +664,9 @@ Item { T.Popup { id: popup - x: textInput.x + control.style.borderWidth + x: textInput.x y: textInput.height - width: textInput.width - (control.style.borderWidth * 2) + width: textInput.width height: Math.min(popup.contentItem.implicitHeight + popup.topPadding + popup.bottomPadding, control.Window.height - popup.topMargin - popup.bottomMargin, control.style.maxComboBoxPopupHeight) @@ -694,9 +689,18 @@ Item { return null } - ScrollBar.vertical: ScrollBar { + HoverHandler { id: hoverHandler } + + ScrollBar.vertical: TransientScrollBar { id: popupScrollBar - visible: listView.height < listView.contentHeight + parent: listView + x: listView.width - verticalScrollBar.width + y: 0 + height: listView.availableHeight + orientation: Qt.Vertical + + show: (hoverHandler.hovered || popupScrollBar.inUse) + && popupScrollBar.isNeeded } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml index 61483feadfb..ea80abe7ea3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml @@ -138,9 +138,19 @@ T.ComboBox { currentIndex: control.highlightedIndex boundsBehavior: Flickable.StopAtBounds - ScrollBar.vertical: ScrollBar { - id: comboBoxPopupScrollBar - visible: listView.height < listView.contentHeight + HoverHandler { id: hoverHandler } + + ScrollBar.vertical: TransientScrollBar { + id: verticalScrollBar + style: control.style + parent: listView + x: listView.width - verticalScrollBar.width + y: 0 + height: listView.availableHeight + orientation: Qt.Vertical + + show: (hoverHandler.hovered || verticalScrollBar.inUse) + && verticalScrollBar.isNeeded } delegate: ItemDelegate { @@ -155,9 +165,11 @@ T.ComboBox { width: control.width height: control.style.controlSize.height padding: 0 + enabled: model.enabled === undefined ? true : model.enabled contentItem: Text { - leftPadding: itemDelegateIconArea.width + leftPadding: 8 + rightPadding: verticalScrollBar.style.scrollBarThicknessHover text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) @@ -166,41 +178,37 @@ T.ComboBox { if (!itemDelegate.enabled) return control.style.text.disabled - return itemDelegate.hovered ? control.style.text.selectedText - : control.style.text.idle + if (control.currentIndex === index) + return control.style.text.selectedText + + return control.style.text.idle } font: control.font elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } - Item { - id: itemDelegateIconArea - width: itemDelegate.height - height: itemDelegate.height - - T.Label { - id: itemDelegateIcon - text: StudioTheme.Constants.tickIcon - color: itemDelegate.hovered ? control.style.text.selectedText - : control.style.text.idle - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: control.style.smallIconFontSize - visible: control.currentIndex === index - anchors.fill: parent - renderType: Text.NativeRendering - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - background: Rectangle { id: itemDelegateBackground x: control.style.borderWidth y: 0 width: itemDelegate.width - 2 * control.style.borderWidth height: itemDelegate.height - color: itemDelegate.hovered ? control.style.interaction : "transparent" + color: { + if (!itemDelegate.enabled) + return "transparent" + + if (itemDelegate.hovered && control.currentIndex === index) + return control.style.interactionHover + + if (control.currentIndex === index) + return control.style.interaction + + if (itemDelegate.hovered) + return control.style.background.hover + + return "transparent" + } } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TransientScrollBar.qml similarity index 94% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollBar.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TransientScrollBar.qml index 43456d46ddd..141a981f749 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollBar.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TransientScrollBar.qml @@ -14,7 +14,8 @@ T.ScrollBar { property bool otherInUse: false property bool isNeeded: control.size < 1.0 property bool inUse: control.hovered || control.pressed - property int thickness: control.inUse || control.otherInUse ? 10 : 8 + property int thickness: control.inUse || control.otherInUse ? control.style.scrollBarThicknessHover + : control.style.scrollBarThickness property bool scrollBarVisible: parent.childrenRect.height > parent.height diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml deleted file mode 100644 index 752f2bc6e4e..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml +++ /dev/null @@ -1,38 +0,0 @@ -// 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 StudioTheme 1.0 as StudioTheme - -ScrollBar { - id: control - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding) - - property bool scrollBarVisible: parent.contentHeight > control.height - - minimumSize: control.width / control.height - orientation: Qt.Vertical - policy: control.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - - height: parent.availableHeight - - (parent.bothVisible ? parent.horizontalThickness : 0) - padding: control.active ? control.style.scrollBarActivePadding - : control.style.scrollBarInactivePadding - - background: Rectangle { - implicitWidth: control.style.scrollBarThickness - implicitHeight: control.style.scrollBarThickness - color: control.style.scrollBar.track - } - - contentItem: Rectangle { - implicitWidth: control.style.scrollBarThickness - 2 * control.padding - implicitHeight: control.style.scrollBarThickness - 2 * control.padding - color: control.style.scrollBar.handle - } -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir index d6071b6ff2d..5ce433812c2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir @@ -47,6 +47,6 @@ TabButton 1.0 TabButton.qml TextArea 1.0 TextArea.qml TextField 1.0 TextField.qml ToolTip 1.0 ToolTip.qml +TransientScrollBar 1.0 TransientScrollBar.qml TranslationIndicator 1.0 TranslationIndicator.qml -VerticalScrollBar 1.0 VerticalScrollBar.qml TopLevelComboBox 1.0 TopLevelComboBox.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ConnectionPopupControlStyle.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ConnectionPopupControlStyle.qml index 9a4d1194c67..828e52e93ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ConnectionPopupControlStyle.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ConnectionPopupControlStyle.qml @@ -4,13 +4,15 @@ import QtQuick ControlStyle { - radius: Values.smallRadius baseIconFontSize: Values.baseFont controlSize: Qt.size(Values.viewBarComboWidth, Values.viewBarComboHeight) smallIconFontSize: Values.baseFont + scrollBarThickness: 4 + scrollBarThicknessHover: 6 + background: ControlStyle.BackgroundColors { idle: Values.themePopoutControlBackground_idle hover: Values.themePopoutControlBackground_hover @@ -36,4 +38,10 @@ ControlStyle { interaction: Values.themeInteraction disabled: Values.themePopoutControlBorder_disabled } + + scrollBar: ControlStyle.ScrollBarColors { + track: Values.themeScrollBarTrack + handle: Values.themeScrollBarHandle_idle + handleHover: Values.themeScrollBarHandle + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ControlStyle.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ControlStyle.qml index 460ff6db3f3..b0e6e3e798e 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ControlStyle.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ControlStyle.qml @@ -67,7 +67,8 @@ QtObject { property real sectionHeadHeight: Values.sectionHeadHeight property real sectionHeadSpacerHeight: Values.sectionHeadSpacerHeight - property real scrollBarThickness: Values.scrollBarThickness + property real scrollBarThickness: 4//Values.scrollBarThickness + property real scrollBarThicknessHover: 6//Values.scrollBarThicknessHover property real scrollBarActivePadding: Values.scrollBarActivePadding property real scrollBarInactivePadding: Values.scrollBarInactivePadding @@ -76,6 +77,7 @@ QtObject { // Special colors property color interaction: Values.themeInteraction property color interactionHover: Values.themeInteractionHover + property color interactionGlobalHover: "#ffB0E1FC" // TODO needs to removed in the future property color thumbnailLabelBackground: Values.themeThumbnailLabelBackground diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 0feb5063441..168f3c37908 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -23,330 +23,348 @@ QtObject { readonly property string addTable: "\u0028" readonly property string add_medium: "\u0029" readonly property string add_small: "\u002A" - readonly property string adsClose: "\u002B" - readonly property string adsDetach: "\u002C" - readonly property string adsDropDown: "\u002D" - readonly property string alias: "\u002E" - readonly property string aliasAnimated: "\u002F" - readonly property string alignBottom: "\u0030" - readonly property string alignCenterHorizontal: "\u0031" - readonly property string alignCenterVertical: "\u0032" - readonly property string alignLeft: "\u0033" - readonly property string alignRight: "\u0034" - readonly property string alignTo: "\u0035" - readonly property string alignToCam_medium: "\u0036" - readonly property string alignToCamera_small: "\u0037" - readonly property string alignToObject_small: "\u0038" - readonly property string alignToView_medium: "\u0039" - readonly property string alignTop: "\u003A" - readonly property string anchorBaseline: "\u003B" - readonly property string anchorBottom: "\u003C" - readonly property string anchorFill: "\u003D" - readonly property string anchorLeft: "\u003E" - readonly property string anchorRight: "\u003F" - readonly property string anchorTop: "\u0040" - readonly property string anchors_small: "\u0041" - readonly property string animatedProperty: "\u0042" - readonly property string annotationBubble: "\u0043" - readonly property string annotationDecal: "\u0044" - readonly property string annotations_large: "\u0045" - readonly property string annotations_small: "\u0046" - readonly property string applyMaterialToSelected: "\u0047" - readonly property string apply_medium: "\u0048" - readonly property string apply_small: "\u0049" - readonly property string arrange_small: "\u004A" - readonly property string arrow_small: "\u004B" - readonly property string assign: "\u004C" - readonly property string attach_medium: "\u004D" - readonly property string back_medium: "\u004E" - readonly property string backspace_small: "\u004F" - readonly property string bevelAll: "\u0050" - readonly property string bevelCorner: "\u0051" - readonly property string bezier_medium: "\u0052" - readonly property string binding_medium: "\u0053" - readonly property string bounds_small: "\u0054" - readonly property string branch_medium: "\u0055" - readonly property string camera_small: "\u0056" - readonly property string centerHorizontal: "\u0057" - readonly property string centerVertical: "\u0058" - readonly property string cleanLogs_medium: "\u0059" - readonly property string closeCross: "\u005A" - readonly property string closeFile_large: "\u005B" - readonly property string closeLink: "\u005C" - readonly property string close_small: "\u005D" - readonly property string code: "\u005E" - readonly property string colorPopupClose: "\u005F" - readonly property string colorSelection_medium: "\u0060" - readonly property string columnsAndRows: "\u0061" - readonly property string cone_medium: "\u0062" - readonly property string cone_small: "\u0063" - readonly property string connection_small: "\u0064" - readonly property string connections_medium: "\u0065" - readonly property string copyLink: "\u0066" - readonly property string copyStyle: "\u0067" - readonly property string copy_small: "\u0068" - readonly property string cornerA: "\u0069" - readonly property string cornerB: "\u006A" - readonly property string cornersAll: "\u006B" - readonly property string createComponent_large: "\u006C" - readonly property string createComponent_small: "\u006D" - readonly property string create_medium: "\u006E" - readonly property string create_small: "\u006F" - readonly property string cube_medium: "\u0070" - readonly property string cube_small: "\u0071" - readonly property string curveDesigner: "\u0072" - readonly property string curveDesigner_medium: "\u0073" - readonly property string curveEditor: "\u0074" - readonly property string customMaterialEditor: "\u0075" - readonly property string cylinder_medium: "\u0076" - readonly property string cylinder_small: "\u0077" - readonly property string decisionNode: "\u0078" - readonly property string deleteColumn: "\u0079" - readonly property string deleteMaterial: "\u007A" - readonly property string deleteRow: "\u007B" - readonly property string deleteTable: "\u007C" - readonly property string delete_medium: "\u007D" - readonly property string delete_small: "\u007E" - readonly property string designMode_large: "\u007F" - readonly property string detach: "\u0080" - readonly property string directionalLight_small: "\u0081" - readonly property string distributeBottom: "\u0082" - readonly property string distributeCenterHorizontal: "\u0083" - readonly property string distributeCenterVertical: "\u0084" - readonly property string distributeLeft: "\u0085" - readonly property string distributeOriginBottomRight: "\u0086" - readonly property string distributeOriginCenter: "\u0087" - readonly property string distributeOriginNone: "\u0088" - readonly property string distributeOriginTopLeft: "\u0089" - readonly property string distributeRight: "\u008A" - readonly property string distributeSpacingHorizontal: "\u008B" - readonly property string distributeSpacingVertical: "\u008C" - readonly property string distributeTop: "\u008D" - readonly property string download: "\u008E" - readonly property string downloadUnavailable: "\u008F" - readonly property string downloadUpdate: "\u0090" - readonly property string downloaded: "\u0091" - readonly property string dragmarks: "\u0092" - readonly property string duplicate_small: "\u0093" - readonly property string edit: "\u0094" - readonly property string editComponent_large: "\u0095" - readonly property string editComponent_small: "\u0096" - readonly property string editLightOff_medium: "\u0097" - readonly property string editLightOn_medium: "\u0098" - readonly property string edit_medium: "\u0099" - readonly property string edit_small: "\u009A" - readonly property string effects: "\u009B" - readonly property string events_small: "\u009D" - readonly property string export_medium: "\u009E" - readonly property string eyeDropper: "\u009F" - readonly property string favorite: "\u00A0" - readonly property string fitAll_medium: "\u00A1" - readonly property string fitSelected_small: "\u00A2" - readonly property string fitSelection_medium: "\u00A3" - readonly property string fitToView_medium: "\u00A4" - readonly property string flowAction: "\u00A5" - readonly property string flowTransition: "\u00A6" - readonly property string fontStyleBold: "\u00A7" - readonly property string fontStyleItalic: "\u00A8" - readonly property string fontStyleStrikethrough: "\u00A9" - readonly property string fontStyleUnderline: "\u00AA" - readonly property string forward_medium: "\u00AB" - readonly property string globalOrient_medium: "\u00AC" - readonly property string gradient: "\u00AE" - readonly property string gridView: "\u00AF" - readonly property string grid_medium: "\u00B0" - readonly property string group_small: "\u00B1" - readonly property string help: "\u00B2" - readonly property string home_large: "\u00B3" - readonly property string idAliasOff: "\u00B4" - readonly property string idAliasOn: "\u00B5" - readonly property string import_medium: "\u00B6" - readonly property string imported: "\u00B7" - readonly property string importedModels_small: "\u00B8" - readonly property string infinity: "\u00B9" - readonly property string invisible_medium: "\u00BA" - readonly property string invisible_small: "\u00BB" - readonly property string keyframe: "\u00BC" - readonly property string languageList_medium: "\u00BD" - readonly property string layouts_small: "\u00BE" - readonly property string lights_small: "\u00BF" - readonly property string linear_medium: "\u00C0" - readonly property string linkTriangle: "\u00C1" - readonly property string linked: "\u00C2" - readonly property string listView: "\u00C3" - readonly property string list_medium: "\u00C4" - readonly property string localOrient_medium: "\u00C5" - readonly property string lockOff: "\u00C6" - readonly property string lockOn: "\u00C7" - readonly property string loopPlayback_medium: "\u00C8" - readonly property string materialBrowser_medium: "\u00C9" - readonly property string materialPreviewEnvironment: "\u00CA" - readonly property string materialPreviewModel: "\u00CB" - readonly property string material_medium: "\u00CC" - readonly property string maxBar_small: "\u00CD" - readonly property string mergeCells: "\u00CE" - readonly property string merge_small: "\u00CF" - readonly property string minus: "\u00D0" - readonly property string mirror: "\u00D1" - readonly property string more_medium: "\u00D2" - readonly property string mouseArea_small: "\u00D3" - readonly property string moveDown_medium: "\u00D4" - readonly property string moveInwards_medium: "\u00D5" - readonly property string moveUp_medium: "\u00D6" - readonly property string moveUpwards_medium: "\u00D7" - readonly property string move_medium: "\u00D8" - readonly property string newMaterial: "\u00D9" - readonly property string nextFile_large: "\u00DA" - readonly property string normalBar_small: "\u00DB" - readonly property string openLink: "\u00DC" - readonly property string openMaterialBrowser: "\u00DD" - readonly property string orientation: "\u00DE" - readonly property string orthCam_medium: "\u00DF" - readonly property string orthCam_small: "\u00E0" - readonly property string paddingEdge: "\u00E1" - readonly property string paddingFrame: "\u00E2" - readonly property string particleAnimation_medium: "\u00E3" - readonly property string pasteStyle: "\u00E4" - readonly property string paste_small: "\u00E5" - readonly property string pause: "\u00E6" - readonly property string perspectiveCam_medium: "\u00E7" - readonly property string perspectiveCam_small: "\u00E8" - readonly property string pin: "\u00E9" - readonly property string plane_medium: "\u00EA" - readonly property string plane_small: "\u00EB" - readonly property string play: "\u00EC" - readonly property string playFill_medium: "\u00ED" - readonly property string playOutline_medium: "\u00EE" - readonly property string plus: "\u00EF" - readonly property string pointLight_small: "\u00F0" - readonly property string positioners_small: "\u00F1" - readonly property string previewEnv_medium: "\u00F2" - readonly property string previousFile_large: "\u00F3" - readonly property string promote: "\u00F4" - readonly property string properties_medium: "\u00F5" - readonly property string readOnly: "\u00F6" - readonly property string recordFill_medium: "\u00F7" - readonly property string recordOutline_medium: "\u00F8" - readonly property string redo: "\u00F9" - readonly property string reload_medium: "\u00FA" - readonly property string remove_medium: "\u00FB" - readonly property string remove_small: "\u00FC" - readonly property string rename_small: "\u00FD" - readonly property string replace_small: "\u00FE" - readonly property string resetView_small: "\u00FF" - readonly property string restartParticles_medium: "\u0100" - readonly property string reverseOrder_medium: "\u0101" - readonly property string roatate_medium: "\u0102" - readonly property string rotationFill: "\u0103" - readonly property string rotationOutline: "\u0104" - readonly property string runProjFill_large: "\u0105" - readonly property string runProjOutline_large: "\u0106" - readonly property string s_anchors: "\u0107" - readonly property string s_annotations: "\u0108" - readonly property string s_arrange: "\u0109" - readonly property string s_boundingBox: "\u010A" - readonly property string s_component: "\u010B" - readonly property string s_connections: "\u010C" - readonly property string s_edit: "\u010D" - readonly property string s_enterComponent: "\u010E" - readonly property string s_eventList: "\u010F" - readonly property string s_group: "\u0110" - readonly property string s_layouts: "\u0111" - readonly property string s_merging: "\u0112" - readonly property string s_mouseArea: "\u0113" - readonly property string s_positioners: "\u0114" - readonly property string s_selection: "\u0115" - readonly property string s_snapping: "\u0116" - readonly property string s_timeline: "\u0117" - readonly property string s_visibility: "\u0118" - readonly property string saveLogs_medium: "\u0119" - readonly property string scale_medium: "\u011A" - readonly property string search: "\u011B" - readonly property string search_small: "\u011C" - readonly property string sectionToggle: "\u011D" - readonly property string selectFill_medium: "\u011E" - readonly property string selectOutline_medium: "\u011F" - readonly property string selectParent_small: "\u0120" - readonly property string selection_small: "\u0121" - readonly property string settings_medium: "\u0122" - readonly property string signal_small: "\u0123" - readonly property string snapping_conf_medium: "\u0124" - readonly property string snapping_medium: "\u0125" - readonly property string snapping_small: "\u0126" - readonly property string sphere_medium: "\u0127" - readonly property string sphere_small: "\u0128" - readonly property string splitColumns: "\u0129" - readonly property string splitRows: "\u012A" - readonly property string spotLight_small: "\u012B" - readonly property string stackedContainer_small: "\u012C" - readonly property string startNode: "\u012D" - readonly property string step_medium: "\u012E" - readonly property string stop_medium: "\u012F" - readonly property string testIcon: "\u0130" - readonly property string textAlignBottom: "\u0131" - readonly property string textAlignCenter: "\u0132" - readonly property string textAlignJustified: "\u0133" - readonly property string textAlignLeft: "\u0134" - readonly property string textAlignMiddle: "\u0135" - readonly property string textAlignRight: "\u0136" - readonly property string textAlignTop: "\u0137" - readonly property string textBulletList: "\u0138" - readonly property string textFullJustification: "\u0139" - readonly property string textNumberedList: "\u013A" - readonly property string textures_medium: "\u013B" - readonly property string tickIcon: "\u013C" - readonly property string tickMark_small: "\u013D" - readonly property string timeline_small: "\u013E" - readonly property string toEndFrame_medium: "\u013F" - readonly property string toNextFrame_medium: "\u0140" - readonly property string toPrevFrame_medium: "\u0141" - readonly property string toStartFrame_medium: "\u0142" - readonly property string topToolbar_annotations: "\u0143" - readonly property string topToolbar_closeFile: "\u0144" - readonly property string topToolbar_designMode: "\u0145" - readonly property string topToolbar_enterComponent: "\u0146" - readonly property string topToolbar_home: "\u0147" - readonly property string topToolbar_makeComponent: "\u0148" - readonly property string topToolbar_navFile: "\u0149" - readonly property string topToolbar_runProject: "\u014A" - readonly property string translationCreateFiles: "\u014B" - readonly property string translationCreateReport: "\u014C" - readonly property string translationExport: "\u014D" - readonly property string translationImport: "\u014E" - readonly property string translationSelectLanguages: "\u014F" - readonly property string translationTest: "\u0150" - readonly property string transparent: "\u0151" - readonly property string triState: "\u0152" - readonly property string triangleArcA: "\u0153" - readonly property string triangleArcB: "\u0154" - readonly property string triangleCornerA: "\u0155" - readonly property string triangleCornerB: "\u0156" - readonly property string unLinked: "\u0157" - readonly property string undo: "\u0158" - readonly property string unify_medium: "\u0159" - readonly property string unpin: "\u015A" - readonly property string upDownIcon: "\u015B" - readonly property string upDownSquare2: "\u015C" - readonly property string updateAvailable_medium: "\u015D" - readonly property string updateContent_medium: "\u015E" - readonly property string visibilityOff: "\u015F" - readonly property string visibilityOn: "\u0160" - readonly property string visible_medium: "\u0161" - readonly property string visible_small: "\u0162" - readonly property string wildcard: "\u0163" - readonly property string wizardsAutomotive: "\u0164" - readonly property string wizardsDesktop: "\u0165" - readonly property string wizardsGeneric: "\u0166" - readonly property string wizardsMcuEmpty: "\u0167" - readonly property string wizardsMcuGraph: "\u0168" - readonly property string wizardsMobile: "\u0169" - readonly property string wizardsUnknown: "\u016A" - readonly property string zoomAll: "\u016B" - readonly property string zoomIn: "\u016C" - readonly property string zoomIn_medium: "\u016D" - readonly property string zoomOut: "\u016E" - readonly property string zoomOut_medium: "\u016F" - readonly property string zoomSelection: "\u0170" + readonly property string addcolumnleft_medium: "\u002B" + readonly property string addcolumnright_medium: "\u002C" + readonly property string addrowabove_medium: "\u002D" + readonly property string addrowbelow_medium: "\u002E" + readonly property string adsClose: "\u002F" + readonly property string adsDetach: "\u0030" + readonly property string adsDropDown: "\u0031" + readonly property string alias: "\u0032" + readonly property string aliasAnimated: "\u0033" + readonly property string alignBottom: "\u0034" + readonly property string alignCenterHorizontal: "\u0035" + readonly property string alignCenterVertical: "\u0036" + readonly property string alignLeft: "\u0037" + readonly property string alignRight: "\u0038" + readonly property string alignTo: "\u0039" + readonly property string alignToCam_medium: "\u003A" + readonly property string alignToCamera_small: "\u003B" + readonly property string alignToObject_small: "\u003C" + readonly property string alignToView_medium: "\u003D" + readonly property string alignTop: "\u003E" + readonly property string anchorBaseline: "\u003F" + readonly property string anchorBottom: "\u0040" + readonly property string anchorFill: "\u0041" + readonly property string anchorLeft: "\u0042" + readonly property string anchorRight: "\u0043" + readonly property string anchorTop: "\u0044" + readonly property string anchors_small: "\u0045" + readonly property string animatedProperty: "\u0046" + readonly property string annotationBubble: "\u0047" + readonly property string annotationDecal: "\u0048" + readonly property string annotations_large: "\u0049" + readonly property string annotations_small: "\u004A" + readonly property string applyMaterialToSelected: "\u004B" + readonly property string apply_medium: "\u004C" + readonly property string apply_small: "\u004D" + readonly property string arrange_small: "\u004E" + readonly property string arrow_small: "\u004F" + readonly property string assign: "\u0050" + readonly property string attach_medium: "\u0051" + readonly property string back_medium: "\u0052" + readonly property string backspace_small: "\u0053" + readonly property string bevelAll: "\u0054" + readonly property string bevelCorner: "\u0055" + readonly property string bezier_medium: "\u0056" + readonly property string binding_medium: "\u0057" + readonly property string bounds_small: "\u0058" + readonly property string branch_medium: "\u0059" + readonly property string camera_small: "\u005A" + readonly property string centerHorizontal: "\u005B" + readonly property string centerVertical: "\u005C" + readonly property string cleanLogs_medium: "\u005D" + readonly property string closeCross: "\u005E" + readonly property string closeFile_large: "\u005F" + readonly property string closeLink: "\u0060" + readonly property string close_small: "\u0061" + readonly property string code: "\u0062" + readonly property string codeEditor_medium: "\u0063" + readonly property string codeview_medium: "\u0064" + readonly property string colorPopupClose: "\u0065" + readonly property string colorSelection_medium: "\u0066" + readonly property string columnsAndRows: "\u0067" + readonly property string cone_medium: "\u0068" + readonly property string cone_small: "\u0069" + readonly property string connection_small: "\u006A" + readonly property string connections_medium: "\u006B" + readonly property string copyLink: "\u006C" + readonly property string copyStyle: "\u006D" + readonly property string copy_small: "\u006E" + readonly property string cornerA: "\u006F" + readonly property string cornerB: "\u0070" + readonly property string cornersAll: "\u0071" + readonly property string createComponent_large: "\u0072" + readonly property string createComponent_small: "\u0073" + readonly property string create_medium: "\u0074" + readonly property string create_small: "\u0075" + readonly property string cube_medium: "\u0076" + readonly property string cube_small: "\u0077" + readonly property string curveDesigner: "\u0078" + readonly property string curveDesigner_medium: "\u0079" + readonly property string curveEditor: "\u007A" + readonly property string customMaterialEditor: "\u007B" + readonly property string cylinder_medium: "\u007C" + readonly property string cylinder_small: "\u007D" + readonly property string decisionNode: "\u007E" + readonly property string deleteColumn: "\u007F" + readonly property string deleteMaterial: "\u0080" + readonly property string deleteRow: "\u0081" + readonly property string deleteTable: "\u0082" + readonly property string delete_medium: "\u0083" + readonly property string delete_small: "\u0084" + readonly property string deletecolumn_medium: "\u0085" + readonly property string deleterow_medium: "\u0086" + readonly property string designMode_large: "\u0087" + readonly property string detach: "\u0088" + readonly property string directionalLight_small: "\u0089" + readonly property string distributeBottom: "\u008A" + readonly property string distributeCenterHorizontal: "\u008B" + readonly property string distributeCenterVertical: "\u008C" + readonly property string distributeLeft: "\u008D" + readonly property string distributeOriginBottomRight: "\u008E" + readonly property string distributeOriginCenter: "\u008F" + readonly property string distributeOriginNone: "\u0090" + readonly property string distributeOriginTopLeft: "\u0091" + readonly property string distributeRight: "\u0092" + readonly property string distributeSpacingHorizontal: "\u0093" + readonly property string distributeSpacingVertical: "\u0094" + readonly property string distributeTop: "\u0095" + readonly property string download: "\u0096" + readonly property string downloadUnavailable: "\u0097" + readonly property string downloadUpdate: "\u0098" + readonly property string downloadcsv_large: "\u0099" + readonly property string downloadcsv_medium: "\u009A" + readonly property string downloaded: "\u009B" + readonly property string downloadjson_large: "\u009D" + readonly property string downloadjson_medium: "\u009E" + readonly property string dragmarks: "\u009F" + readonly property string duplicate_small: "\u00A0" + readonly property string edit: "\u00A1" + readonly property string editComponent_large: "\u00A2" + readonly property string editComponent_small: "\u00A3" + readonly property string editLightOff_medium: "\u00A4" + readonly property string editLightOn_medium: "\u00A5" + readonly property string edit_medium: "\u00A6" + readonly property string edit_small: "\u00A7" + readonly property string effects: "\u00A8" + readonly property string events_small: "\u00A9" + readonly property string export_medium: "\u00AA" + readonly property string eyeDropper: "\u00AB" + readonly property string favorite: "\u00AC" + readonly property string fitAll_medium: "\u00AE" + readonly property string fitSelected_small: "\u00AF" + readonly property string fitSelection_medium: "\u00B0" + readonly property string fitToView_medium: "\u00B1" + readonly property string flowAction: "\u00B2" + readonly property string flowTransition: "\u00B3" + readonly property string fontStyleBold: "\u00B4" + readonly property string fontStyleItalic: "\u00B5" + readonly property string fontStyleStrikethrough: "\u00B6" + readonly property string fontStyleUnderline: "\u00B7" + readonly property string forward_medium: "\u00B8" + readonly property string globalOrient_medium: "\u00B9" + readonly property string gradient: "\u00BA" + readonly property string gridView: "\u00BB" + readonly property string grid_medium: "\u00BC" + readonly property string group_small: "\u00BD" + readonly property string help: "\u00BE" + readonly property string home_large: "\u00BF" + readonly property string idAliasOff: "\u00C0" + readonly property string idAliasOn: "\u00C1" + readonly property string import_medium: "\u00C2" + readonly property string imported: "\u00C3" + readonly property string importedModels_small: "\u00C4" + readonly property string infinity: "\u00C5" + readonly property string invisible_medium: "\u00C6" + readonly property string invisible_small: "\u00C7" + readonly property string keyframe: "\u00C8" + readonly property string languageList_medium: "\u00C9" + readonly property string layouts_small: "\u00CA" + readonly property string lights_small: "\u00CB" + readonly property string linear_medium: "\u00CC" + readonly property string linkTriangle: "\u00CD" + readonly property string linked: "\u00CE" + readonly property string listView: "\u00CF" + readonly property string list_medium: "\u00D0" + readonly property string localOrient_medium: "\u00D1" + readonly property string lockOff: "\u00D2" + readonly property string lockOn: "\u00D3" + readonly property string loopPlayback_medium: "\u00D4" + readonly property string materialBrowser_medium: "\u00D5" + readonly property string materialPreviewEnvironment: "\u00D6" + readonly property string materialPreviewModel: "\u00D7" + readonly property string material_medium: "\u00D8" + readonly property string maxBar_small: "\u00D9" + readonly property string mergeCells: "\u00DA" + readonly property string merge_small: "\u00DB" + readonly property string minus: "\u00DC" + readonly property string mirror: "\u00DD" + readonly property string more_medium: "\u00DE" + readonly property string mouseArea_small: "\u00DF" + readonly property string moveDown_medium: "\u00E0" + readonly property string moveInwards_medium: "\u00E1" + readonly property string moveUp_medium: "\u00E2" + readonly property string moveUpwards_medium: "\u00E3" + readonly property string move_medium: "\u00E4" + readonly property string newMaterial: "\u00E5" + readonly property string nextFile_large: "\u00E6" + readonly property string normalBar_small: "\u00E7" + readonly property string openLink: "\u00E8" + readonly property string openMaterialBrowser: "\u00E9" + readonly property string orientation: "\u00EA" + readonly property string orthCam_medium: "\u00EB" + readonly property string orthCam_small: "\u00EC" + readonly property string paddingEdge: "\u00ED" + readonly property string paddingFrame: "\u00EE" + readonly property string particleAnimation_medium: "\u00EF" + readonly property string pasteStyle: "\u00F0" + readonly property string paste_small: "\u00F1" + readonly property string pause: "\u00F2" + readonly property string perspectiveCam_medium: "\u00F3" + readonly property string perspectiveCam_small: "\u00F4" + readonly property string pin: "\u00F5" + readonly property string plane_medium: "\u00F6" + readonly property string plane_small: "\u00F7" + readonly property string play: "\u00F8" + readonly property string playFill_medium: "\u00F9" + readonly property string playOutline_medium: "\u00FA" + readonly property string plus: "\u00FB" + readonly property string pointLight_small: "\u00FC" + readonly property string positioners_small: "\u00FD" + readonly property string previewEnv_medium: "\u00FE" + readonly property string previousFile_large: "\u00FF" + readonly property string promote: "\u0100" + readonly property string properties_medium: "\u0101" + readonly property string readOnly: "\u0102" + readonly property string recordFill_medium: "\u0103" + readonly property string recordOutline_medium: "\u0104" + readonly property string redo: "\u0105" + readonly property string reload_medium: "\u0106" + readonly property string remove_medium: "\u0107" + readonly property string remove_small: "\u0108" + readonly property string rename_small: "\u0109" + readonly property string replace_small: "\u010A" + readonly property string resetView_small: "\u010B" + readonly property string restartParticles_medium: "\u010C" + readonly property string reverseOrder_medium: "\u010D" + readonly property string roatate_medium: "\u010E" + readonly property string rotationFill: "\u010F" + readonly property string rotationOutline: "\u0110" + readonly property string runProjFill_large: "\u0111" + readonly property string runProjOutline_large: "\u0112" + readonly property string s_anchors: "\u0113" + readonly property string s_annotations: "\u0114" + readonly property string s_arrange: "\u0115" + readonly property string s_boundingBox: "\u0116" + readonly property string s_component: "\u0117" + readonly property string s_connections: "\u0118" + readonly property string s_edit: "\u0119" + readonly property string s_enterComponent: "\u011A" + readonly property string s_eventList: "\u011B" + readonly property string s_group: "\u011C" + readonly property string s_layouts: "\u011D" + readonly property string s_merging: "\u011E" + readonly property string s_mouseArea: "\u011F" + readonly property string s_positioners: "\u0120" + readonly property string s_selection: "\u0121" + readonly property string s_snapping: "\u0122" + readonly property string s_timeline: "\u0123" + readonly property string s_visibility: "\u0124" + readonly property string saveLogs_medium: "\u0125" + readonly property string scale_medium: "\u0126" + readonly property string search: "\u0127" + readonly property string search_small: "\u0128" + readonly property string sectionToggle: "\u0129" + readonly property string selectFill_medium: "\u012A" + readonly property string selectOutline_medium: "\u012B" + readonly property string selectParent_small: "\u012C" + readonly property string selection_small: "\u012D" + readonly property string settings_medium: "\u012E" + readonly property string signal_small: "\u012F" + readonly property string snapping_conf_medium: "\u0130" + readonly property string snapping_medium: "\u0131" + readonly property string snapping_small: "\u0132" + readonly property string sortascending_medium: "\u0133" + readonly property string sortdescending_medium: "\u0134" + readonly property string sphere_medium: "\u0135" + readonly property string sphere_small: "\u0136" + readonly property string splitColumns: "\u0137" + readonly property string splitRows: "\u0138" + readonly property string spotLight_small: "\u0139" + readonly property string stackedContainer_small: "\u013A" + readonly property string startNode: "\u013B" + readonly property string step_medium: "\u013C" + readonly property string stop_medium: "\u013D" + readonly property string testIcon: "\u013E" + readonly property string textAlignBottom: "\u013F" + readonly property string textAlignCenter: "\u0140" + readonly property string textAlignJustified: "\u0141" + readonly property string textAlignLeft: "\u0142" + readonly property string textAlignMiddle: "\u0143" + readonly property string textAlignRight: "\u0144" + readonly property string textAlignTop: "\u0145" + readonly property string textBulletList: "\u0146" + readonly property string textFullJustification: "\u0147" + readonly property string textNumberedList: "\u0148" + readonly property string textures_medium: "\u0149" + readonly property string tickIcon: "\u014A" + readonly property string tickMark_small: "\u014B" + readonly property string timeline_small: "\u014C" + readonly property string toEndFrame_medium: "\u014D" + readonly property string toNextFrame_medium: "\u014E" + readonly property string toPrevFrame_medium: "\u014F" + readonly property string toStartFrame_medium: "\u0150" + readonly property string topToolbar_annotations: "\u0151" + readonly property string topToolbar_closeFile: "\u0152" + readonly property string topToolbar_designMode: "\u0153" + readonly property string topToolbar_enterComponent: "\u0154" + readonly property string topToolbar_home: "\u0155" + readonly property string topToolbar_makeComponent: "\u0156" + readonly property string topToolbar_navFile: "\u0157" + readonly property string topToolbar_runProject: "\u0158" + readonly property string translationCreateFiles: "\u0159" + readonly property string translationCreateReport: "\u015A" + readonly property string translationExport: "\u015B" + readonly property string translationImport: "\u015C" + readonly property string translationSelectLanguages: "\u015D" + readonly property string translationTest: "\u015E" + readonly property string transparent: "\u015F" + readonly property string triState: "\u0160" + readonly property string triangleArcA: "\u0161" + readonly property string triangleArcB: "\u0162" + readonly property string triangleCornerA: "\u0163" + readonly property string triangleCornerB: "\u0164" + readonly property string unLinked: "\u0165" + readonly property string undo: "\u0166" + readonly property string unify_medium: "\u0167" + readonly property string unpin: "\u0168" + readonly property string upDownIcon: "\u0169" + readonly property string upDownSquare2: "\u016A" + readonly property string updateAvailable_medium: "\u016B" + readonly property string updateContent_medium: "\u016C" + readonly property string uploadcsv_large: "\u016D" + readonly property string uploadcsv_medium: "\u016E" + readonly property string uploadjson_large: "\u016F" + readonly property string uploadjson_medium: "\u0170" + readonly property string visibilityOff: "\u0171" + readonly property string visibilityOn: "\u0172" + readonly property string visible_medium: "\u0173" + readonly property string visible_small: "\u0174" + readonly property string wildcard: "\u0175" + readonly property string wizardsAutomotive: "\u0176" + readonly property string wizardsDesktop: "\u0177" + readonly property string wizardsGeneric: "\u0178" + readonly property string wizardsMcuEmpty: "\u0179" + readonly property string wizardsMcuGraph: "\u017A" + readonly property string wizardsMobile: "\u017B" + readonly property string wizardsUnknown: "\u017C" + readonly property string zoomAll: "\u017D" + readonly property string zoomIn: "\u017E" + readonly property string zoomIn_medium: "\u017F" + readonly property string zoomOut: "\u0180" + readonly property string zoomOut_medium: "\u0181" + readonly property string zoomSelection: "\u0182" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index 43badf91300..0aa9a1b7217 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -99,7 +99,8 @@ QtObject { property real inputHorizontalPadding: Math.round(6 * values.scaleFactor) property real typeLabelVerticalShift: Math.round(6 * values.scaleFactor) - property real scrollBarThickness: 10 + property real scrollBarThickness: 8 + property real scrollBarThicknessHover: 10 property real scrollBarActivePadding: 1 property real scrollBarInactivePadding: 2 @@ -221,7 +222,7 @@ QtObject { property real colorEditorPopupSpinBoxWidth: 54 // Popup Window - property real titleBarHeight: values.height + 10 + property real titleBarHeight: values.height + 20 property real popupMargin: 10 // Toolbar @@ -339,6 +340,7 @@ QtObject { // Panels & Panes property color themeBackgroundColorNormal: Theme.color(Theme.DSBackgroundColorNormal) property color themeBackgroundColorAlternate: Theme.color(Theme.DSBackgroundColorAlternate) + property color themeConnectionCodeEditor: Theme.color(Theme.DSconnectionCodeEditor) // Text colors property color themeTextColor: Theme.color(Theme.DStextColor) @@ -442,8 +444,20 @@ QtObject { property color themeDialogOutline: values.themeInteraction // Expression Builder - property color themePillBackground: Theme.color(Theme.DSdockWidgetSplitter) + property color themePillDefaultBackgroundIdle: Theme.color(Theme.DSpillDefaultBackgroundIdle) + property color themePillDefaultBackgroundHover: Theme.color(Theme.DSpillDefaultBackgroundHover) + property color themePillOperatorBackgroundIdle: Theme.color(Theme.DSpillOperatorBackgroundIdle) + property color themePillOperatorBackgroundHover: Theme.color(Theme.DSpillOperatorBackgroundHover) + property color themePillLiteralBackgroundIdle: Theme.color(Theme.DSpillLiteralBackgroundIdle) + property color themePillLiteralBackgroundHover: Theme.color(Theme.DSpillLiteralBackgroundHover) + property color themePillShadowBackground: values.themeInteraction + + property color themePillOutline: "#ffffffff" + + property color themePillText: Theme.color(Theme.DSpillText) + property color themePillTextSelected: Theme.color(Theme.DSpillTextSelected) + property color themePillTextEdit: Theme.color(Theme.DspillTextEdit) // Control Style Mapping property ControlStyle controlStyle: DefaultStyle {} @@ -458,4 +472,5 @@ QtObject { property ControlStyle statusbarControlStyle: StatusBarControlStyle {} property ControlStyle statesControlStyle: StatesControlStyle {} property ControlStyle searchControlStyle: SearchControlStyle {} + property ControlStyle viewStyle: ViewStyle {} } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ViewStyle.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ViewStyle.qml new file mode 100644 index 00000000000..2502f0a2523 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/ViewStyle.qml @@ -0,0 +1,9 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick + +ControlStyle { + scrollBarThickness: Values.scrollBarThickness + scrollBarThicknessHover: Values.scrollBarThicknessHover +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 75f1fb94fad..4d18734ddac 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/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir index 60138d86d0f..45e55c4edc6 100755 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir @@ -14,3 +14,4 @@ StatusBarControlStyle 1.0 StatusBarControlStyle.qml TopToolbarButtonStyle 1.0 TopToolbarButtonStyle.qml ViewBarButtonStyle 1.0 ViewBarButtonStyle.qml ViewBarControlStyle 1.0 ViewBarControlStyle.qml +ViewStyle 1.0 ViewStyle.qml diff --git a/share/qtcreator/qmldesigner/qt4mcu/metadata.qml b/share/qtcreator/qmldesigner/qt4mcu/metadata.qml index d4da42cc0a1..d208ee096ac 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/metadata.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/metadata.qml @@ -5,7 +5,7 @@ Metadata { id: metadataFile - defaultVersion: v24 + defaultVersion: v25 VersionData { id: v14 @@ -60,4 +60,16 @@ Metadata { name: "Qt for MCUs 2.4" path: "qul-24.qml" } + + VersionData { + id: v25 + name: "Qt for MCUs 2.5" + path: "qul-25.qml" + } + + VersionData { + id: v26 + name: "Qt for MCUs 2.6" + path: "qul-26.qml" + } } diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-17.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-17.qml index 5a29a3b7e62..b41e9794ac6 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-17.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-17.qml @@ -1,6 +1,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new MCU-specific imports: QtQuickUltralite.Extras, QtQuickUltralite.Layers + VersionData { name: "Qt for MCUs 1.7" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-18.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-18.qml index 8597cfd9320..8a74250cab1 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-18.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-18.qml @@ -1,6 +1,11 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new import: QtQuick.Shapes +// new import provides new types: QtQuick.Shapes.Shape, QtQuick.Shapes.ShapePath +// new types: QtQuick.Path, PathArc, PathLine, PathMove, PathQuad, PathCubic, PathElement, PathSvg + + VersionData { name: "Qt for MCUs 1.8" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-21.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-21.qml index 47d416ba9ee..bbbdcffd177 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-21.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-21.qml @@ -1,6 +1,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new property: QtQuick.Text::elide + VersionData { name: "Qt for MCUs 2.1" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-22.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-22.qml index 9af17175b49..82ec865cdde 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-22.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-22.qml @@ -1,6 +1,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new properties: QtQuick.Text::wrapMode, QtQuick.Controls.AbstractButton::icon + VersionData { name: "Qt for MCUs 2.2" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-23.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-23.qml index 867790a6542..934a8229a25 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-23.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-23.qml @@ -1,6 +1,9 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new type: QtQuick.Loader +// new properties: QtQuick.Flickable::boundsBehavior, ::flickableDirection + VersionData { name: "Qt for MCUs 2.3" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml index 0475203251a..dbeeabf971d 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml @@ -1,6 +1,9 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new type: QtQuick.AnimatedSprite +// new property: QtQuick.Loader::sourceComponent + VersionData { name: "Qt for MCUs 2.4" diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml new file mode 100644 index 00000000000..3fb7e1b9259 --- /dev/null +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml @@ -0,0 +1,217 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +VersionData { + name: "Qt for MCUs 2.5" + + bannedItems: [ + "QtQuick.AnimatedImage", + "QtQuick.Flow", + "QtQuick.FocusScope", + "QtQuick.Grid", + "QtQuick.GridView", + "QtQuick.PathView", + "QtQuick.TextEdit", + "QtQuick.TextInput", + "QtQuick.Controls", + "QtQuick.Controls.BusyIndicator", + "QtQuick.Controls.ButtonGroup", + "QtQuick.Controls.CheckDelegate", + "QtQuick.Controls.ComboBox", + "QtQuick.Controls.Container", + "QtQuick.Controls.DelayButton", + "QtQuick.Controls.Frame", + "QtQuick.Controls.GroupBox", + "QtQuick.Controls.ItemDelegate", + "QtQuick.Controls.Label", + "QtQuick.Controls.Page", + "QtQuick.Controls.PageIndicator", + "QtQuick.Controls.Pane", + "QtQuick.Controls.RadioDelegate", + "QtQuick.Controls.RangeSlider", + "QtQuick.Controls.RoundButton", + "QtQuick.Controls.ScrollView", + "QtQuick.Controls.SpinBox", + "QtQuick.Controls.StackView", + "QtQuick.Controls.SwipeDelegate", + "QtQuick.Controls.SwitchDelegate", + "QtQuick.Controls.TabBar", + "QtQuick.Controls.TabButton", + "QtQuick.Controls.TextArea", + "QtQuick.Controls.TextField", + "QtQuick.Controls.ToolBar", + "QtQuick.Controls.ToolButton", + "QtQuick.Controls.ToolSeparator", + "QtQuick.Controls.Tumbler", + "QtQuick.Shapes.ConicalGradient", + "QtQuick.Shapes.LinearGradient", + "QtQuick.Shapes.RadialGradient", + "QtQuick.Shapes.ShapeGradient" + ] + + allowedImports: [ + "QtQuick", + "QtQuick.Controls", + "QtQuick.Shapes", + "QtQuick.Timeline", + "QtQuickUltralite.Extras", + "QtQuickUltralite.Layers" + ] + + bannedImports: [ + "FlowView", + "SimulinkConnector" + ] + + //ComplexProperty is not a type, it's just a way to handle bigger props + ComplexProperty { + prefix: "font" + bannedProperties: ["wordSpacing", "letterSpacing", "hintingPreference", + "kerning", "preferShaping", "capitalization", + "strikeout", "underline", "styleName"] + } + + QtQml.Timer { + bannedProperties: ["triggeredOnStart"] + } + + QtQuick.Item { + bannedProperties: ["layer", "opacity", "smooth", "antialiasing", + "baselineOffset", "focus", "activeFocusOnTab", + "rotation", "scale", "transformOrigin"] + } + + QtQuick.Rectangle { + bannedProperties: ["gradient", "border"] + } + + QtQuick.Flickable { + bannedProperties: ["boundsMovement", "flickDeceleration", + "leftMargin", "rightMargin", "bottomMargin", "topMargin", + "originX", "originY", "pixelAligned", "pressDelay", "synchronousDrag"] + } + + QtQuick.MouseArea { + bannedProperties: ["propagateComposedEvents", "preventStealing", "cursorShape", + "scrollGestureEnabled", "drag", "acceptedButtons", "hoverEnabled"] + } + + QtQuick.Image { + allowChildren: false + allowedProperties: ["rotation", "scale", "transformOrigin"] + bannedProperties: ["mirror", "mipmap", "cache", "autoTransform", "asynchronous", + "sourceSize", "smooth"] + } + + QtQuick.BorderImage { + bannedProperties: ["asynchronous", "cache", "currentFrame", "frameCount", + "horizontalTileMode", "mirror", "progress", "smooth", "sourceSize", + "status", "verticalTileMode"] + } + + QtQuick.Text { + allowChildren: false + allowedProperties: ["rotation", "scale", "transformOrigin"] + bannedProperties: ["lineHeight", "lineHeightMode", "style", + "styleColor", "minimumPointSize", "minimumPixelSize", + "fontSizeMode", "renderType", "renderTypeQuality", "textFormat", "maximumLineCount"] + } + + QtQuick.Loader { + bannedProperties: ["asynchronous", "progress", "status"] + } + + //Padding is not an actual item, but rather set of properties in Text + Padding { + bannedProperties: ["bottomPadding", "topPadding", "leftPadding", "rightPadding"] + } + + QtQuick.Column { + bannedProperties: ["bottomPadding", "leftPadding", "rightPadding", "topPadding"] + } + + QtQuick.Row { + bannedProperties: ["bottomPadding", "leftPadding", "rightPadding", "topPadding", + "effectiveLayoutDirection", "layoutDirection"] + } + + QtQuick.ListView { + bannedProperties: ["cacheBuffer", "highlightRangeMode", "highlightMoveDuration", + "highlightResizeDuration", "preferredHighlightBegin", "layoutDirection", + "preferredHighlightEnd", "highlightFollowsCurrentItem", "keyNavigationWraps", + "snapMode", "highlightMoveVelocity", "highlightResizeVelocity"] + } + + QtQuick.Animation { + bannedProperties: ["paused"] + } + + //Quick Controls2 Items and properties: + + QtQuick.Controls.Control { + bannedProperties: ["focusPolicy", "hoverEnabled", "wheelEnabled"] + } + + QtQuick.Controls.AbstractButton { + bannedProperties: ["display", "autoExclusive", "icon"] + } + + QtQuick.Controls.ProgressBar { + bannedProperties: ["indeterminate"] + } + + QtQuick.Controls.Slider { + bannedProperties: ["live", "snapMode", "touchDragThreshold"] + } + + //Path and Shapes related: + + QtQuick.Path { + bannedProperties: ["scale", "pathElements"] + } + + QtQuick.PathArc { + bannedProperties: ["relativeX", "relativeY"] + } + + QtQuick.PathLine { + bannedProperties: ["relativeX", "relativeY"] + } + + QtQuick.PathMove { + bannedProperties: ["relativeX", "relativeY"] + } + + QtQuick.PathQuad { + bannedProperties: ["relativeX", "relativeY", + "relativeControlX", "relativeControlY"] + } + + QtQuick.PathCubic { + bannedProperties: ["relativeX", "relativeY", + "relativeControl1X", "relativeControl1Y", + "relativeControl2X", "relativeControl2Y"] + } + + QtQuick.PathElement { + //nothing + } + + QtQuick.PathSvg { + //nothing + } + + QtQuick.Shapes.Shape { + bannedProperties: ["asynchronous", "containsMode", "data", + "renderType", "status", "vendorExtensionsEnabled"] + } + + QtQuick.Shapes.ShapePath { + bannedProperties: ["dashOffset", "dashPattern", + "fillGradient", "strokeStyle"] + } + + QtQuickUltralite.Extras.ItemBuffer { + allowedProperties: ["rotation", "scale", "transformOrigin"] + } +} diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml index cc9235ed63c..26c68a6e7d4 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml @@ -1,6 +1,8 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// new property: QtQuick.Text::textFormat + VersionData { name: "Qt for MCUs 2.6" diff --git a/share/qtcreator/qmldesigner/stateseditor/Main.qml b/share/qtcreator/qmldesigner/stateseditor/Main.qml index 7e8ad669327..1e9c3dccde0 100644 --- a/share/qtcreator/qmldesigner/stateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/stateseditor/Main.qml @@ -579,26 +579,30 @@ Rectangle { anchors.topMargin: root.topMargin anchors.leftMargin: root.leftMargin - ScrollBar.horizontal: HelperWidgets.ScrollBar { + ScrollBar.horizontal: StudioControls.TransientScrollBar { id: horizontalBar + style: StudioTheme.Values.viewStyle parent: scrollView x: scrollView.leftPadding y: scrollView.height - height width: scrollView.availableWidth orientation: Qt.Horizontal + visible: root.isLandscape show: (scrollView.hovered || scrollView.focus || scrollView.adsFocus) && horizontalBar.isNeeded otherInUse: verticalBar.inUse } - ScrollBar.vertical: HelperWidgets.ScrollBar { + ScrollBar.vertical: StudioControls.TransientScrollBar { id: verticalBar + style: StudioTheme.Values.viewStyle parent: scrollView x: scrollView.mirrored ? 0 : scrollView.width - width y: scrollView.topPadding height: scrollView.availableHeight orientation: Qt.Vertical + visible: !root.isLandscape show: (scrollView.hovered || scrollView.focus || scrollView.adsFocus) && verticalBar.isNeeded diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/app_mcu.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/app_mcu.qmlproject.tpl index 80bb0c5743e..f17d608f96c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/app_mcu.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/app_mcu.qmlproject.tpl @@ -55,7 +55,7 @@ Project { QDS.qtForMCUs: true QDS.qt6Project: true - QDS.qdsVersion: "4.2" + QDS.qdsVersion: "4.3" QDS.quickVersion: "6.5" /* List of plugin directories passed to QML runtime */ diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/wizard.json index 5c27a294467..01baa5f9760 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-mcu/wizard.json @@ -188,7 +188,7 @@ { "isBinary": true, "source": "MCUDefaultStyle/images", - "target": "%{ProjectDirectory}/MCUDefaultStyle/images" + "target": "%{ProjectDirectory}/imports/MCUDefaultStyle/images" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl index 0b96ccf3ba3..50840bae070 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -105,7 +105,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "4.2" + qdsVersion: "4.3" quickVersion: "%{QtQuickVersion}" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl index 5e2d5923b1e..81f2ceba948 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl @@ -8,7 +8,7 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") include(FetchContent) FetchContent_Declare( ds - GIT_TAG qds-4.1 + GIT_TAG qds-4.3 GIT_REPOSITORY https://code.qt.io/qt-labs/qtquickdesigner-components.git ) diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml index 72f1c464f92..244f3beb83d 100644 --- a/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml @@ -32,7 +32,9 @@ PropertyEditorPane { Text { text: { - if (!hasQuick3DImport) + if (!isQt6Project) + qsTr("Texture Editor is not supported in Qt5 projects.") + else if (!hasQuick3DImport) qsTr("To use Texture Editor, first add the QtQuick3D module in the Components view.") else if (!hasMaterialLibrary) qsTr("Texture Editor is disabled inside a non-visual component.") diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index 30e4aa36b5b..09378040b1d 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -27,7 +27,6 @@ fullWhite=ffffffff lightWhite=ffdfdfdf offWhite=ffdcdada slateGrey=ff8d8d8d -concreteGrey=ffbbbbbb smokeGrey=ff8b8b8b shadowGrey=ff636363 duskGrey=ff606060 @@ -55,10 +54,8 @@ highlightHover=ff74CBFC ;DS Theme Palette END [Colors] -;DS controls theme - -;NEW FOR QtDS 4 -;4.0 +;DS controls theme START +;NEW FOR QtDS 4.0 DScontrolBackground_toolbarIdle=midnightGrey DScontrolBackground_toolbarHover=midnightGrey DScontrolBackground_topToolbarHover=ashGrey @@ -88,8 +85,8 @@ DSpopoutControlBackground_interaction=highlightBlue DSpopoutControlBackground_disabled=offBlack DSpopoutPopupBackground=nearBlack -DSpopoutControlBorder_idle=nearBlack -DSpopoutControlBorder_hover=midnightGrey +DSpopoutControlBorder_idle=midnightGrey +DSpopoutControlBorder_hover=raincloudGrey DSpopoutControlBorder_interaction=highlightBlue DSpopoutControlBorder_disabled=offBlack @@ -103,64 +100,47 @@ DSpopoutButtonBorder_hover=lightWhite DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offBlack +;4.4 +DSconnectionCodeEditor=midnightGrey +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullWhite +DSpillDefaultBackgroundIdle=graniteGrey +DSpillDefaultBackgroundHover=raincloudGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 -;REMAPPED -DSinteraction=highlightBlue -DScontrolBackground=dawnGrey -DScontrolOutlineInteraction=highlightBlue -DScontrolOutline=nearBlack -DSiconColorInteraction=nearBlack -DSiconColorSelected=nearBlack -DSsectionHeadBackground=midnightGrey -DSstateSeparatorColor=graniteGrey -DSnavigatorItemBackground=dawnGrey -DSnavigatorItemBackgroundHover=graniteGrey -DSnavigatorItemBackgroundSelected=midnightGrey -DStabActiveBackground=raincloudGrey -DStabActiveText=offWhite -DStabActiveIcon=offWhite -DStabFocusButtonHover=highlightBlue -DStabFocusButtonPress=highlightBlue -DStabFocusBackground=highlightBlue -DSnavigatorText=lightWhite -DSpopupBackground=offBlack -DSinteractionHover=highlightHover -DSnavigatorTextSelected=highlightBlue - -;contentious remap -;background color for main form view, library, navigator, properties, connections -QmlDesigner_BackgroundColorDarkAlternate=dawnGrey - -;TODO -DSscrollBarTrack=smokeGrey -DSscrollBarHandle=concreteGrey -DSscrollBarHandle_idle=slateGrey - -DSdockWidgetTitleBar=dawnGrey -DSdockWidgetSplitter=fullBlack - -;LEGACY DSwelcomeScreenBackground=ff242424 DSsubPanelBackground=ff1c1c1c DSthumbnailBackground=ff232323 DSthumbnailLabelBackground=ff2b2a2a + DSgreenLight=ff5cdc68 DSamberLight=ffffbf00 DSredLight=ffff0401 +DSinteraction=highlightBlue DSerrorColor=ffdf3a3a DSwarningColor=warning DSdisabledColor=ff707070 +DSinteractionHover=ff74cbfc +DScontrolBackground=dawnGrey DScontrolBackgroundInteraction=ff3d3d3d DScontrolBackgroundDisabled=ff2e2f30 DScontrolBackgroundGlobalHover=ff333333 DScontrolBackgroundHover=ff333333 +DScontrolOutline=nearBlack +DScontrolOutlineInteraction=highlightBlue DScontrolOutlineDisabled=ff707070 +DStextColor=ffffffff DStextColorDisabled=ff707070 DStextSelectionColor=ff2aafd3 DStextSelectedTextColor=ff000000 @@ -168,16 +148,18 @@ DStextSelectedTextColor=ff000000 DSplaceholderTextColor=ffffffff DSplaceholderTextColorInteraction=ffababab -DSiconColor=fullWhite +DSiconColor=ffffffff DSiconColorHover=ffffffff +DSiconColorInteraction=nearBlack DSiconColorDisabled=ffC7C7C7 -DSiconColorSelected=ff2aafd3 +DSiconColorSelected=nearBlack + DSlinkIndicatorColor=ff808080 DSlinkIndicatorColorHover=ffffffff DSlinkIndicatorColorInteraction=ff2aafd3 DSlinkIndicatorColorDisabled=ff707070 - +DSpopupBackground=offBlack DSpopupOverlayColor=99191919 DSsliderActiveTrack=ff7c7b7b @@ -191,8 +173,15 @@ DSsliderHandleHover=ff606060 DSsliderHandleFocus=ff0492c9 DSsliderHandleInteraction=ff2aafd3 +DSscrollBarTrack=dawnGrey +DSscrollBarHandle=offBlack +DSscrollBarHandle_idle=slateGrey + +DSsectionHeadBackground=midnightGrey + DSstateDefaultHighlight=ffffe400 -DSstateBackgroundColor=fffff000 +DSstateSeparatorColor=graniteGrey +DSstateBackgroundColor=ff383838 DSstatePreviewOutline=ffaaaaaa DSstatePanelBackground=ff252525 @@ -215,7 +204,11 @@ DStableHeaderText=ff00ff00 DSdockContainerBackground=ff242424 DSdockContainerSplitter=ff323232 DSdockAreaBackground=ff262728 + DSdockWidgetBackground=ff00ff00 +DSdockWidgetSplitter=fullBlack +DSdockWidgetTitleBar=dawnGrey + DStitleBarText=ffdadada DStitleBarIcon=ffffffff DStitleBarButtonHover=40ffffff @@ -230,16 +223,26 @@ DStabInactiveIcon=ffffffff DStabInactiveButtonHover=ff1f1f1f DStabInactiveButtonPress=ff1f1f1f +DStabActiveBackground=raincloudGrey +DStabActiveText=offWhite +DStabActiveIcon=offWhite DStabActiveButtonHover=ffdadada DStabActiveButtonPress=ffdadada +DStabFocusBackground=highlightBlue DStabFocusText=ff111111 DStabFocusIcon=ff000000 +DStabFocusButtonHover=highlightBlue +DStabFocusButtonPress=highlightBlue DSnavigatorBranch=ff7c7b7b DSnavigatorBranchIndicator=ff7c7b7b +DSnavigatorItemBackground=dawnGrey +DSnavigatorItemBackgroundHover=graniteGrey +DSnavigatorItemBackgroundSelected=midnightGrey +DSnavigatorText=lightWhite DSnavigatorTextHover=ffffffff - +DSnavigatorTextSelected=ff2aafd3 DSnavigatorIcon=ffffffff DSnavigatorIconHover=ffa1a1a1 DSnavigatorIconSelected=ffffffff @@ -259,8 +262,11 @@ DSUnimportedModuleColor=ffe33c2e DSBackgroundColorAlternate=alternateBackground DSBackgroundColorNormal=normalBackground +DStoolbarBackground=midnightGrey + ;DS controls theme END + BackgroundColorAlternate=alternateBackground BackgroundColorDark=shadowBackground BackgroundColorHover=hoverBackground diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme index 24a0f09fc82..241502d1e53 100644 --- a/share/qtcreator/themes/default.creatortheme +++ b/share/qtcreator/themes/default.creatortheme @@ -94,6 +94,18 @@ DSpopoutButtonBorder_hover=shadowGrey DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offWhite +;4.4 +DSconnectionCodeEditor=lightWhite +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullBlack +DSpillDefaultBackgroundIdle=shadowGrey +DSpillDefaultBackgroundHover=smokeGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 DSpanelBackground=ffeaeaea diff --git a/share/qtcreator/themes/design-light.creatortheme b/share/qtcreator/themes/design-light.creatortheme index 46fc695570a..5c9a0c6277a 100644 --- a/share/qtcreator/themes/design-light.creatortheme +++ b/share/qtcreator/themes/design-light.creatortheme @@ -107,6 +107,18 @@ DSpopoutButtonBorder_hover=shadowGrey DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offWhite +;4.4 +DSconnectionCodeEditor=lightWhite +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullBlack +DSpillDefaultBackgroundIdle=shadowGrey +DSpillDefaultBackgroundHover=smokeGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 DSpanelBackground=ffeaeaea diff --git a/share/qtcreator/themes/design.creatortheme b/share/qtcreator/themes/design.creatortheme index 1d8295145e4..f2a12766b9d 100644 --- a/share/qtcreator/themes/design.creatortheme +++ b/share/qtcreator/themes/design.creatortheme @@ -90,8 +90,8 @@ DSpopoutControlBackground_interaction=highlightBlue DSpopoutControlBackground_disabled=offBlack DSpopoutPopupBackground=nearBlack -DSpopoutControlBorder_idle=nearBlack -DSpopoutControlBorder_hover=midnightGrey +DSpopoutControlBorder_idle=midnightGrey +DSpopoutControlBorder_hover=raincloudGrey DSpopoutControlBorder_interaction=highlightBlue DSpopoutControlBorder_disabled=offBlack @@ -105,6 +105,18 @@ DSpopoutButtonBorder_hover=lightWhite DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offBlack +;4.4 +DSconnectionCodeEditor=midnightGrey +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullWhite +DSpillDefaultBackgroundIdle=graniteGrey +DSpillDefaultBackgroundHover=raincloudGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 ;REMAPPED diff --git a/share/qtcreator/themes/flat-dark.creatortheme b/share/qtcreator/themes/flat-dark.creatortheme index 09a2d528c01..7803e42bc27 100644 --- a/share/qtcreator/themes/flat-dark.creatortheme +++ b/share/qtcreator/themes/flat-dark.creatortheme @@ -31,7 +31,6 @@ fullWhite=ffffffff lightWhite=ffdfdfdf offWhite=ffdcdada slateGrey=ff8d8d8d -concreteGrey=ffbbbbbb smokeGrey=ff8b8b8b shadowGrey=ff636363 duskGrey=ff606060 @@ -59,10 +58,9 @@ highlightHover=ff74CBFC ;DS Theme Palette END [Colors] -;DS controls theme +;DS controls theme START -;NEW FOR QtDS 4 -;4.0 +;NEW FOR QtDS 4.0 DScontrolBackground_toolbarIdle=midnightGrey DScontrolBackground_toolbarHover=midnightGrey DScontrolBackground_topToolbarHover=ashGrey @@ -92,8 +90,8 @@ DSpopoutControlBackground_interaction=highlightBlue DSpopoutControlBackground_disabled=offBlack DSpopoutPopupBackground=nearBlack -DSpopoutControlBorder_idle=nearBlack -DSpopoutControlBorder_hover=midnightGrey +DSpopoutControlBorder_idle=midnightGrey +DSpopoutControlBorder_hover=raincloudGrey DSpopoutControlBorder_interaction=highlightBlue DSpopoutControlBorder_disabled=offBlack @@ -107,64 +105,47 @@ DSpopoutButtonBorder_hover=lightWhite DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offBlack +;4.4 +DSconnectionCodeEditor=midnightGrey +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullWhite +DSpillDefaultBackgroundIdle=graniteGrey +DSpillDefaultBackgroundHover=raincloudGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 -;REMAPPED -DSinteraction=highlightBlue -DScontrolBackground=dawnGrey -DScontrolOutlineInteraction=highlightBlue -DScontrolOutline=nearBlack -DSiconColorInteraction=nearBlack -DSiconColorSelected=nearBlack -DSsectionHeadBackground=midnightGrey -DSstateSeparatorColor=graniteGrey -DSnavigatorItemBackground=dawnGrey -DSnavigatorItemBackgroundHover=graniteGrey -DSnavigatorItemBackgroundSelected=midnightGrey -DStabActiveBackground=raincloudGrey -DStabActiveText=offWhite -DStabActiveIcon=offWhite -DStabFocusButtonHover=highlightBlue -DStabFocusButtonPress=highlightBlue -DStabFocusBackground=highlightBlue -DSnavigatorText=lightWhite -DSpopupBackground=offBlack -DSinteractionHover=highlightHover -DSnavigatorTextSelected=highlightBlue - -;contentious remap -;background color for main form view, library, navigator, properties, connections -QmlDesigner_BackgroundColorDarkAlternate=dawnGrey - -;TODO -DSscrollBarTrack=smokeGrey -DSscrollBarHandle=concreteGrey -DSscrollBarHandle_idle=slateGrey - -DSdockWidgetTitleBar=dawnGrey -DSdockWidgetSplitter=fullBlack - -;LEGACY DSwelcomeScreenBackground=ff242424 DSsubPanelBackground=ff1c1c1c DSthumbnailBackground=ff232323 DSthumbnailLabelBackground=ff2b2a2a + DSgreenLight=ff5cdc68 DSamberLight=ffffbf00 DSredLight=ffff0401 +DSinteraction=highlightBlue DSerrorColor=ffdf3a3a DSwarningColor=warning DSdisabledColor=ff707070 +DSinteractionHover=ff74cbfc +DScontrolBackground=dawnGrey DScontrolBackgroundInteraction=ff3d3d3d DScontrolBackgroundDisabled=ff2e2f30 DScontrolBackgroundGlobalHover=ff333333 DScontrolBackgroundHover=ff333333 +DScontrolOutline=nearBlack +DScontrolOutlineInteraction=highlightBlue DScontrolOutlineDisabled=ff707070 +DStextColor=ffffffff DStextColorDisabled=ff707070 DStextSelectionColor=ff2aafd3 DStextSelectedTextColor=ff000000 @@ -172,16 +153,18 @@ DStextSelectedTextColor=ff000000 DSplaceholderTextColor=ffffffff DSplaceholderTextColorInteraction=ffababab -DSiconColor=fullWhite +DSiconColor=ffffffff DSiconColorHover=ffffffff +DSiconColorInteraction=nearBlack DSiconColorDisabled=ffC7C7C7 -DSiconColorSelected=ff2aafd3 +DSiconColorSelected=nearBlack + DSlinkIndicatorColor=ff808080 DSlinkIndicatorColorHover=ffffffff DSlinkIndicatorColorInteraction=ff2aafd3 DSlinkIndicatorColorDisabled=ff707070 - +DSpopupBackground=offBlack DSpopupOverlayColor=99191919 DSsliderActiveTrack=ff7c7b7b @@ -195,8 +178,15 @@ DSsliderHandleHover=ff606060 DSsliderHandleFocus=ff0492c9 DSsliderHandleInteraction=ff2aafd3 +DSscrollBarTrack=dawnGrey +DSscrollBarHandle=offBlack +DSscrollBarHandle_idle=slateGrey + +DSsectionHeadBackground=midnightGrey + DSstateDefaultHighlight=ffffe400 -DSstateBackgroundColor=fffff000 +DSstateSeparatorColor=graniteGrey +DSstateBackgroundColor=ff383838 DSstatePreviewOutline=ffaaaaaa DSstatePanelBackground=ff252525 @@ -219,7 +209,11 @@ DStableHeaderText=ff00ff00 DSdockContainerBackground=ff242424 DSdockContainerSplitter=ff323232 DSdockAreaBackground=ff262728 + DSdockWidgetBackground=ff00ff00 +DSdockWidgetSplitter=fullBlack +DSdockWidgetTitleBar=dawnGrey + DStitleBarText=ffdadada DStitleBarIcon=ffffffff DStitleBarButtonHover=40ffffff @@ -234,16 +228,26 @@ DStabInactiveIcon=ffffffff DStabInactiveButtonHover=ff1f1f1f DStabInactiveButtonPress=ff1f1f1f +DStabActiveBackground=raincloudGrey +DStabActiveText=offWhite +DStabActiveIcon=offWhite DStabActiveButtonHover=ffdadada DStabActiveButtonPress=ffdadada +DStabFocusBackground=highlightBlue DStabFocusText=ff111111 DStabFocusIcon=ff000000 +DStabFocusButtonHover=highlightBlue +DStabFocusButtonPress=highlightBlue DSnavigatorBranch=ff7c7b7b DSnavigatorBranchIndicator=ff7c7b7b +DSnavigatorItemBackground=dawnGrey +DSnavigatorItemBackgroundHover=graniteGrey +DSnavigatorItemBackgroundSelected=midnightGrey +DSnavigatorText=lightWhite DSnavigatorTextHover=ffffffff - +DSnavigatorTextSelected=ff2aafd3 DSnavigatorIcon=ffffffff DSnavigatorIconHover=ffa1a1a1 DSnavigatorIconSelected=ffffffff @@ -263,6 +267,8 @@ DSUnimportedModuleColor=ffe33c2e DSBackgroundColorAlternate=alternateBackground DSBackgroundColorNormal=normalBackground +DStoolbarBackground=midnightGrey + ;DS controls theme END BackgroundColorAlternate=alternateBackground diff --git a/share/qtcreator/themes/flat-light.creatortheme b/share/qtcreator/themes/flat-light.creatortheme index 64cb37a1a37..21ded057ece 100644 --- a/share/qtcreator/themes/flat-light.creatortheme +++ b/share/qtcreator/themes/flat-light.creatortheme @@ -103,6 +103,18 @@ DSpopoutButtonBorder_hover=shadowGrey DSpopoutButtonBorder_interaction=highlightBlue DSpopoutButtonBorder_disabled=offWhite +;4.4 +DSconnectionCodeEditor=lightWhite +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullBlack +DSpillDefaultBackgroundIdle=shadowGrey +DSpillDefaultBackgroundHover=smokeGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B + ;END NEW FOR QtDS 4 DSpanelBackground=ffeaeaea diff --git a/share/qtcreator/themes/flat.creatortheme b/share/qtcreator/themes/flat.creatortheme index 4ca0d2d1f45..6dc677d9ff3 100644 --- a/share/qtcreator/themes/flat.creatortheme +++ b/share/qtcreator/themes/flat.creatortheme @@ -25,7 +25,6 @@ fullWhite=ffffffff lightWhite=ffdfdfdf offWhite=ffdcdada slateGrey=ff8d8d8d -concreteGrey=ffbbbbbb smokeGrey=ff8b8b8b shadowGrey=ff636363 duskGrey=ff606060 @@ -55,60 +54,69 @@ highlightHover=ff74CBFC [Colors] ;DS controls theme START -;NEW FOR QtDS 4 -;4.0 -DScontrolBackground_toolbarIdle=offWhite -DScontrolBackground_toolbarHover=offWhite -DScontrolBackground_topToolbarHover=concreteGrey -DScontrolBackground_statusbarIdle=concreteGrey -DSControlBackground_statusbarHover=lightWhite -DScontrolOutline_topToolbarIdle=concreteGrey -DScontrolOutline_topToolbarHover=lightWhite +;NEW FOR QtDS 4.0 +DScontrolBackground_toolbarIdle=midnightGrey +DScontrolBackground_toolbarHover=midnightGrey +DScontrolBackground_topToolbarHover=ashGrey +DScontrolBackground_statusbarIdle=offBlack +DSControlBackground_statusbarHover=dawnGrey +DScontrolOutline_topToolbarIdle=dawnGrey +DScontrolOutline_topToolbarHover=raincloudGrey DSidleGreen=idleGreen DSrunningGreen=runningGreen -DStoolbarBackground=offWhite +DStoolbarBackground=midnightGrey DStoolbarIcon_blocked=shadowGrey -DSthumbnailBackground_baseState=smokeGrey -DStextColor=raincloudGrey -DSstatusbarBackground=concreteGrey +DSthumbnailBackground_baseState=nearBlack +DStextColor=lightWhite +DSstatusbarBackground=offBlack DSprimaryButton_hoverHighlight=highlightHover -DSstateBackgroundColor_hover=concreteGrey -DSstateControlBackgroundColor_globalHover=concreteGrey -DSstateControlBackgroundColor_hover=smokeGrey +DSstateBackgroundColor_hover=ashGrey +DSstateControlBackgroundColor_globalHover=ashGrey +DSstateControlBackgroundColor_hover=raincloudGrey DSpanelBackground=dawnGrey ;4.3 -DSpopoutBackground=offWhite -DSpopoutControlBackground_idle=offWhite -DSpopoutControlBackground_hover=lightWhite -DSpopoutControlBackground_globalHover=lightWhite +DSpopoutBackground=offBlack +DSpopoutControlBackground_idle=offBlack +DSpopoutControlBackground_hover=dawnGrey +DSpopoutControlBackground_globalHover=dawnGrey DSpopoutControlBackground_interaction=highlightBlue -DSpopoutControlBackground_disabled=offWhite -DSpopoutPopupBackground=lightWhite +DSpopoutControlBackground_disabled=offBlack +DSpopoutPopupBackground=nearBlack -DSpopoutControlBorder_idle=slateGrey -DSpopoutControlBorder_hover=concreteGrey +DSpopoutControlBorder_idle=midnightGrey +DSpopoutControlBorder_hover=raincloudGrey DSpopoutControlBorder_interaction=highlightBlue -DSpopoutControlBorder_disabled=offWhite +DSpopoutControlBorder_disabled=offBlack -DSpopoutButtonBackground_idle=offWhite -DSpopoutButtonBackground_hover=lightWhite +DSpopoutButtonBackground_idle=offBlack +DSpopoutButtonBackground_hover=dawnGrey DSpopoutButtonBackground_interaction=highlightBlue -DSpopoutButtonBackground_disabled=offWhite +DSpopoutButtonBackground_disabled=offBlack -DSpopoutButtonBorder_idle=smokeGrey -DSpopoutButtonBorder_hover=shadowGrey +DSpopoutButtonBorder_idle=slateGrey +DSpopoutButtonBorder_hover=lightWhite DSpopoutButtonBorder_interaction=highlightBlue -DSpopoutButtonBorder_disabled=offWhite +DSpopoutButtonBorder_disabled=offBlack + +;4.4 +DSconnectionCodeEditor=midnightGrey +DSpillText=fullWhite +DSpillTextSelected=fullBlack +DspillTextEdit=fullWhite +DSpillDefaultBackgroundIdle=graniteGrey +DSpillDefaultBackgroundHover=raincloudGrey +DSpillOperatorBackgroundIdle=ff6b2a7b +DSpillOperatorBackgroundHover=ff7e478b +DSpillLiteralBackgroundIdle=ff447953 +DSpillLiteralBackgroundHover=ff61866B ;END NEW FOR QtDS 4 -DSpanelBackground=ffeaeaea - -DSwelcomeScreenBackground=ffEAEAEA -DSsubPanelBackground=ffEFEFEF -DSthumbnailBackground=ffE8E8E8 -DSthumbnailLabelBackground=ffDDDDDD +DSwelcomeScreenBackground=ff242424 +DSsubPanelBackground=ff1c1c1c +DSthumbnailBackground=ff232323 +DSthumbnailLabelBackground=ff2b2a2a DSgreenLight=ff5cdc68 DSamberLight=ffffbf00 @@ -117,45 +125,46 @@ DSredLight=ffff0401 DSinteraction=highlightBlue DSerrorColor=ffdf3a3a DSwarningColor=warning -DSdisabledColor=ff8e8e8e +DSdisabledColor=ff707070 -DSinteractionHover=highlightHover +DSinteractionHover=ff74cbfc -DScontrolBackground=ffeaeaea -DScontrolBackgroundInteraction=ffc9c9c9 -DScontrolBackgroundDisabled=ffeaeaea -DScontrolBackgroundGlobalHover=ffdedddd -DScontrolBackgroundHover=ffdedddd +DScontrolBackground=dawnGrey +DScontrolBackgroundInteraction=ff3d3d3d +DScontrolBackgroundDisabled=ff2e2f30 +DScontrolBackgroundGlobalHover=ff333333 +DScontrolBackgroundHover=ff333333 -DScontrolOutline=ffcecccc -DScontrolOutlineInteraction=ff2aafd3 +DScontrolOutline=nearBlack +DScontrolOutlineInteraction=highlightBlue DScontrolOutlineDisabled=ff707070 +DStextColor=ffffffff DStextColorDisabled=ff707070 DStextSelectionColor=ff2aafd3 DStextSelectedTextColor=ff000000 -DSplaceholderTextColor=ff262626 +DSplaceholderTextColor=ffffffff DSplaceholderTextColorInteraction=ffababab -DSiconColor=ff262626 -DSiconColorHover=ff191919 -DSiconColorInteraction=ffffffff -DSiconColorDisabled=ff707070 -DSiconColorSelected=ff2aafd3 +DSiconColor=ffffffff +DSiconColorHover=ffffffff +DSiconColorInteraction=nearBlack +DSiconColorDisabled=ffC7C7C7 +DSiconColorSelected=nearBlack DSlinkIndicatorColor=ff808080 -DSlinkIndicatorColorHover=ff1f1f1f +DSlinkIndicatorColorHover=ffffffff DSlinkIndicatorColorInteraction=ff2aafd3 DSlinkIndicatorColorDisabled=ff707070 -DSpopupBackground=ffd3d3d3 +DSpopupBackground=offBlack DSpopupOverlayColor=99191919 DSsliderActiveTrack=ff7c7b7b DSsliderActiveTrackHover=ff000000 DSsliderActiveTrackFocus=ffaaaaaa -DSsliderInactiveTrack=ffaaaaaa +DSsliderInactiveTrack=ff595959 DSsliderInactiveTrackHover=ff505050 DSsliderInactiveTrackFocus=ff606060 DSsliderHandle=ff1f1f1f @@ -163,19 +172,19 @@ DSsliderHandleHover=ff606060 DSsliderHandleFocus=ff0492c9 DSsliderHandleInteraction=ff2aafd3 -DSscrollBarTrack=smokeGrey -DSscrollBarHandle=shadowGrey +DSscrollBarTrack=dawnGrey +DSscrollBarHandle=offBlack DSscrollBarHandle_idle=slateGrey -DSsectionHeadBackground=ffd8d8d8 +DSsectionHeadBackground=midnightGrey DSstateDefaultHighlight=ffffe400 -DSstateSeparatorColor=ffadadad -DSstateBackgroundColor=ffe0e0e0 -DSstatePreviewOutline=ff363636 +DSstateSeparatorColor=graniteGrey +DSstateBackgroundColor=ff383838 +DSstatePreviewOutline=ffaaaaaa -DSstatePanelBackground=ffdadada -DSstateHighlight=ff8d8d8d +DSstatePanelBackground=ff252525 +DSstateHighlight=ff727272 DSchangedStateText=ff99ccff @@ -191,56 +200,56 @@ DSactionJIT=ff2db543 DStableHeaderBackground=ffff0000 DStableHeaderText=ff00ff00 -DSdockContainerBackground=ff323232 +DSdockContainerBackground=ff242424 DSdockContainerSplitter=ff323232 DSdockAreaBackground=ff262728 DSdockWidgetBackground=ff00ff00 -DSdockWidgetSplitter=ff595959 -DSdockWidgetTitleBar=ffeaeaea +DSdockWidgetSplitter=fullBlack +DSdockWidgetTitleBar=dawnGrey DStitleBarText=ffdadada -DStitleBarIcon=ff4f5052 +DStitleBarIcon=ffffffff DStitleBarButtonHover=40ffffff DStitleBarButtonPress=60ffffff -DStabContainerBackground=ffdadada +DStabContainerBackground=ff1f1f1f DStabSplitter=ff595959 -DStabInactiveBackground=ff999999 -DStabInactiveText=ff262626 +DStabInactiveBackground=ff1f1f1f +DStabInactiveText=ffdadada DStabInactiveIcon=ffffffff DStabInactiveButtonHover=ff1f1f1f DStabInactiveButtonPress=ff1f1f1f -DStabActiveBackground=ffdadada -DStabActiveText=ff111111 -DStabActiveIcon=ff000000 +DStabActiveBackground=raincloudGrey +DStabActiveText=offWhite +DStabActiveIcon=offWhite DStabActiveButtonHover=ffdadada DStabActiveButtonPress=ffdadada -DStabFocusBackground=ff2aafd3 +DStabFocusBackground=highlightBlue DStabFocusText=ff111111 DStabFocusIcon=ff000000 -DStabFocusButtonHover=ff2aafd3 -DStabFocusButtonPress=ff2aafd3 +DStabFocusButtonHover=highlightBlue +DStabFocusButtonPress=highlightBlue DSnavigatorBranch=ff7c7b7b DSnavigatorBranchIndicator=ff7c7b7b -DSnavigatorItemBackground=ffeaeaea -DSnavigatorItemBackgroundHover=ffdedddd -DSnavigatorItemBackgroundSelected=ffffffff -DSnavigatorText=ff262626 -DSnavigatorTextHover=ff1f1f1f +DSnavigatorItemBackground=dawnGrey +DSnavigatorItemBackgroundHover=graniteGrey +DSnavigatorItemBackgroundSelected=midnightGrey +DSnavigatorText=lightWhite +DSnavigatorTextHover=ffffffff DSnavigatorTextSelected=ff2aafd3 -DSnavigatorIcon=ff1f1f1f -DSnavigatorIconHover=ff7c7b7b -DSnavigatorIconSelected=ff1f1f1f +DSnavigatorIcon=ffffffff +DSnavigatorIconHover=ffa1a1a1 +DSnavigatorIconSelected=ffffffff DSnavigatorAliasIconChecked=ffff0000 DSnavigatorDropIndicatorBackground=ff2aafd3 DSnavigatorDropIndicatorOutline=ff2aafd3 -DSheaderViewBackground=ffd8d8d8 +DSheaderViewBackground=ff1f1f1f DStableViewAlternateBackground=ff00ff00 DStoolTipBackground=ff111111 @@ -249,11 +258,14 @@ DStoolTipText=ffdadada DSUnimportedModuleColor=ffe33c2e -DSBackgroundColorAlternate=ffeaeaea -DSBackgroundColorNormal=ffd8d8d8 +DSBackgroundColorAlternate=alternateBackground +DSBackgroundColorNormal=normalBackground + +DStoolbarBackground=midnightGrey ;DS controls theme END + BackgroundColorAlternate=alternateBackground BackgroundColorDark=shadowBackground BackgroundColorHover=hoverBackground diff --git a/src/libs/3rdparty/sqlite/sqlite3.c b/src/libs/3rdparty/sqlite/sqlite3.c index dd3b5c57570..1884b082396 100644 --- a/src/libs/3rdparty/sqlite/sqlite3.c +++ b/src/libs/3rdparty/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.42.0. By combining all the individual C code files into this +** version 3.43.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -16,6 +16,9 @@ ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. +** +** The content in this amalgamation comes from Fossil check-in +** d3a40c05c49e1a49264912b1a05bc2143ac. */ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 @@ -50,11 +53,11 @@ ** used on lines of code that actually ** implement parts of coverage testing. ** -** OPTIMIZATION-IF-TRUE - This branch is allowed to alway be false +** OPTIMIZATION-IF-TRUE - This branch is allowed to always be false ** and the correct answer is still obtained, ** though perhaps more slowly. ** -** OPTIMIZATION-IF-FALSE - This branch is allowed to alway be true +** OPTIMIZATION-IF-FALSE - This branch is allowed to always be true ** and the correct answer is still obtained, ** though perhaps more slowly. ** @@ -456,9 +459,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.42.0" -#define SQLITE_VERSION_NUMBER 3042000 -#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0" +#define SQLITE_VERSION "3.43.1" +#define SQLITE_VERSION_NUMBER 3043001 +#define SQLITE_SOURCE_ID "2023-09-11 12:01:27 2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -838,6 +841,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) @@ -1500,7 +1504,7 @@ struct sqlite3_io_methods { ** by clients within the current process, only within other processes. ** **
  • [[SQLITE_FCNTL_CKSM_FILE]] -** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the ** [checksum VFS shim] only. ** **
  • [[SQLITE_FCNTL_RESET_CACHE]] @@ -2764,7 +2768,7 @@ struct sqlite3_mem_methods { ** the [VACUUM] command will fail with an obscure error when attempting to ** process a table with generated columns and a descending index. This is ** not considered a bug since SQLite versions 3.3.0 and earlier do not support -** either generated columns or decending indexes. +** either generated columns or descending indexes. ** ** ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] @@ -3045,6 +3049,7 @@ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether ** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. */ SQLITE_API void sqlite3_interrupt(sqlite3*); SQLITE_API int sqlite3_is_interrupted(sqlite3*); @@ -3698,8 +3703,10 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** M argument should be the bitwise OR-ed combination of ** zero or more [SQLITE_TRACE] constants. ** -** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides -** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. ** ** ^The X callback is invoked whenever any of the events identified by ** mask M occur. ^The integer return value from the callback is currently @@ -4068,7 +4075,7 @@ SQLITE_API int sqlite3_open_v2( ** as F) must be one of: ** @@ -4181,7 +4188,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); /* ** CAPI3REF: Create and Destroy VFS Filenames ** -** These interfces are provided for use by [VFS shim] implementations and +** These interfaces are provided for use by [VFS shim] implementations and ** are not useful outside of that context. ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of @@ -4728,6 +4735,41 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); */ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt @@ -4891,7 +4933,7 @@ typedef struct sqlite3_context sqlite3_context; ** with it may be passed. ^It is called to dispose of the BLOB or string even ** if the call to the bind API fails, except the destructor is not called if ** the third parameter is a NULL pointer or the fourth parameter is negative. -** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that ** the application remains responsible for disposing of the object. ^In this ** case, the object and the provided pointer to it must remain valid until ** either the prepared statement is finalized or the same SQL parameter is @@ -5570,14 +5612,26 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** -** ^If the most recent call to [sqlite3_step(S)] for the -** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], -** or if [sqlite3_step(S)] has never before been called on S, -** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. @@ -5794,7 +5848,7 @@ SQLITE_API int sqlite3_create_window_function( ** [application-defined SQL function] ** that has side-effects or that could potentially leak sensitive information. ** This will prevent attacks in which an application is tricked -** into using a database file that has had its schema surreptiously +** into using a database file that has had its schema surreptitiously ** modified to invoke the application-defined function in ways that are ** harmful. **

    @@ -8471,7 +8525,8 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_TRACEFLAGS 31 #define SQLITE_TESTCTRL_TUNE 32 #define SQLITE_TESTCTRL_LOGEST 33 -#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking @@ -9503,8 +9558,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections -** unlock-notify callback may also be canceled by closing the blocked +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes @@ -9927,7 +9982,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** [[SQLITE_VTAB_DIRECTONLY]]

    SQLITE_VTAB_DIRECTONLY
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** prohibits that virtual table from being used from within triggers and ** views. **
    @@ -10117,7 +10172,7 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); ** communicated to the xBestIndex method as a ** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use ** this constraint, it must set the corresponding -** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under ** the usual mode of handling IN operators, SQLite generates [bytecode] ** that invokes the [xFilter|xFilter() method] once for each value ** on the right-hand side of the IN operator.)^ Thus the virtual table @@ -10546,7 +10601,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** When the [sqlite3_blob_write()] API is used to update a blob column, ** the pre-update hook is invoked with SQLITE_DELETE. This is because the ** in this case the new values are not available. In this case, when a -** callback made with op==SQLITE_DELETE is actuall a write using the +** callback made with op==SQLITE_DELETE is actually a write using the ** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns ** the index of the column being written. In other cases, where the ** pre-update hook is being invoked for some other reason, including a @@ -13064,7 +13119,7 @@ struct Fts5PhraseIter { ** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); @@ -13293,8 +13348,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; @@ -13342,7 +13397,7 @@ struct fts5_api { int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -13351,7 +13406,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -13359,7 +13414,7 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); @@ -13470,7 +13525,7 @@ struct fts5_api { ** level of recursion for each term. A stack overflow can result ** if the number of terms is too large. In practice, most SQL ** never has more than 3 or 4 terms. Use a value of 0 to disable -** any limit on the number of terms in a compount SELECT. +** any limit on the number of terms in a compound SELECT. */ #ifndef SQLITE_MAX_COMPOUND_SELECT # define SQLITE_MAX_COMPOUND_SELECT 500 @@ -14573,8 +14628,31 @@ typedef INT16_TYPE LogEst; ** the end of buffer S. This macro returns true if P points to something ** contained within the buffer S. */ -#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +/* +** P is one byte past the end of a large buffer. Return true if a span of bytes +** between S..E crosses the end of that buffer. In other words, return true +** if the sub-buffer S..E-1 overflows the buffer whose last byte is P-1. +** +** S is the start of the span. E is one byte past the end of end of span. +** +** P +** |-----------------| FALSE +** |-------| +** S E +** +** P +** |-----------------| +** |-------| TRUE +** S E +** +** P +** |-----------------| +** |-------| FALSE +** S E +*/ +#define SQLITE_OVERFLOW(P,S,E) (((uptr)(S)<(uptr)(P))&&((uptr)(E)>(uptr)(P))) /* ** Macros to determine whether the machine is big or little endian, @@ -14808,7 +14886,7 @@ struct BusyHandler { /* ** Name of table that holds the database schema. ** -** The PREFERRED names are used whereever possible. But LEGACY is also +** The PREFERRED names are used wherever possible. But LEGACY is also ** used for backwards compatibility. ** ** 1. Queries can use either the PREFERRED or the LEGACY names @@ -14922,6 +15000,7 @@ typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct FKey FKey; +typedef struct FpDecode FpDecode; typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; @@ -14940,6 +15019,7 @@ typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; +typedef struct RCStr RCStr; typedef struct RenameToken RenameToken; typedef struct Returning Returning; typedef struct RowSet RowSet; @@ -15577,6 +15657,10 @@ SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); # define enable_simulated_io_errors() #endif +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager*); +#endif + #endif /* SQLITE_PAGER_H */ /************** End of pager.h ***********************************************/ @@ -15906,9 +15990,7 @@ SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int flags); SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeCursorPin(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor*); -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor*); -#endif SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor*); @@ -16383,7 +16465,7 @@ typedef struct VdbeOpList VdbeOpList; /* 8 */ 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x01, 0x01,\ /* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x49, 0x49, 0x49,\ /* 24 */ 0x49, 0x01, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49,\ -/* 32 */ 0x41, 0x01, 0x01, 0x01, 0x41, 0x01, 0x41, 0x41,\ +/* 32 */ 0x41, 0x01, 0x41, 0x41, 0x41, 0x01, 0x41, 0x41,\ /* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x23, 0x0b,\ /* 48 */ 0x01, 0x01, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ /* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x03, 0x01, 0x41,\ @@ -16395,7 +16477,7 @@ typedef struct VdbeOpList VdbeOpList; /* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ /* 112 */ 0x40, 0x00, 0x12, 0x40, 0x40, 0x10, 0x40, 0x00,\ /* 120 */ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10, 0x10,\ -/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,\ +/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x50,\ /* 136 */ 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50, 0x40,\ /* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ /* 152 */ 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\ @@ -16577,7 +16659,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** The VdbeCoverage macros are used to set a coverage testing point ** for VDBE branch instructions. The coverage testing points are line ** numbers in the sqlite3.c source file. VDBE branch coverage testing -** only works with an amalagmation build. That's ok since a VDBE branch +** only works with an amalgamation build. That's ok since a VDBE branch ** coverage build designed for testing the test suite only. No application ** should ever ship with VDBE branch coverage measuring turned on. ** @@ -16595,7 +16677,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** // NULL option is not possible ** ** VdbeCoverageEqNe(v) // Previous OP_Jump is only interested -** // in distingishing equal and not-equal. +** // in distinguishing equal and not-equal. ** ** Every VDBE branch operation must be tagged with one of the macros above. ** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and @@ -16605,7 +16687,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** During testing, the test application will invoke ** sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE,...) to set a callback ** routine that is invoked as each bytecode branch is taken. The callback -** contains the sqlite3.c source line number ov the VdbeCoverage macro and +** contains the sqlite3.c source line number of the VdbeCoverage macro and ** flags to indicate whether or not the branch was taken. The test application ** is responsible for keeping track of this and reporting byte-code branches ** that are never taken. @@ -16944,7 +17026,7 @@ SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); /* ** Default synchronous levels. ** -** Note that (for historcal reasons) the PAGER_SYNCHRONOUS_* macros differ +** Note that (for historical reasons) the PAGER_SYNCHRONOUS_* macros differ ** from the SQLITE_DEFAULT_SYNCHRONOUS value by 1. ** ** PAGER_SYNCHRONOUS DEFAULT_SYNCHRONOUS @@ -16983,7 +17065,7 @@ struct Db { ** An instance of the following structure stores a database schema. ** ** Most Schema objects are associated with a Btree. The exception is -** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. +** the Schema for the TEMP database (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. ** @@ -17094,7 +17176,7 @@ struct Lookaside { LookasideSlot *pInit; /* List of buffers not previously used */ LookasideSlot *pFree; /* List of available buffers */ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE - LookasideSlot *pSmallInit; /* List of small buffers not prediously used */ + LookasideSlot *pSmallInit; /* List of small buffers not previously used */ LookasideSlot *pSmallFree; /* List of available small buffers */ void *pMiddle; /* First byte past end of full-size buffers and ** the first byte of LOOKASIDE_SMALL buffers */ @@ -17111,7 +17193,7 @@ struct LookasideSlot { #define EnableLookaside db->lookaside.bDisable--;\ db->lookaside.sz=db->lookaside.bDisable?0:db->lookaside.szTrue -/* Size of the smaller allocations in two-size lookside */ +/* Size of the smaller allocations in two-size lookaside */ #ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE # define LOOKASIDE_SMALL 0 #else @@ -17450,6 +17532,7 @@ struct sqlite3 { #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ #define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ +#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -17532,6 +17615,7 @@ struct FuncDestructor { ** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd ** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG ** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_BYTELEN == OPFLAG_BYTELENARG ** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API ** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API ** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! @@ -17539,7 +17623,7 @@ struct FuncDestructor { ** ** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the ** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is -** used internally and if set means tha the function has side effects. +** used internally and if set means that the function has side effects. ** SQLITE_INNOCUOUS is used by application code and means "not unsafe". ** See multiple instances of tag-20230109-1. */ @@ -17550,6 +17634,7 @@ struct FuncDestructor { #define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/ #define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */ #define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */ +#define SQLITE_FUNC_BYTELEN 0x00c0 /* Built-in octet_length() function */ #define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */ /* 0x0200 -- available for reuse */ #define SQLITE_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */ @@ -18129,7 +18214,7 @@ struct FKey { ** foreign key. ** ** The OE_Default value is a place holder that means to use whatever -** conflict resolution algorthm is required from context. +** conflict resolution algorithm is required from context. ** ** The following symbolic values are used to record which type ** of conflict resolution action to take. @@ -18543,7 +18628,7 @@ struct Expr { ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old ** EP_Unlikely: 134217728 times likelihood - ** TK_IN: ephemerial table holding RHS + ** TK_IN: ephemeral table holding RHS ** TK_SELECT_COLUMN: Number of columns on the LHS ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. @@ -18625,6 +18710,8 @@ struct Expr { */ #define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) #define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseWOfst(E) (((E)->flags&(EP_InnerON|EP_OuterON))==0) +#define ExprUseWJoin(E) (((E)->flags&(EP_InnerON|EP_OuterON))!=0) #define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) #define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) #define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) @@ -18813,7 +18900,7 @@ struct SrcItem { unsigned notCte :1; /* This item may not match a CTE */ unsigned isUsing :1; /* u3.pUsing is valid */ unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ - unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ @@ -19349,6 +19436,9 @@ struct Parse { int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ int nSelect; /* Number of SELECT stmts. Counter for Select.selId */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ +#endif #ifndef SQLITE_OMIT_SHARED_CACHE int nTableLock; /* Number of locks in aTableLock */ TableLock *aTableLock; /* Required table locks for shared-cache mode */ @@ -19362,12 +19452,9 @@ struct Parse { int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ Returning *pReturning; /* The RETURNING clause */ } u1; - u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ -#endif + LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ @@ -19491,6 +19578,7 @@ struct AuthContext { #define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */ #define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ #define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ +#define OPFLAG_BYTELENARG 0xc0 /* OP_Column only for octet_length() */ #define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ #define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ #define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ @@ -19633,6 +19721,25 @@ struct sqlite3_str { #define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) +/* +** The following object is the header for an "RCStr" or "reference-counted +** string". An RCStr is passed around and used like any other char* +** that has been dynamically allocated. The important interface +** differences: +** +** 1. RCStr strings are reference counted. They are deallocated +** when the reference count reaches zero. +** +** 2. Use sqlite3RCStrUnref() to free an RCStr string rather than +** sqlite3_free() +** +** 3. Make a (read-only) copy of a read-only RCStr string using +** sqlite3RCStrRef(). +*/ +struct RCStr { + u64 nRCRef; /* Number of references */ + /* Total structure size should be a multiple of 8 bytes for alignment */ +}; /* ** A pointer to this structure is used to communicate information @@ -19659,7 +19766,7 @@ typedef struct { /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning ** parameters are for temporary use during development, to help find -** optimial values for parameters in the query planner. The should not +** optimal values for parameters in the query planner. The should not ** be used on trunk check-ins. They are a temporary mechanism available ** for transient development builds only. ** @@ -19685,6 +19792,7 @@ struct Sqlite3Config { u8 bUseCis; /* Use covering indices for full-scans */ u8 bSmallMalloc; /* Avoid large memory allocations if true */ u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ + u8 bUseLongDouble; /* Make use of long double */ int mxStrlen; /* Maximum string length */ int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ @@ -19771,6 +19879,7 @@ struct Walker { void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ int walkerDepth; /* Number of subqueries */ u16 eCode; /* A small processing code */ + u16 mWFlags; /* Use-dependent flags */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ int n; /* A counter */ @@ -19810,6 +19919,7 @@ struct DbFixer { /* Forward declarations */ SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3WalkExprNN(Walker*, Expr*); SQLITE_PRIVATE int sqlite3WalkExprList(Walker*, ExprList*); SQLITE_PRIVATE int sqlite3WalkSelect(Walker*, Select*); SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker*, Select*); @@ -20191,6 +20301,20 @@ struct PrintfArguments { sqlite3_value **apArg; /* The argument values */ }; +/* +** An instance of this object receives the decoding of a floating point +** value into an approximate decimal representation. +*/ +struct FpDecode { + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ + int n; /* Significant digits in the decode */ + int iDP; /* Location of the decimal point */ + char *z; /* Start of significant digits */ + char zBuf[24]; /* Storage for significant digits */ +}; + +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode*,double,int,int); SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...); SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) @@ -20481,7 +20605,7 @@ SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int) SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int); SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); -SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int); +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int,int); SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); @@ -20630,6 +20754,7 @@ SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*); SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*); SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*); SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); + SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64); SQLITE_PRIVATE i64 sqlite3RealToI64(double); SQLITE_PRIVATE int sqlite3Int64ToText(i64,char*); @@ -20734,6 +20859,7 @@ SQLITE_PRIVATE void sqlite3FileSuffix3(const char*, char*); SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,u8); SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8); +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*)); SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8); SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); @@ -20841,6 +20967,11 @@ SQLITE_PRIVATE void sqlite3OomClear(sqlite3*); SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); +SQLITE_PRIVATE char *sqlite3RCStrRef(char*); +SQLITE_PRIVATE void sqlite3RCStrUnref(char*); +SQLITE_PRIVATE char *sqlite3RCStrNew(u64); +SQLITE_PRIVATE char *sqlite3RCStrResize(char*,u64); + SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum*, i64); SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*); @@ -21092,6 +21223,7 @@ SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int); #define sqlite3SelectExprHeight(x) 0 #define sqlite3ExprCheckHeight(x,y) #endif +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr*,int); SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*); SQLITE_PRIVATE void sqlite3Put4byte(u8*, u32); @@ -21377,9 +21509,6 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#ifdef SQLITE_64BIT_STATS - "64BIT_STATS", -#endif #ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN # if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), @@ -21716,6 +21845,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), #endif +#ifdef SQLITE_LEGACY_JSON_VALID + "LEGACY_JSON_VALID", +#endif #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS "LIKE_DOESNT_MATCH_BLOBS", #endif @@ -22350,6 +22482,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0, /* bSmallMalloc */ 1, /* bExtraSchemaChecks */ + sizeof(LONGDOUBLE_TYPE)>8, /* bUseLongDouble */ 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ @@ -22579,6 +22712,9 @@ typedef struct VdbeSorter VdbeSorter; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; +/* A cache of large TEXT or BLOB values in a VdbeCursor */ +typedef struct VdbeTxtBlbCache VdbeTxtBlbCache; + /* Types of VDBE cursors */ #define CURTYPE_BTREE 0 #define CURTYPE_SORTER 1 @@ -22610,6 +22746,7 @@ struct VdbeCursor { Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ + Bool colCache:1; /* pCache pointer is initialized and non-NULL */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ union { /* pBtx for isEphermeral. pAltMap otherwise */ Btree *pBtx; /* Separate file holding temporary table */ @@ -22650,6 +22787,7 @@ struct VdbeCursor { #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif + VdbeTxtBlbCache *pCache; /* Cache of large TEXT or BLOB values */ /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for @@ -22662,12 +22800,25 @@ struct VdbeCursor { #define IsNullCursor(P) \ ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) - /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. */ #define CACHE_STALE 0 +/* +** Large TEXT or BLOB values can be slow to load, so we want to avoid +** loading them more than once. For that reason, large TEXT and BLOB values +** can be stored in a cache defined by this object, and attached to the +** VdbeCursor using the pCache field. +*/ +struct VdbeTxtBlbCache { + char *pCValue; /* A RCStr buffer to hold the value */ + i64 iOffset; /* File offset of the row being cached */ + int iCol; /* Column for which the cache is valid */ + u32 cacheStatus; /* Vdbe.cacheCtr value */ + u32 colCacheCtr; /* Column cache counter */ +}; + /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as @@ -22988,16 +23139,18 @@ struct Vdbe { u32 nWrite; /* Number of write operations that have occurred */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResAlloc; /* Column slots allocated to aColName[] */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ - bft explain:2; /* True if EXPLAIN present on SQL command */ + bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ bft changeCntOn:1; /* True to update the change-counter */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ + bft haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ @@ -23044,7 +23197,7 @@ struct PreUpdate { i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ - Table *pTab; /* Schema object being upated */ + Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; @@ -23134,6 +23287,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem*,int); SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem*); #endif SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); @@ -23730,8 +23884,8 @@ struct DateTime { */ static int getDigits(const char *zDate, const char *zFormat, ...){ /* The aMx[] array translates the 3rd character of each format - ** spec into a max size: a b c d e f */ - static const u16 aMx[] = { 12, 14, 24, 31, 59, 9999 }; + ** spec into a max size: a b c d e f */ + static const u16 aMx[] = { 12, 14, 24, 31, 59, 14712 }; va_list ap; int cnt = 0; char nextC; @@ -24072,17 +24226,14 @@ static void computeYMD(DateTime *p){ ** Compute the Hour, Minute, and Seconds from the julian day number. */ static void computeHMS(DateTime *p){ - int s; + int day_ms, day_min; /* milliseconds, minutes into the day */ if( p->validHMS ) return; computeJD(p); - s = (int)((p->iJD + 43200000) % 86400000); - p->s = s/1000.0; - s = (int)p->s; - p->s -= s; - p->h = s/3600; - s -= p->h*3600; - p->m = s/60; - p->s += s - p->m*60; + day_ms = (int)((p->iJD + 43200000) % 86400000); + p->s = (day_ms % 60000)/1000.0; + day_min = day_ms/60000; + p->m = day_min % 60; + p->h = day_min / 60; p->rawS = 0; p->validHMS = 1; } @@ -24261,6 +24412,25 @@ static const struct { { 4, "year", 14713.0, 31536000.0 }, }; +/* +** If the DateTime p is raw number, try to figure out if it is +** a julian day number of a unix timestamp. Set the p value +** appropriately. +*/ +static void autoAdjustDate(DateTime *p){ + if( !p->rawS || p->validJD ){ + p->rawS = 0; + }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ + && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ + ){ + double r = p->s*1000.0 + 210866760000000.0; + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + } +} + /* ** Process a modifier to a date-time stamp. The modifiers are ** as follows: @@ -24304,19 +24474,8 @@ static int parseModifier( */ if( sqlite3_stricmp(z, "auto")==0 ){ if( idx>1 ) return 1; /* IMP: R-33611-57934 */ - if( !p->rawS || p->validJD ){ - rc = 0; - p->rawS = 0; - }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ - && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ - ){ - r = p->s*1000.0 + 210866760000000.0; - clearYMD_HMS_TZ(p); - p->iJD = (sqlite3_int64)(r + 0.5); - p->validJD = 1; - p->rawS = 0; - rc = 0; - } + autoAdjustDate(p); + rc = 0; } break; } @@ -24482,18 +24641,73 @@ static int parseModifier( case '9': { double rRounder; int i; - for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} + int Y,M,D,h,m,x; + const char *z2 = z; + char z0 = z[0]; + for(n=1; z[n]; n++){ + if( z[n]==':' ) break; + if( sqlite3Isspace(z[n]) ) break; + if( z[n]=='-' ){ + if( n==5 && getDigits(&z[1], "40f", &Y)==1 ) break; + if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; + } + } if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ - rc = 1; + assert( rc==1 ); break; } - if( z[n]==':' ){ + if( z[n]=='-' ){ + /* A modifier of the form (+|-)YYYY-MM-DD adds or subtracts the + ** specified number of years, months, and days. MM is limited to + ** the range 0-11 and DD is limited to 0-30. + */ + if( z0!='+' && z0!='-' ) break; /* Must start with +/- */ + if( n==5 ){ + if( getDigits(&z[1], "40f-20a-20d", &Y, &M, &D)!=3 ) break; + }else{ + assert( n==6 ); + if( getDigits(&z[1], "50f-20a-20d", &Y, &M, &D)!=3 ) break; + z++; + } + if( M>=12 ) break; /* M range 0..11 */ + if( D>=31 ) break; /* D range 0..30 */ + computeYMD_HMS(p); + p->validJD = 0; + if( z0=='-' ){ + p->Y -= Y; + p->M -= M; + D = -D; + }else{ + p->Y += Y; + p->M += M; + } + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + computeJD(p); + p->validHMS = 0; + p->validYMD = 0; + p->iJD += (i64)D*86400000; + if( z[11]==0 ){ + rc = 0; + break; + } + if( sqlite3Isspace(z[11]) + && getDigits(&z[12], "20c:20e", &h, &m)==2 + ){ + z2 = &z[12]; + n = 2; + }else{ + break; + } + } + if( z2[n]==':' ){ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the ** specified number of hours, minutes, seconds, and fractional seconds ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be ** omitted. */ - const char *z2 = z; + DateTime tx; sqlite3_int64 day; if( !sqlite3Isdigit(*z2) ) z2++; @@ -24503,7 +24717,7 @@ static int parseModifier( tx.iJD -= 43200000; day = tx.iJD/86400000; tx.iJD -= day*86400000; - if( z[0]=='-' ) tx.iJD = -tx.iJD; + if( z0=='-' ) tx.iJD = -tx.iJD; computeJD(p); clearYMD_HMS_TZ(p); p->iJD += tx.iJD; @@ -24519,7 +24733,7 @@ static int parseModifier( if( n>10 || n<3 ) break; if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); - rc = 1; + assert( rc==1 ); rRounder = r<0 ? -0.5 : +0.5; for(i=0; iM += (int)r; @@ -24687,7 +24900,7 @@ static void datetimeFunc( zBuf[16] = '0' + (x.m)%10; zBuf[17] = ':'; if( x.useSubsec ){ - s = (int)1000.0*x.s; + s = (int)(1000.0*x.s + 0.5); zBuf[18] = '0' + (s/10000)%10; zBuf[19] = '0' + (s/1000)%10; zBuf[20] = '.'; @@ -24734,7 +24947,7 @@ static void timeFunc( zBuf[4] = '0' + (x.m)%10; zBuf[5] = ':'; if( x.useSubsec ){ - s = (int)1000.0*x.s; + s = (int)(1000.0*x.s + 0.5); zBuf[6] = '0' + (s/10000)%10; zBuf[7] = '0' + (s/1000)%10; zBuf[8] = '.'; @@ -24805,7 +25018,7 @@ static void dateFunc( ** %M minute 00-59 ** %s seconds since 1970-01-01 ** %S seconds 00-59 -** %w day of week 0-6 sunday==0 +** %w day of week 0-6 Sunday==0 ** %W week of year 00-53 ** %Y year 0000-9999 ** %% % @@ -24945,6 +25158,117 @@ static void cdateFunc( dateFunc(context, 0, 0); } +/* +** timediff(DATE1, DATE2) +** +** Return the amount of time that must be added to DATE2 in order to +** convert it into DATE2. The time difference format is: +** +** +YYYY-MM-DD HH:MM:SS.SSS +** +** The initial "+" becomes "-" if DATE1 occurs before DATE2. For +** date/time values A and B, the following invariant should hold: +** +** datetime(A) == (datetime(B, timediff(A,B)) +** +** Both DATE arguments must be either a julian day number, or an +** ISO-8601 string. The unix timestamps are not supported by this +** routine. +*/ +static void timediffFunc( + sqlite3_context *context, + int NotUsed1, + sqlite3_value **argv +){ + char sign; + int Y, M; + DateTime d1, d2; + sqlite3_str sRes; + UNUSED_PARAMETER(NotUsed1); + if( isDate(context, 1, &argv[0], &d1) ) return; + if( isDate(context, 1, &argv[1], &d2) ) return; + computeYMD_HMS(&d1); + computeYMD_HMS(&d2); + if( d1.iJD>=d2.iJD ){ + sign = '+'; + Y = d1.Y - d2.Y; + if( Y ){ + d2.Y = d1.Y; + d2.validJD = 0; + computeJD(&d2); + } + M = d1.M - d2.M; + if( M<0 ){ + Y--; + M += 12; + } + if( M!=0 ){ + d2.M = d1.M; + d2.validJD = 0; + computeJD(&d2); + } + while( d1.iJDd2.iJD ){ + M--; + if( M<0 ){ + M = 11; + Y--; + } + d2.M++; + if( d2.M>12 ){ + d2.M = 1; + d2.Y++; + } + d2.validJD = 0; + computeJD(&d2); + } + d1.iJD = d2.iJD - d1.iJD; + d1.iJD += (u64)1486995408 * (u64)100000; + } + d1.validYMD = 0; + d1.validHMS = 0; + d1.validTZ = 0; + computeYMD_HMS(&d1); + sqlite3StrAccumInit(&sRes, 0, 0, 0, 100); + sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f", + sign, Y, M, d1.D-1, d1.h, d1.m, d1.s); + sqlite3ResultStrAccum(context, &sRes); +} + + /* ** current_timestamp() ** @@ -25019,6 +25343,7 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ PURE_DATE(time, -1, 0, 0, timeFunc ), PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), + PURE_DATE(timediff, 2, 0, 0, timediffFunc ), DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), DFUNCTION(current_date, 0, 0, 0, cdateFunc ), @@ -25172,7 +25497,7 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite ** is using a regular VFS, it is called after the corresponding ** transaction has been committed. Injecting a fault at this point - ** confuses the test scripts - the COMMIT comand returns SQLITE_NOMEM + ** confuses the test scripts - the COMMIT command returns SQLITE_NOMEM ** but the transaction is committed anyway. ** ** The core must call OsFileControl() though, not OsFileControlHint(), @@ -25793,7 +26118,7 @@ static void *sqlite3MemMalloc(int nByte){ ** or sqlite3MemRealloc(). ** ** For this low-level routine, we already know that pPrior!=0 since -** cases where pPrior==0 will have been intecepted and dealt with +** cases where pPrior==0 will have been intercepted and dealt with ** by higher-level routines. */ static void sqlite3MemFree(void *pPrior){ @@ -25881,7 +26206,7 @@ static int sqlite3MemInit(void *NotUsed){ return SQLITE_OK; } len = sizeof(cpuCount); - /* One usually wants to use hw.acctivecpu for MT decisions, but not here */ + /* One usually wants to use hw.activecpu for MT decisions, but not here */ sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0); if( cpuCount>1 ){ /* defer MT decisions to system malloc */ @@ -28348,7 +28673,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ /* ** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields -** are necessary under two condidtions: (1) Debug builds and (2) using +** are necessary under two conditions: (1) Debug builds and (2) using ** home-grown mutexes. Encapsulate these conditions into a single #define. */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HOMEGROWN_RECURSIVE_MUTEX) @@ -28849,7 +29174,7 @@ struct sqlite3_mutex { CRITICAL_SECTION mutex; /* Mutex controlling the lock */ int id; /* Mutex type */ #ifdef SQLITE_DEBUG - volatile int nRef; /* Number of enterances */ + volatile int nRef; /* Number of entrances */ volatile DWORD owner; /* Thread holding this mutex */ volatile LONG trace; /* True to trace changes */ #endif @@ -30221,57 +30546,6 @@ static const et_info fmtinfo[] = { ** %!S Like %S but prefer the zName over the zAlias */ -/* Floating point constants used for rounding */ -static const double arRound[] = { - 5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05, - 5.0e-06, 5.0e-07, 5.0e-08, 5.0e-09, 5.0e-10, -}; - -/* -** If SQLITE_OMIT_FLOATING_POINT is defined, then none of the floating point -** conversions will work. -*/ -#ifndef SQLITE_OMIT_FLOATING_POINT -/* -** "*val" is a double such that 0.1 <= *val < 10.0 -** Return the ascii code for the leading digit of *val, then -** multiply "*val" by 10.0 to renormalize. -** -** Example: -** input: *val = 3.14159 -** output: *val = 1.4159 function return = '3' -** -** The counter *cnt is incremented each time. After counter exceeds -** 16 (the number of significant digits in a 64-bit float) '0' is -** always returned. -*/ -static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ - int digit; - LONGDOUBLE_TYPE d; - if( (*cnt)<=0 ) return '0'; - (*cnt)--; - digit = (int)*val; - d = digit; - digit += '0'; - *val = (*val - d)*10.0; - return (char)digit; -} -#endif /* SQLITE_OMIT_FLOATING_POINT */ - -#ifndef SQLITE_OMIT_FLOATING_POINT -/* -** "*val" is a u64. *msd is a divisor used to extract the -** most significant digit of *val. Extract that most significant -** digit and return it. -*/ -static char et_getdigit_int(u64 *val, u64 *msd){ - u64 x = (*val)/(*msd); - *val -= x*(*msd); - if( *msd>=10 ) *msd /= 10; - return '0' + (char)(x & 15); -} -#endif /* SQLITE_OMIT_FLOATING_POINT */ - /* ** Set the StrAccum object to an error mode. */ @@ -30363,20 +30637,15 @@ SQLITE_API void sqlite3_str_vappendf( u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ - LONGDOUBLE_TYPE realvalue; /* Value for real types */ - sqlite_uint64 msd; /* Divisor to get most-significant-digit - ** of longvalue */ + double realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char *zOut; /* Rendering buffer */ int nOut; /* Size of the rendering buffer */ char *zExtra = 0; /* Malloced memory used by some conversion */ -#ifndef SQLITE_OMIT_FLOATING_POINT - int exp, e2; /* exponent of real numbers */ - int nsd; /* Number of significant digits returned */ - double rounder; /* Used for rounding floating point values */ + int exp, e2; /* exponent of real numbers */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ -#endif + PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ @@ -30651,94 +30920,61 @@ SQLITE_API void sqlite3_str_vappendf( break; case etFLOAT: case etEXP: - case etGENERIC: + case etGENERIC: { + FpDecode s; + int iRound; + int j; + if( bArgList ){ realvalue = getDoubleArg(pArgList); }else{ realvalue = va_arg(ap,double); } -#ifdef SQLITE_OMIT_FLOATING_POINT - length = 0; -#else if( precision<0 ) precision = 6; /* Set default precision */ #ifdef SQLITE_FP_PRECISION_LIMIT if( precision>SQLITE_FP_PRECISION_LIMIT ){ precision = SQLITE_FP_PRECISION_LIMIT; } #endif - if( realvalue<0.0 ){ - realvalue = -realvalue; + if( xtype==etFLOAT ){ + iRound = -precision; + }else if( xtype==etGENERIC ){ + iRound = precision; + }else{ + iRound = precision+1; + } + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); + if( s.isSpecial ){ + if( s.isSpecial==2 ){ + bufpt = flag_zeropad ? "null" : "NaN"; + length = sqlite3Strlen30(bufpt); + break; + }else if( flag_zeropad ){ + s.z[0] = '9'; + s.iDP = 1000; + s.n = 1; + }else{ + memcpy(buf, "-Inf", 5); + bufpt = buf; + if( s.sign=='-' ){ + /* no-op */ + }else if( flag_prefix ){ + buf[0] = flag_prefix; + }else{ + bufpt++; + } + length = sqlite3Strlen30(bufpt); + break; + } + } + if( s.sign=='-' ){ prefix = '-'; }else{ prefix = flag_prefix; } - exp = 0; - if( xtype==etGENERIC && precision>0 ) precision--; - testcase( precision>0xfff ); - if( realvalue<1.0e+16 - && realvalue==(LONGDOUBLE_TYPE)(longvalue = (u64)realvalue) - ){ - /* Number is a pure integer that can be represented as u64 */ - for(msd=1; msd*10<=longvalue; msd *= 10, exp++){} - if( exp>precision && xtype!=etFLOAT ){ - u64 rnd = msd/2; - int kk = precision; - while( kk-- > 0 ){ rnd /= 10; } - longvalue += rnd; - } - }else{ - msd = 0; - longvalue = 0; /* To prevent a compiler warning */ - idx = precision & 0xfff; - rounder = arRound[idx%10]; - while( idx>=10 ){ rounder *= 1.0e-10; idx -= 10; } - if( xtype==etFLOAT ){ - double rx = (double)realvalue; - sqlite3_uint64 u; - int ex; - memcpy(&u, &rx, sizeof(u)); - ex = -1023 + (int)((u>>52)&0x7ff); - if( precision+(ex/3) < 15 ) rounder += realvalue*3e-16; - realvalue += rounder; - } - if( sqlite3IsNaN((double)realvalue) ){ - if( flag_zeropad ){ - bufpt = "null"; - length = 4; - }else{ - bufpt = "NaN"; - length = 3; - } - break; - } - /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ - if( ALWAYS(realvalue>0.0) ){ - LONGDOUBLE_TYPE scale = 1.0; - while( realvalue>=1e100*scale && exp<=350){ scale*=1e100;exp+=100;} - while( realvalue>=1e10*scale && exp<=350 ){ scale*=1e10; exp+=10; } - while( realvalue>=10.0*scale && exp<=350 ){ scale *= 10.0; exp++; } - realvalue /= scale; - while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } - while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } - if( exp>350 ){ - if( flag_zeropad ){ - realvalue = 9.0; - exp = 999; - }else{ - bufpt = buf; - buf[0] = prefix; - memcpy(buf+(prefix!=0),"Inf",4); - length = 3+(prefix!=0); - break; - } - } - if( xtype!=etFLOAT ){ - realvalue += rounder; - if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } - } - } - } + exp = s.iDP-1; + if( xtype==etGENERIC && precision>0 ) precision--; /* ** If the field type is etGENERIC, then convert to either etEXP @@ -30758,9 +30994,8 @@ SQLITE_API void sqlite3_str_vappendf( if( xtype==etEXP ){ e2 = 0; }else{ - e2 = exp; + e2 = s.iDP - 1; } - nsd = 16 + flag_altform2*10; bufpt = buf; { i64 szBufNeeded; /* Size of a temporary buffer needed */ @@ -30778,16 +31013,12 @@ SQLITE_API void sqlite3_str_vappendf( *(bufpt++) = prefix; } /* Digits prior to the decimal point */ + j = 0; if( e2<0 ){ *(bufpt++) = '0'; - }else if( msd>0 ){ - for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit_int(&longvalue,&msd); - if( cThousand && (e2%3)==0 && e2>1 ) *(bufpt++) = ','; - } }else{ for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt++) = j1 ) *(bufpt++) = ','; } } @@ -30797,19 +31028,12 @@ SQLITE_API void sqlite3_str_vappendf( } /* "0" digits after the decimal point but before the first ** significant digit of the number */ - for(e2++; e2<0; precision--, e2++){ - assert( precision>0 ); + for(e2++; e2<0 && precision>0; precision--, e2++){ *(bufpt++) = '0'; } /* Significant digits after the decimal point */ - if( msd>0 ){ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit_int(&longvalue,&msd); - } - }else{ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); - } + while( (precision--)>0 ){ + *(bufpt++) = jcharset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; @@ -30858,8 +31083,8 @@ SQLITE_API void sqlite3_str_vappendf( while( nPad-- ) bufpt[i++] = '0'; length = width; } -#endif /* !defined(SQLITE_OMIT_FLOATING_POINT) */ break; + } case etSIZE: if( !bArgList ){ *(va_arg(ap,int*)) = pAccum->nChar; @@ -31583,6 +31808,75 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ va_end(ap); } + +/***************************************************************************** +** Reference counted string storage +*****************************************************************************/ + +/* +** Increase the reference count of the string by one. +** +** The input parameter is returned. +*/ +SQLITE_PRIVATE char *sqlite3RCStrRef(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + p->nRCRef++; + return z; +} + +/* +** Decrease the reference count by one. Free the string when the +** reference count reaches zero. +*/ +SQLITE_PRIVATE void sqlite3RCStrUnref(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + assert( p->nRCRef>0 ); + if( p->nRCRef>=2 ){ + p->nRCRef--; + }else{ + sqlite3_free(p); + } +} + +/* +** Create a new string that is capable of holding N bytes of text, not counting +** the zero byte at the end. The string is uninitialized. +** +** The reference count is initially 1. Call sqlite3RCStrUnref() to free the +** newly allocated string. +** +** This routine returns 0 on an OOM. +*/ +SQLITE_PRIVATE char *sqlite3RCStrNew(u64 N){ + RCStr *p = sqlite3_malloc64( N + sizeof(*p) + 1 ); + if( p==0 ) return 0; + p->nRCRef = 1; + return (char*)&p[1]; +} + +/* +** Change the size of the string so that it is able to hold N bytes. +** The string might be reallocated, so return the new allocation. +*/ +SQLITE_PRIVATE char *sqlite3RCStrResize(char *z, u64 N){ + RCStr *p = (RCStr*)z; + RCStr *pNew; + assert( p!=0 ); + p--; + assert( p->nRCRef==1 ); + pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1); + if( pNew==0 ){ + sqlite3_free(p); + return 0; + }else{ + return (char*)&pNew[1]; + } +} + /************** End of printf.c **********************************************/ /************** Begin file treeview.c ****************************************/ /* @@ -32230,7 +32524,8 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m }; assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); assert( pExpr->pRight ); - assert( sqlite3ExprSkipCollate(pExpr->pRight)->op==TK_TRUEFALSE ); + assert( sqlite3ExprSkipCollateAndLikely(pExpr->pRight)->op + == TK_TRUEFALSE ); x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); zUniOp = azOp[x]; break; @@ -33889,7 +34184,7 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ /* ** Calls to sqlite3FaultSim() are used to simulate a failure during testing, ** or to bypass normal error detection during testing in order to let -** execute proceed futher downstream. +** execute proceed further downstream. ** ** In deployment, sqlite3FaultSim() *always* return SQLITE_OK (0). The ** sqlite3FaultSim() function only returns non-zero during testing. @@ -34006,6 +34301,23 @@ SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3 *db){ */ SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){ if( rc==SQLITE_IOERR_NOMEM ) return; +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_IOERR_IN_PAGE ){ + int ii; + int iErr; + sqlite3BtreeEnterAll(db); + for(ii=0; iinDb; ii++){ + if( db->aDb[ii].pBt ){ + iErr = sqlite3PagerWalSystemErrno(sqlite3BtreePager(db->aDb[ii].pBt)); + if( iErr ){ + db->iSysErrno = iErr; + } + } + } + sqlite3BtreeLeaveAll(db); + return; + } +#endif rc &= 0xff; if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ db->iSysErrno = sqlite3OsGetLastError(db->pVfs); @@ -34251,43 +34563,40 @@ SQLITE_PRIVATE u8 sqlite3StrIHash(const char *z){ return h; } -/* -** Compute 10 to the E-th power. Examples: E==1 results in 10. -** E==2 results in 100. E==50 results in 1.0e50. +/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) ** -** This routine only works for values of E between 1 and 341. +** Reference: +** T. J. Dekker, "A Floating-Point Technique for Extending the +** Available Precision". 1971-07-26. */ -static LONGDOUBLE_TYPE sqlite3Pow10(int E){ -#if defined(_MSC_VER) - static const LONGDOUBLE_TYPE x[] = { - 1.0e+001L, - 1.0e+002L, - 1.0e+004L, - 1.0e+008L, - 1.0e+016L, - 1.0e+032L, - 1.0e+064L, - 1.0e+128L, - 1.0e+256L - }; - LONGDOUBLE_TYPE r = 1.0; - int i; - assert( E>=0 && E<=307 ); - for(i=0; E!=0; i++, E >>=1){ - if( E & 1 ) r *= x[i]; - } - return r; -#else - LONGDOUBLE_TYPE x = 10.0; - LONGDOUBLE_TYPE r = 1.0; - while(1){ - if( E & 1 ) r *= x; - E >>= 1; - if( E==0 ) break; - x *= x; - } - return r; -#endif +static void dekkerMul2(volatile double *x, double y, double yy){ + /* + ** The "volatile" keywords on parameter x[] and on local variables + ** below are needed force intermediate results to be truncated to + ** binary64 rather than be carried around in an extended-precision + ** format. The truncation is necessary for the Dekker algorithm to + ** work. Intel x86 floating point might omit the truncation without + ** the use of volatile. + */ + volatile double tx, ty, p, q, c, cc; + double hx, hy; + u64 m; + memcpy(&m, (void*)&x[0], 8); + m &= 0xfffffffffc000000LL; + memcpy(&hx, &m, 8); + tx = x[0] - hx; + memcpy(&m, &y, 8); + m &= 0xfffffffffc000000LL; + memcpy(&hy, &m, 8); + ty = y - hy; + p = hx*hy; + q = hx*ty + tx*hy; + c = p+q; + cc = p - c + q + tx*ty; + cc = x[0]*yy + x[1]*y + cc; + x[0] = c + cc; + x[1] = c - x[0]; + x[1] += cc; } /* @@ -34328,12 +34637,11 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en const char *zEnd; /* sign * significand * (10 ^ (esign * exponent)) */ int sign = 1; /* sign of significand */ - i64 s = 0; /* significand */ + u64 s = 0; /* significand */ int d = 0; /* adjust exponent for shifting decimal point */ int esign = 1; /* sign of exponent */ int e = 0; /* exponent */ int eValid = 1; /* True exponent is either not used or is well-formed */ - double result; int nDigit = 0; /* Number of digits processed */ int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ @@ -34373,7 +34681,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en while( z=((LARGEST_INT64-9)/10) ){ + if( s>=((LARGEST_UINT64-9)/10) ){ /* skip non-significant significand digits ** (increase exponent by d to shift decimal left) */ while( z0 && s<(LARGEST_UINT64/10) ){ + s *= 10; + e--; + } + while( e<0 && (s%10)==0 ){ + s /= 10; + e++; } - if( s==0 ) { - /* In the IEEE 754 standard, zero is signed. */ - result = sign<0 ? -(double)0 : (double)0; - } else { - /* Attempt to reduce exponent. - ** - ** Branches that are not required for the correct answer but which only - ** help to obtain the correct answer faster are marked with special - ** comments, as a hint to the mutation tester. - */ - while( e>0 ){ /*OPTIMIZATION-IF-TRUE*/ - if( esign>0 ){ - if( s>=(LARGEST_INT64/10) ) break; /*OPTIMIZATION-IF-FALSE*/ - s *= 10; - }else{ - if( s%10!=0 ) break; /*OPTIMIZATION-IF-FALSE*/ - s /= 10; - } - e--; - } - - /* adjust the sign of significand */ - s = sign<0 ? -s : s; - - if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/ - result = (double)s; + if( e==0 ){ + *pResult = s; + }else if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE r = (LONGDOUBLE_TYPE)s; + if( e>0 ){ + while( e>=100 ){ e-=100; r *= 1.0e+100L; } + while( e>=10 ){ e-=10; r *= 1.0e+10L; } + while( e>=1 ){ e-=1; r *= 1.0e+01L; } }else{ - /* attempt to handle extremely small/large numbers better */ - if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/ - if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e-308); - if( esign<0 ){ - result = s / scale; - result /= 1.0e+308; - }else{ - result = s * scale; - result *= 1.0e+308; - } - }else{ assert( e>=342 ); - if( esign<0 ){ - result = 0.0*s; - }else{ + while( e<=-100 ){ e+=100; r *= 1.0e-100L; } + while( e<=-10 ){ e+=10; r *= 1.0e-10L; } + while( e<=-1 ){ e+=1; r *= 1.0e-01L; } + } + assert( r>=0.0 ); + if( r>+1.7976931348623157081452742373e+308L ){ #ifdef INFINITY - result = INFINITY*s; + *pResult = +INFINITY; #else - result = 1e308*1e308*s; /* Infinity */ + *pResult = 1.0e308*10.0; #endif - } - } - }else{ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e); - if( esign<0 ){ - result = s / scale; - }else{ - result = s * scale; - } + }else{ + *pResult = (double)r; + } + }else{ + double rr[2]; + u64 s2; + rr[0] = (double)s; + s2 = (u64)rr[0]; + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); + if( e>0 ){ + while( e>=100 ){ + e -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( e>=10 ){ + e -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( e>=1 ){ + e -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + }else{ + while( e<=-100 ){ + e += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( e<=-10 ){ + e += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( e<=-1 ){ + e += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); } } + *pResult = rr[0]+rr[1]; + if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; } + if( sign<0 ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); - /* store the result */ - *pResult = result; - - /* return true if number and no extra non-whitespace chracters after */ +atof_return: + /* return true if number and no extra non-whitespace characters after */ if( z==zEnd && nDigit>0 && eValid && eType>0 ){ return eType; }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ @@ -34636,7 +34954,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc /* This test and assignment is needed only to suppress UB warnings ** from clang and -fsanitize=undefined. This test and assignment make ** the code a little larger and slower, and no harm comes from omitting - ** them, but we must appaise the undefined-behavior pharisees. */ + ** them, but we must appease the undefined-behavior pharisees. */ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; }else if( neg ){ *pNum = -(i64)u; @@ -34714,7 +35032,9 @@ SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ }else #endif /* SQLITE_OMIT_HEX_INTEGER */ { - return sqlite3Atoi64(z, pOut, sqlite3Strlen30(z), SQLITE_UTF8); + int n = (int)(0x3fffffff&strspn(z,"+- \n\t0123456789")); + if( z[n] ) n++; + return sqlite3Atoi64(z, pOut, n, SQLITE_UTF8); } } @@ -34793,6 +35113,153 @@ SQLITE_PRIVATE int sqlite3Atoi(const char *z){ return x; } +/* +** Decode a floating-point value into an approximate decimal +** representation. +** +** Round the decimal representation to n significant digits if +** n is positive. Or round to -n signficant digits after the +** decimal point if n is negative. No rounding is performed if +** n is zero. +** +** The significant digits of the decimal representation are +** stored in p->z[] which is a often (but not always) a pointer +** into the middle of p->zBuf[]. There are p->n significant digits. +** The p->z[] array is *not* zero-terminated. +*/ +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ + int i; + u64 v; + int e, exp = 0; + p->isSpecial = 0; + p->z = p->zBuf; + + /* Convert negative numbers to positive. Deal with Infinity, 0.0, and + ** NaN. */ + if( r<0.0 ){ + p->sign = '-'; + r = -r; + }else if( r==0.0 ){ + p->sign = '+'; + p->n = 1; + p->iDP = 1; + p->z = "0"; + return; + }else{ + p->sign = '+'; + } + memcpy(&v,&r,8); + e = v>>52; + if( (e&0x7ff)==0x7ff ){ + p->isSpecial = 1 + (v!=0x7ff0000000000000LL); + p->n = 0; + p->iDP = 0; + return; + } + + /* Multiply r by powers of ten until it lands somewhere in between + ** 1.0e+19 and 1.0e+17. + */ + if( sqlite3Config.bUseLongDouble ){ + LONGDOUBLE_TYPE rr = r; + if( rr>=1.0e+19 ){ + while( rr>=1.0e+119L ){ exp+=100; rr *= 1.0e-100L; } + while( rr>=1.0e+29L ){ exp+=10; rr *= 1.0e-10L; } + while( rr>=1.0e+19L ){ exp++; rr *= 1.0e-1L; } + }else{ + while( rr<1.0e-97L ){ exp-=100; rr *= 1.0e+100L; } + while( rr<1.0e+07L ){ exp-=10; rr *= 1.0e+10L; } + while( rr<1.0e+17L ){ exp--; rr *= 1.0e+1L; } + } + v = (u64)rr; + }else{ + /* If high-precision floating point is not available using "long double", + ** then use Dekker-style double-double computation to increase the + ** precision. + ** + ** The error terms on constants like 1.0e+100 computed using the + ** decimal extension, for example as follows: + ** + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); + */ + double rr[2]; + rr[0] = r; + rr[1] = 0.0; + if( rr[0]>1.84e+19 ){ + while( rr[0]>1.84e+119 ){ + exp += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( rr[0]>1.84e+29 ){ + exp += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( rr[0]>1.84e+19 ){ + exp += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } + }else{ + while( rr[0]<1.84e-82 ){ + exp -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( rr[0]<1.84e+08 ){ + exp -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( rr[0]<1.84e+18 ){ + exp -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } + } + v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; + } + + + /* Extract significant digits. */ + i = sizeof(p->zBuf)-1; + assert( v>0 ); + while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } + assert( i>=0 && izBuf)-1 ); + p->n = sizeof(p->zBuf) - 1 - i; + assert( p->n>0 ); + assert( p->nzBuf) ); + p->iDP = p->n + exp; + if( iRound<0 ){ + iRound = p->iDP - iRound; + if( iRound==0 && p->zBuf[i+1]>='5' ){ + iRound = 1; + p->zBuf[i--] = '0'; + p->n++; + p->iDP++; + } + } + if( iRound>0 && (iRoundn || p->n>mxRound) ){ + char *z = &p->zBuf[i+1]; + if( iRound>mxRound ) iRound = mxRound; + p->n = iRound; + if( z[iRound]>='5' ){ + int j = iRound-1; + while( 1 /*exit-by-break*/ ){ + z[j]++; + if( z[j]<='9' ) break; + z[j] = '0'; + if( j==0 ){ + p->z[i--] = '1'; + p->n++; + p->iDP++; + break; + }else{ + j--; + } + } + } + } + p->z = &p->zBuf[i+1]; + assert( i+p->n < sizeof(p->zBuf) ); + while( ALWAYS(p->n>0) && p->z[p->n-1]=='0' ){ p->n--; } +} + /* ** Try to convert z into an unsigned 32-bit integer. Return true on ** success and false if there is an error. @@ -35321,7 +35788,7 @@ SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ } /* -** Attempt to add, substract, or multiply the 64-bit signed value iB against +** Attempt to add, subtract, or multiply the 64-bit signed value iB against ** the other 64-bit signed integer at *pA and store the result in *pA. ** Return 0 on success. Or if the operation would have resulted in an ** overflow, leave *pA unchanged and return 1. @@ -35634,7 +36101,7 @@ SQLITE_PRIVATE int sqlite3VListNameToNum(VList *pIn, const char *zName, int nNam #define SQLITE_HWTIME_H /* -** The following routine only works on pentium-class (or newer) processors. +** The following routine only works on Pentium-class (or newer) processors. ** It uses the RDTSC opcode to read the cycle count value out of the ** processor and returns that value. This can be used for high-res ** profiling. @@ -35806,7 +36273,7 @@ static void insertElement( } -/* Resize the hash table so that it cantains "new_size" buckets. +/* Resize the hash table so that it contains "new_size" buckets. ** ** The hash table might fail to resize if sqlite3_malloc() fails or ** if the new size is the same as the prior size. @@ -37192,7 +37659,7 @@ SQLITE_PRIVATE int sqlite3KvvfsInit(void){ ** This source file is organized into divisions where the logic for various ** subfunctions is contained within the appropriate division. PLEASE ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed -** in the correct division and should be clearly labeled. +** in the correct division and should be clearly labelled. ** ** The layout of divisions is as follows: ** @@ -37779,7 +38246,7 @@ static int robustFchown(int fd, uid_t uid, gid_t gid){ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "unix" VFSes. Return SQLITE_OK opon successfully updating the +** "unix" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -38301,7 +38768,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** If you close a file descriptor that points to a file that has locks, ** all locks on that file that are owned by the current process are ** released. To work around this problem, each unixInodeInfo object -** maintains a count of the number of pending locks on tha inode. +** maintains a count of the number of pending locks on the inode. ** When an attempt is made to close an unixFile, if there are ** other unixFile open on the same inode that are holding locks, the call ** to close() the file descriptor is deferred until all of the locks clear. @@ -38315,7 +38782,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** not posix compliant. Under LinuxThreads, a lock created by thread ** A cannot be modified or overridden by a different thread B. ** Only thread A can modify the lock. Locking behavior is correct -** if the appliation uses the newer Native Posix Thread Library (NPTL) +** if the application uses the newer Native Posix Thread Library (NPTL) ** on linux - with NPTL a lock created by thread A can override locks ** in thread B. But there is no way to know at compile-time which ** threading library is being used. So there is no way to know at @@ -38517,7 +38984,7 @@ static void storeLastErrno(unixFile *pFile, int error){ } /* -** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. +** Close all file descriptors accumulated in the unixInodeInfo->pUnused list. */ static void closePendingFds(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; @@ -38880,7 +39347,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** slightly in order to be compatible with Windows95 systems simultaneously ** accessing the same database file, in case that is ever required. ** - ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved + ** Symbols defined in os.h identify the 'pending byte' and the 'reserved ** byte', each single bytes at well known offsets, and the 'shared byte ** range', a range of 510 bytes at a well known offset. ** @@ -38888,7 +39355,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** byte'. If this is successful, 'shared byte range' is read-locked ** and the lock on the 'pending byte' released. (Legacy note: When ** SQLite was first developed, Windows95 systems were still very common, - ** and Widnows95 lacks a shared-lock capability. So on Windows95, a + ** and Windows95 lacks a shared-lock capability. So on Windows95, a ** single randomly selected by from the 'shared byte range' is locked. ** Windows95 is now pretty much extinct, but this work-around for the ** lack of shared-locks on Windows95 lives on, for backwards @@ -38909,7 +39376,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** obtaining a write-lock on the 'pending byte'. This ensures that no new ** SHARED locks can be obtained, but existing SHARED locks are allowed to ** persist. If the call to this function fails to obtain the EXCLUSIVE - ** lock in this case, it holds the PENDING lock intead. The client may + ** lock in this case, it holds the PENDING lock instead. The client may ** then re-attempt the EXCLUSIVE lock later on, after existing SHARED ** locks have cleared. */ @@ -38937,7 +39404,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct. ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); @@ -40155,7 +40622,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); @@ -40271,7 +40738,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){ if( !(failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST + pInode->sharedByte, 1, 0)) ){ int failed2 = SQLITE_OK; - /* now attemmpt to get the exclusive lock range */ + /* now attempt to get the exclusive lock range */ failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 1); if( failed && (failed2 = afpSetLock(context->dbPath, pFile, @@ -40566,7 +41033,7 @@ static int unixRead( #endif #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -40718,7 +41185,7 @@ static int unixWrite( #endif #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -40840,7 +41307,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a ** no-op. But go ahead and call fstat() to validate the file ** descriptor as we need a method to provoke a failure during - ** coverate testing. + ** coverage testing. */ #ifdef SQLITE_NO_SYNC { @@ -43885,12 +44352,17 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ -#if OS_VXWORKS || _POSIX_C_SOURCE >= 199309L +#if !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP+0 struct timespec sp; - sp.tv_sec = microseconds / 1000000; sp.tv_nsec = (microseconds % 1000000) * 1000; + + /* Almost all modern unix systems support nanosleep(). But if you are + ** compiling for one of the rare exceptions, you can use + ** -DHAVE_NANOSLEEP=0 (perhaps in conjuction with -DHAVE_USLEEP if + ** usleep() is available) in order to bypass the use of nanosleep() */ nanosleep(&sp, NULL); + UNUSED_PARAMETER(NotUsed); return microseconds; #elif defined(HAVE_USLEEP) && HAVE_USLEEP @@ -46480,7 +46952,7 @@ static struct win_syscall { /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "win32" VFSes. Return SQLITE_OK opon successfully updating the +** "win32" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -48060,7 +48532,7 @@ static int winRead( pFile->h, pBuf, amt, offset, pFile->locktype)); #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -48138,7 +48610,7 @@ static int winWrite( pFile->h, pBuf, amt, offset, pFile->locktype)); #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -48248,7 +48720,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ ** all references to memory-mapped content are closed. That is doable, ** but involves adding a few branches in the common write code path which ** could slow down normal operations slightly. Hence, we have decided for - ** now to simply make trancations a no-op if there are pending reads. We + ** now to simply make transactions a no-op if there are pending reads. We ** can maybe revisit this decision in the future. */ return SQLITE_OK; @@ -48307,7 +48779,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ #ifdef SQLITE_TEST /* ** Count the number of fullsyncs and normal syncs. This is used to test -** that syncs and fullsyncs are occuring at the right times. +** that syncs and fullsyncs are occurring at the right times. */ SQLITE_API int sqlite3_sync_count = 0; SQLITE_API int sqlite3_fullsync_count = 0; @@ -48664,7 +49136,7 @@ static int winLock(sqlite3_file *id, int locktype){ */ if( locktype==EXCLUSIVE_LOCK && res ){ assert( pFile->locktype>=SHARED_LOCK ); - res = winUnlockReadLock(pFile); + (void)winUnlockReadLock(pFile); res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res ){ @@ -50068,6 +50540,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; size_t i, j; + DWORD pid; int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX); int nMax, nBuf, nDir, nLen; char *zBuf; @@ -50280,7 +50753,10 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ j = sqlite3Strlen30(zBuf); sqlite3_randomness(15, &zBuf[j]); + pid = osGetCurrentProcessId(); for(i=0; i<15; i++, j++){ + zBuf[j] += pid & 0xff; + pid >>= 8; zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; @@ -52645,7 +53121,7 @@ SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){ h = BITVEC_HASH(i++); /* if there wasn't a hash collision, and this doesn't */ /* completely fill the hash, then just add it without */ - /* worring about sub-dividing and re-hashing. */ + /* worrying about sub-dividing and re-hashing. */ if( !p->u.aHash[h] ){ if (p->nSet<(BITVEC_NINT-1)) { goto bitvec_set_end; @@ -52978,7 +53454,7 @@ struct PCache { ** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. ** This routine runs inside of assert() statements only. */ -#ifdef SQLITE_DEBUG +#if defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ PgHdr *p; for(p=pCache->pDirty; p; p=p->pDirtyNext){ @@ -52986,6 +53462,16 @@ static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ } return 0; } +static int pageNotOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 0; + } + return 1; +} +#else +# define pageOnDirtyList(A,B) 1 +# define pageNotOnDirtyList(A,B) 1 #endif /* @@ -53006,7 +53492,7 @@ SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){ assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ - assert( !pageOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirty list */ + assert( pageNotOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirtylist */ }else{ assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); @@ -53142,7 +53628,7 @@ static int numberOfCachePages(PCache *p){ return p->szCache; }else{ i64 n; - /* IMPLEMANTATION-OF: R-59858-46238 If the argument N is negative, then the + /* IMPLEMENTATION-OF: R-59858-46238 If the argument N is negative, then the ** number of cache pages is adjusted to be a number of pages that would ** use approximately abs(N*1024) bytes of memory based on the current ** page size. */ @@ -53630,7 +54116,7 @@ static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){ } /* -** Sort the list of pages in accending order by pgno. Pages are +** Sort the list of pages in ascending order by pgno. Pages are ** connected by pDirty pointers. The pDirtyPrev pointers are ** corrupted by this sort. ** @@ -53870,7 +54356,7 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd ** If N is positive, then N pages worth of memory are allocated using a single ** sqlite3Malloc() call and that memory is used for the first N pages allocated. ** Or if N is negative, then -1024*N bytes of memory are allocated and used -** for as many pages as can be accomodated. +** for as many pages as can be accommodated. ** ** Only one of (2) or (3) can be used. Once the memory available to (2) or ** (3) is exhausted, subsequent allocations fail over to the general-purpose @@ -53904,7 +54390,7 @@ typedef struct PGroup PGroup; ** in memory directly after the associated page data, if the database is ** corrupt, code at the b-tree layer may overread the page buffer and ** read part of this structure before the corruption is detected. This -** can cause a valgrind error if the unitialized gap is accessed. Using u16 +** can cause a valgrind error if the uninitialized gap is accessed. Using u16 ** ensures there is no such gap, and therefore no bytes of uninitialized ** memory in the structure. ** @@ -55124,7 +55610,7 @@ SQLITE_PRIVATE void sqlite3PcacheStats( ** The TEST primitive includes a "batch" number. The TEST primitive ** will only see elements that were inserted before the last change ** in the batch number. In other words, if an INSERT occurs between -** two TESTs where the TESTs have the same batch nubmer, then the +** two TESTs where the TESTs have the same batch number, then the ** value added by the INSERT will not be visible to the second TEST. ** The initial batch number is zero, so if the very first TEST contains ** a non-zero batch number, it will see all prior INSERTs. @@ -55656,6 +56142,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 # define sqlite3WalFramesize(z) 0 # define sqlite3WalFindFrame(x,y,z) 0 # define sqlite3WalFile(x) 0 +# undef SQLITE_USE_SEH #else #define WAL_SAVEPOINT_NDATA 4 @@ -55762,6 +56249,10 @@ SQLITE_PRIVATE int sqlite3WalWriteLock(Wal *pWal, int bLock); SQLITE_PRIVATE void sqlite3WalDb(Wal *pWal, sqlite3 *db); #endif +#ifdef SQLITE_USE_SEH +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal*); +#endif + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* SQLITE_WAL_H */ @@ -56047,7 +56538,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** outstanding transactions have been abandoned, the pager is able to ** transition back to OPEN state, discarding the contents of the ** page-cache and any other in-memory state at the same time. Everything -** is reloaded from disk (and, if necessary, hot-journal rollback peformed) +** is reloaded from disk (and, if necessary, hot-journal rollback performed) ** when a read-transaction is next opened on the pager (transitioning ** the pager into READER state). At that point the system has recovered ** from the error. @@ -57420,7 +57911,7 @@ static int readJournalHdr( ** + 4 bytes: super-journal name checksum. ** + 8 bytes: aJournalMagic[]. ** -** The super-journal page checksum is the sum of the bytes in thesuper-journal +** The super-journal page checksum is the sum of the bytes in the super-journal ** name, where each byte is interpreted as a signed 8-bit integer. ** ** If zSuper is a NULL pointer (occurs for a single database transaction), @@ -57473,7 +57964,7 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){ } pPager->journalOff += (nSuper+20); - /* If the pager is in peristent-journal mode, then the physical + /* If the pager is in persistent-journal mode, then the physical ** journal-file may extend past the end of the super-journal name ** and 8 bytes of magic data just written to the file. This is ** dangerous because the code to rollback a hot-journal file @@ -57643,7 +58134,7 @@ static void pager_unlock(Pager *pPager){ /* ** This function is called whenever an IOERR or FULL error that requires -** the pager to transition into the ERROR state may ahve occurred. +** the pager to transition into the ERROR state may have occurred. ** The first argument is a pointer to the pager structure, the second ** the error-code about to be returned by a pager API function. The ** value returned is a copy of the second argument to this function. @@ -57918,7 +58409,7 @@ static void pagerUnlockAndRollback(Pager *pPager){ /* ** Parameter aData must point to a buffer of pPager->pageSize bytes -** of data. Compute and return a checksum based ont the contents of the +** of data. Compute and return a checksum based on the contents of the ** page of data and the current value of pPager->cksumInit. ** ** This is not a real checksum. It is really just the sum of the @@ -58884,7 +59375,7 @@ static int pagerWalFrames( assert( pPager->pWal ); assert( pList ); #ifdef SQLITE_DEBUG - /* Verify that the page list is in accending order */ + /* Verify that the page list is in ascending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -59015,7 +59506,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ #ifndef SQLITE_OMIT_WAL /* ** Check if the *-wal file that corresponds to the database opened by pPager -** exists if the database is not empy, or verify that the *-wal file does +** exists if the database is not empty, or verify that the *-wal file does ** not exist (by deleting it) if the database file is empty. ** ** If the database is not empty and the *-wal file exists, open the pager @@ -60425,11 +60916,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int rc = SQLITE_OK; /* Return code */ int tempFile = 0; /* True for temp files (incl. in-memory files) */ int memDb = 0; /* True if this is an in-memory file */ -#ifndef SQLITE_OMIT_DESERIALIZE int memJM = 0; /* Memory journal mode */ -#else -# define memJM 0 -#endif int readOnly = 0; /* True if this is a read-only file */ int journalFileSize; /* Bytes to allocate for each journal fd */ char *zPathname = 0; /* Full path to database file */ @@ -60548,12 +61035,13 @@ SQLITE_PRIVATE int sqlite3PagerOpen( ** specific formatting and order of the various filenames, so if the format ** changes here, be sure to change it there as well. */ + assert( SQLITE_PTRSIZE==sizeof(Pager*) ); pPtr = (u8 *)sqlite3MallocZero( ROUND8(sizeof(*pPager)) + /* Pager structure */ ROUND8(pcacheSize) + /* PCache object */ ROUND8(pVfs->szOsFile) + /* The main db file */ journalFileSize * 2 + /* The two journal files */ - sizeof(pPager) + /* Space to hold a pointer */ + SQLITE_PTRSIZE + /* Space to hold a pointer */ 4 + /* Database prefix */ nPathname + 1 + /* database filename */ nUriByte + /* query parameters */ @@ -60574,7 +61062,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( pPager->sjfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; pPager->jfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) ); - memcpy(pPtr, &pPager, sizeof(pPager)); pPtr += sizeof(pPager); + memcpy(pPtr, &pPager, SQLITE_PTRSIZE); pPtr += SQLITE_PTRSIZE; /* Fill in the Pager.zFilename and pPager.zQueryParam fields */ pPtr += 4; /* Skip zero prefix */ @@ -60628,9 +61116,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); -#ifndef SQLITE_OMIT_DESERIALIZE pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; -#endif readOnly = (fout&SQLITE_OPEN_READONLY)!=0; /* If the file was successfully opened for read/write access, @@ -60767,7 +61253,7 @@ act_like_temp_file: /* ** Return the sqlite3_file for the main database given the name -** of the corresonding WAL or Journal name as passed into +** of the corresponding WAL or Journal name as passed into ** xOpen. */ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char *zName){ @@ -63052,7 +63538,7 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ assert( pPager->eState!=PAGER_ERROR ); pPager->journalMode = (u8)eMode; - /* When transistioning from TRUNCATE or PERSIST to any other journal + /* When transitioning from TRUNCATE or PERSIST to any other journal ** mode except WAL, unless the pager is in locking_mode=exclusive mode, ** delete the journal file. */ @@ -63480,6 +63966,12 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ } #endif +#ifdef SQLITE_USE_SEH +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager *pPager){ + return sqlite3WalSystemErrno(pPager->pWal); +} +#endif + #endif /* SQLITE_OMIT_DISKIO */ /************** End of pager.c ***********************************************/ @@ -63770,7 +64262,7 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0; ** ** Technically, the various VFSes are free to implement these locks however ** they see fit. However, compatibility is encouraged so that VFSes can -** interoperate. The standard implemention used on both unix and windows +** interoperate. The standard implementation used on both unix and windows ** is for the index number to indicate a byte offset into the ** WalCkptInfo.aLock[] array in the wal-index header. In other words, all ** locks are on the shm file. The WALINDEX_LOCK_OFFSET constant (which @@ -63846,7 +64338,7 @@ struct WalIndexHdr { ** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff) ** for any aReadMark[] means that entry is unused. aReadMark[0] is ** a special case; its value is never used and it exists as a place-holder -** to avoid having to offset aReadMark[] indexs by one. Readers holding +** to avoid having to offset aReadMark[] indexes by one. Readers holding ** WAL_READ_LOCK(0) always ignore the entire WAL and read all content ** directly from the database. ** @@ -64014,7 +64506,15 @@ struct Wal { u32 iReCksum; /* On commit, recalculate checksums from here */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ +#ifdef SQLITE_USE_SEH + u32 lockMask; /* Mask of locks held */ + void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ + int iSysErrno; /* System error code following exception */ +#endif #ifdef SQLITE_DEBUG + int nSehTry; /* Number of nested SEH_TRY{} blocks */ u8 lockError; /* True if a locking error has occurred */ #endif #ifdef SQLITE_ENABLE_SNAPSHOT @@ -64096,6 +64596,113 @@ struct WalIterator { sizeof(ht_slot)*HASHTABLE_NSLOT + HASHTABLE_NPAGE*sizeof(u32) \ ) +/* +** Structured Exception Handling (SEH) is a Windows-specific technique +** for catching exceptions raised while accessing memory-mapped files. +** +** The -DSQLITE_USE_SEH compile-time option means to use SEH to catch and +** deal with system-level errors that arise during WAL -shm file processing. +** Without this compile-time option, any system-level faults that appear +** while accessing the memory-mapped -shm file will cause a process-wide +** signal to be deliver, which will more than likely cause the entire +** process to exit. +*/ +#ifdef SQLITE_USE_SEH +#include + +/* Beginning of a block of code in which an exception might occur */ +# define SEH_TRY __try { \ + assert( walAssertLockmask(pWal) && pWal->nSehTry==0 ); \ + VVA_ONLY(pWal->nSehTry++); + +/* The end of a block of code in which an exception might occur */ +# define SEH_EXCEPT(X) \ + VVA_ONLY(pWal->nSehTry--); \ + assert( pWal->nSehTry==0 ); \ + } __except( sehExceptionFilter(pWal, GetExceptionCode(), GetExceptionInformation() ) ){ X } + +/* Simulate a memory-mapping fault in the -shm file for testing purposes */ +# define SEH_INJECT_FAULT sehInjectFault(pWal) + +/* +** The second argument is the return value of GetExceptionCode() for the +** current exception. Return EXCEPTION_EXECUTE_HANDLER if the exception code +** indicates that the exception may have been caused by accessing the *-shm +** file mapping. Or EXCEPTION_CONTINUE_SEARCH otherwise. +*/ +static int sehExceptionFilter(Wal *pWal, int eCode, EXCEPTION_POINTERS *p){ + VVA_ONLY(pWal->nSehTry--); + if( eCode==EXCEPTION_IN_PAGE_ERROR ){ + if( p && p->ExceptionRecord && p->ExceptionRecord->NumberParameters>=3 ){ + /* From MSDN: For this type of exception, the first element of the + ** ExceptionInformation[] array is a read-write flag - 0 if the exception + ** was thrown while reading, 1 if while writing. The second element is + ** the virtual address being accessed. The "third array element specifies + ** the underlying NTSTATUS code that resulted in the exception". */ + pWal->iSysErrno = (int)p->ExceptionRecord->ExceptionInformation[2]; + } + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +/* +** If one is configured, invoke the xTestCallback callback with 650 as +** the argument. If it returns true, throw the same exception that is +** thrown by the system if the *-shm file mapping is accessed after it +** has been invalidated. +*/ +static void sehInjectFault(Wal *pWal){ + int res; + assert( pWal->nSehTry>0 ); + + res = sqlite3FaultSim(650); + if( res!=0 ){ + ULONG_PTR aArg[3]; + aArg[0] = 0; + aArg[1] = 0; + aArg[2] = (ULONG_PTR)res; + RaiseException(EXCEPTION_IN_PAGE_ERROR, 0, 3, (const ULONG_PTR*)aArg); + } +} + +/* +** There are two ways to use this macro. To set a pointer to be freed +** if an exception is thrown: +** +** SEH_FREE_ON_ERROR(0, pPtr); +** +** and to cancel the same: +** +** SEH_FREE_ON_ERROR(pPtr, 0); +** +** In the first case, there must not already be a pointer registered to +** be freed. In the second case, pPtr must be the registered pointer. +*/ +#define SEH_FREE_ON_ERROR(X,Y) \ + assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y + +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + +#else +# define SEH_TRY VVA_ONLY(pWal->nSehTry++); +# define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); +# define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); +# define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) +#endif /* ifdef SQLITE_USE_SEH */ + + /* ** Obtain a pointer to the iPage'th page of the wal-index. The wal-index ** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are @@ -64168,6 +64775,7 @@ static int walIndexPage( int iPage, /* The page we seek */ volatile u32 **ppPage /* Write the page pointer here */ ){ + SEH_INJECT_FAULT; if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ return walIndexPageRealloc(pWal, iPage, ppPage); } @@ -64179,6 +64787,7 @@ static int walIndexPage( */ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalCkptInfo*)&(pWal->apWiData[0][sizeof(WalIndexHdr)/2]); } @@ -64187,6 +64796,7 @@ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ */ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalIndexHdr*)pWal->apWiData[0]; } @@ -64376,7 +64986,7 @@ static int walDecodeFrame( return 0; } - /* A frame is only valid if the page number is creater than zero. + /* A frame is only valid if the page number is greater than zero. */ pgno = sqlite3Get4byte(&aFrame[0]); if( pgno==0 ){ @@ -64384,7 +64994,7 @@ static int walDecodeFrame( } /* A frame is only valid if a checksum of the WAL header, - ** all prior frams, the first 16 bytes of this frame-header, + ** all prior frames, the first 16 bytes of this frame-header, ** and the frame-data matches the checksum in the last 8 ** bytes of this frame-header. */ @@ -64444,12 +65054,18 @@ static int walLockShared(Wal *pWal, int lockIdx){ WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal, walLockName(lockIdx), rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ) pWal->lockMask |= (1 << lockIdx); +#endif return rc; } static void walUnlockShared(Wal *pWal, int lockIdx){ if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(1 << lockIdx); +#endif WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } static int walLockExclusive(Wal *pWal, int lockIdx, int n){ @@ -64460,12 +65076,20 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){ WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ){ + pWal->lockMask |= (((1<exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(((1<apWiData[0][WALINDEX_HDR_SIZE/sizeof(u32) + iFrame - 1]; } @@ -64816,6 +65441,7 @@ static int walIndexRecover(Wal *pWal){ /* Malloc a buffer to read frames into. */ szFrame = szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame + WALINDEX_PGSZ); + SEH_FREE_ON_ERROR(0, aFrame); if( !aFrame ){ rc = SQLITE_NOMEM_BKPT; goto recovery_error; @@ -64834,6 +65460,7 @@ static int walIndexRecover(Wal *pWal){ rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); assert( aShare!=0 || rc!=SQLITE_OK ); if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); pWal->apWiData[iPg] = aPrivate; for(iFrame=iFirst; iFrame<=iLast; iFrame++){ @@ -64861,6 +65488,7 @@ static int walIndexRecover(Wal *pWal){ } } pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0,0); nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); nHdr32 = nHdr / sizeof(u32); #ifndef SQLITE_SAFER_WALINDEX_RECOVERY @@ -64891,9 +65519,11 @@ static int walIndexRecover(Wal *pWal){ } } #endif + SEH_INJECT_FAULT; if( iFrame<=iLast ) break; } + SEH_FREE_ON_ERROR(aFrame, 0); sqlite3_free(aFrame); } @@ -64921,6 +65551,7 @@ finished: }else{ pInfo->aReadMark[i] = READMARK_NOT_USED; } + SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc!=SQLITE_BUSY ){ goto recovery_error; @@ -65078,7 +65709,7 @@ SQLITE_PRIVATE int sqlite3WalOpen( } /* -** Change the size to which the WAL file is trucated on each reset. +** Change the size to which the WAL file is truncated on each reset. */ SQLITE_PRIVATE void sqlite3WalLimit(Wal *pWal, i64 iLimit){ if( pWal ) pWal->mxWalSize = iLimit; @@ -65304,23 +65935,16 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + iLast*sizeof(ht_slot); - p = (WalIterator *)sqlite3_malloc64(nByte); + p = (WalIterator *)sqlite3_malloc64(nByte + + sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) + ); if( !p ){ return SQLITE_NOMEM_BKPT; } memset(p, 0, nByte); p->nSegment = nSegment; - - /* Allocate temporary space used by the merge-sort routine. This block - ** of memory will be freed before this function returns. - */ - aTmp = (ht_slot *)sqlite3_malloc64( - sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) - ); - if( !aTmp ){ - rc = SQLITE_NOMEM_BKPT; - } - + aTmp = (ht_slot*)&(((u8*)p)[nByte]); + SEH_FREE_ON_ERROR(0, p); for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && iaSegment[i].aPgno = (u32 *)sLoc.aPgno; } } - sqlite3_free(aTmp); - if( rc!=SQLITE_OK ){ + SEH_FREE_ON_ERROR(p, 0); walIteratorFree(p); p = 0; } @@ -65576,13 +66199,13 @@ static int walCheckpoint( mxSafeFrame = pWal->hdr.mxFrame; mxPage = pWal->hdr.nPage; for(i=1; iaReadMark+i); + u32 y = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxSafeFrame>y ){ assert( y<=pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); if( rc==SQLITE_OK ){ u32 iMark = (i==1 ? mxSafeFrame : READMARK_NOT_USED); - AtomicStore(pInfo->aReadMark+i, iMark); + AtomicStore(pInfo->aReadMark+i, iMark); SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc==SQLITE_BUSY ){ mxSafeFrame = y; @@ -65603,8 +66226,7 @@ static int walCheckpoint( && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK ){ u32 nBackfill = pInfo->nBackfill; - - pInfo->nBackfillAttempted = mxSafeFrame; + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; /* Sync the WAL to disk */ rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); @@ -65635,6 +66257,7 @@ static int walCheckpoint( while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ i64 iOffset; assert( walFramePgno(pWal, iFrame)==iDbpage ); + SEH_INJECT_FAULT; if( AtomicLoad(&db->u1.isInterrupted) ){ rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; break; @@ -65664,7 +66287,7 @@ static int walCheckpoint( } } if( rc==SQLITE_OK ){ - AtomicStore(&pInfo->nBackfill, mxSafeFrame); + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; } } @@ -65686,6 +66309,7 @@ static int walCheckpoint( */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); + SEH_INJECT_FAULT; if( pInfo->nBackfillhdr.mxFrame ){ rc = SQLITE_BUSY; }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ @@ -65717,6 +66341,7 @@ static int walCheckpoint( } walcheckpoint_out: + SEH_FREE_ON_ERROR(pIter, 0); walIteratorFree(pIter); return rc; } @@ -65739,6 +66364,93 @@ static void walLimitSize(Wal *pWal, i64 nMax){ } } +#ifdef SQLITE_USE_SEH +/* +** This is the "standard" exception handler used in a few places to handle +** an exception thrown by reading from the *-shm mapping after it has become +** invalid in SQLITE_USE_SEH builds. It is used as follows: +** +** SEH_TRY { ... } +** SEH_EXCEPT( rc = walHandleException(pWal); ) +** +** This function does three things: +** +** 1) Determines the locks that should be held, based on the contents of +** the Wal.readLock, Wal.writeLock and Wal.ckptLock variables. All other +** held locks are assumed to be transient locks that would have been +** released had the exception not been thrown and are dropped. +** +** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). +** +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. +*/ +static int walHandleException(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<lockMask & ~( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) + ); + for(ii=0; iipFree); + pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } + return SQLITE_IOERR_IN_PAGE; +} + +/* +** Assert that the Wal.lockMask mask, which indicates the locks held +** by the connenction, is consistent with the Wal.readLock, Wal.writeLock +** and Wal.ckptLock variables. To be used as: +** +** assert( walAssertLockmask(pWal) ); +*/ +static int walAssertLockmask(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) +#ifdef SQLITE_ENABLE_SNAPSHOT + | (pWal->pSnapshot ? (pWal->lockMask & (1 << WAL_CKPT_LOCK)) : 0) +#endif + ); + assert( mExpect==pWal->lockMask ); + } + return 1; +} + +/* +** Return and zero the "system error" field set when an +** EXCEPTION_IN_PAGE_ERROR exception is caught. +*/ +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal *pWal){ + int iRet = 0; + if( pWal ){ + iRet = pWal->iSysErrno; + pWal->iSysErrno = 0; + } + return iRet; +} + +#else +# define walAssertLockmask(x) 1 +#endif /* ifdef SQLITE_USE_SEH */ + /* ** Close a connection to a log file. */ @@ -65753,6 +66465,8 @@ SQLITE_PRIVATE int sqlite3WalClose( if( pWal ){ int isDelete = 0; /* True to unlink wal and wal-index files */ + assert( walAssertLockmask(pWal) ); + /* If an EXCLUSIVE lock can be obtained on the database file (using the ** ordinary, rollback-mode locking methods, this guarantees that the ** connection associated with this log file is the only connection to @@ -65777,7 +66491,7 @@ SQLITE_PRIVATE int sqlite3WalClose( ); if( bPersist!=1 ){ /* Try to delete the WAL file if the checkpoint completed and - ** fsyned (rc==SQLITE_OK) and if we are not in persistent-wal + ** fsynced (rc==SQLITE_OK) and if we are not in persistent-wal ** mode (!bPersist) */ isDelete = 1; }else if( pWal->mxWalSize>=0 ){ @@ -65844,7 +66558,7 @@ static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ ** give false-positive warnings about these accesses because the tools do not ** account for the double-read and the memory barrier. The use of mutexes ** here would be problematic as the memory being accessed is potentially - ** shared among multiple processes and not all mutex implementions work + ** shared among multiple processes and not all mutex implementations work ** reliably in that environment. */ aHdr = walIndexHdr(pWal); @@ -66295,6 +67009,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ assert( pWal->nWiData>0 ); assert( pWal->apWiData[0]!=0 ); pInfo = walCkptInfo(pWal); + SEH_INJECT_FAULT; if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame #ifdef SQLITE_ENABLE_SNAPSHOT && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) @@ -66344,7 +67059,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } #endif for(i=1; iaReadMark+i); + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxReadMark<=thisMark && thisMark<=mxFrame ){ assert( thisMark!=READMARK_NOT_USED ); mxReadMark = thisMark; @@ -66410,7 +67125,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** we can guarantee that the checkpointer that set nBackfill could not ** see any pages past pWal->hdr.mxFrame, this problem does not come up. */ - pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; walShmBarrier(pWal); if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) @@ -66425,6 +67140,54 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } #ifdef SQLITE_ENABLE_SNAPSHOT +/* +** This function does the work of sqlite3WalSnapshotRecover(). +*/ +static int walSnapshotRecover( + Wal *pWal, /* WAL handle */ + void *pBuf1, /* Temp buffer pWal->szPage bytes in size */ + void *pBuf2 /* Temp buffer pWal->szPage bytes in size */ +){ + int szPage = (int)pWal->szPage; + int rc; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ + WalHashLoc sLoc; /* Hash table location */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &sLoc); + if( rc!=SQLITE_OK ) break; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + return rc; +} + /* ** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted ** variable so that older snapshots can be accessed. To do this, loop @@ -66450,50 +67213,21 @@ SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ assert( pWal->readLock>=0 ); rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc==SQLITE_OK ){ - volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - int szPage = (int)pWal->szPage; - i64 szDb; /* Size of db file in bytes */ - - rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); - if( rc==SQLITE_OK ){ - void *pBuf1 = sqlite3_malloc(szPage); - void *pBuf2 = sqlite3_malloc(szPage); - if( pBuf1==0 || pBuf2==0 ){ - rc = SQLITE_NOMEM; - }else{ - u32 i = pInfo->nBackfillAttempted; - for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ - WalHashLoc sLoc; /* Hash table location */ - u32 pgno; /* Page number in db file */ - i64 iDbOff; /* Offset of db file entry */ - i64 iWalOff; /* Offset of wal file entry */ - - rc = walHashGet(pWal, walFramePage(i), &sLoc); - if( rc!=SQLITE_OK ) break; - assert( i - sLoc.iZero - 1 >=0 ); - pgno = sLoc.aPgno[i-sLoc.iZero-1]; - iDbOff = (i64)(pgno-1) * szPage; - - if( iDbOff+szPage<=szDb ){ - iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; - rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); - - if( rc==SQLITE_OK ){ - rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); - } - - if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ - break; - } - } - - pInfo->nBackfillAttempted = i-1; - } + void *pBuf1 = sqlite3_malloc(pWal->szPage); + void *pBuf2 = sqlite3_malloc(pWal->szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + pWal->ckptLock = 1; + SEH_TRY { + rc = walSnapshotRecover(pWal, pBuf1, pBuf2); } - - sqlite3_free(pBuf1); - sqlite3_free(pBuf2); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->ckptLock = 0; } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); } @@ -66502,28 +67236,20 @@ SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ #endif /* SQLITE_ENABLE_SNAPSHOT */ /* -** Begin a read transaction on the database. -** -** This routine used to be called sqlite3OpenSnapshot() and with good reason: -** it takes a snapshot of the state of the WAL and wal-index for the current -** instant in time. The current thread will continue to use this snapshot. -** Other threads might append new content to the WAL and wal-index but -** that extra content is ignored by the current thread. -** -** If the database contents have changes since the previous read -** transaction, then *pChanged is set to 1 before returning. The -** Pager layer will use this to know that its cache is stale and -** needs to be flushed. +** This function does the work of sqlite3WalBeginReadTransaction() (see +** below). That function simply calls this one inside an SEH_TRY{...} block. */ -SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ +static int walBeginReadTransaction(Wal *pWal, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ #ifdef SQLITE_ENABLE_SNAPSHOT + int ckptLock = 0; int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; #endif assert( pWal->ckptLock==0 ); + assert( pWal->nSehTry>0 ); #ifdef SQLITE_ENABLE_SNAPSHOT if( pSnapshot ){ @@ -66546,7 +67272,7 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ if( rc!=SQLITE_OK ){ return rc; } - pWal->ckptLock = 1; + ckptLock = 1; } #endif @@ -66610,15 +67336,37 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ } /* Release the shared CKPT lock obtained above. */ - if( pWal->ckptLock ){ + if( ckptLock ){ assert( pSnapshot ); walUnlockShared(pWal, WAL_CKPT_LOCK); - pWal->ckptLock = 0; } #endif return rc; } +/* +** Begin a read transaction on the database. +** +** This routine used to be called sqlite3OpenSnapshot() and with good reason: +** it takes a snapshot of the state of the WAL and wal-index for the current +** instant in time. The current thread will continue to use this snapshot. +** Other threads might append new content to the WAL and wal-index but +** that extra content is ignored by the current thread. +** +** If the database contents have changes since the previous read +** transaction, then *pChanged is set to 1 before returning. The +** Pager layer will use this to know that its cache is stale and +** needs to be flushed. +*/ +SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; + SEH_TRY { + rc = walBeginReadTransaction(pWal, pChanged); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** Finish with a read transaction. All this does is release the ** read-lock. @@ -66639,7 +67387,7 @@ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){ ** Return SQLITE_OK if successful, or an error code if an error occurs. If an ** error does occur, the final value of *piRead is undefined. */ -SQLITE_PRIVATE int sqlite3WalFindFrame( +static int walFindFrame( Wal *pWal, /* WAL handle */ Pgno pgno, /* Database page number to read data for */ u32 *piRead /* OUT: Frame number (or zero) */ @@ -66702,6 +67450,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( } nCollide = HASHTABLE_NSLOT; iKey = walHash(pgno); + SEH_INJECT_FAULT; while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ u32 iFrame = iH + sLoc.iZero; if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ @@ -66738,6 +67487,30 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( return SQLITE_OK; } +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +** +** The difference between this function and walFindFrame() is that this +** function wraps walFindFrame() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + int rc; + SEH_TRY { + rc = walFindFrame(pWal, pgno, piRead); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + return rc; +} + /* ** Read the contents of frame iRead from the wal file into buffer pOut ** (which is nOut bytes in size). Return SQLITE_OK if successful, or an @@ -66819,12 +67592,17 @@ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ ** time the read transaction on this connection was started, then ** the write is disallowed. */ - if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + SEH_TRY { + if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + + if( rc!=SQLITE_OK ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; - rc = SQLITE_BUSY_SNAPSHOT; } - return rc; } @@ -66860,30 +67638,33 @@ SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *p Pgno iMax = pWal->hdr.mxFrame; Pgno iFrame; - /* Restore the clients cache of the wal-index header to the state it - ** was in before the client began writing to the database. - */ - memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); - - for(iFrame=pWal->hdr.mxFrame+1; - ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; - iFrame++ - ){ - /* This call cannot fail. Unless the page for which the page number - ** is passed as the second argument is (a) in the cache and - ** (b) has an outstanding reference, then xUndo is either a no-op - ** (if (a) is false) or simply expels the page from the cache (if (b) - ** is false). - ** - ** If the upper layer is doing a rollback, it is guaranteed that there - ** are no outstanding references to any page other than page 1. And - ** page 1 is never written to the log until the transaction is - ** committed. As a result, the call to xUndo may not fail. + SEH_TRY { + /* Restore the clients cache of the wal-index header to the state it + ** was in before the client began writing to the database. */ - assert( walFramePgno(pWal, iFrame)!=1 ); - rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + for(iFrame=pWal->hdr.mxFrame+1; + ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; + iFrame++ + ){ + /* This call cannot fail. Unless the page for which the page number + ** is passed as the second argument is (a) in the cache and + ** (b) has an outstanding reference, then xUndo is either a no-op + ** (if (a) is false) or simply expels the page from the cache (if (b) + ** is false). + ** + ** If the upper layer is doing a rollback, it is guaranteed that there + ** are no outstanding references to any page other than page 1. And + ** page 1 is never written to the log until the transaction is + ** committed. As a result, the call to xUndo may not fail. + */ + assert( walFramePgno(pWal, iFrame)!=1 ); + rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + } + if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } - if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; } @@ -66927,7 +67708,10 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ pWal->hdr.mxFrame = aWalData[0]; pWal->hdr.aFrameCksum[0] = aWalData[1]; pWal->hdr.aFrameCksum[1] = aWalData[2]; - walCleanupHash(pWal); + SEH_TRY { + walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; @@ -67108,7 +67892,7 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){ ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). */ -SQLITE_PRIVATE int sqlite3WalFrames( +static int walFrames( Wal *pWal, /* Wal handle to write to */ int szPage, /* Database page-size in bytes */ PgHdr *pList, /* List of dirty pages to write */ @@ -67219,7 +68003,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( ** checksums must be recomputed when the transaction is committed. */ if( iFirst && (p->pDirty || isCommit==0) ){ u32 iWrite = 0; - VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite); + VVA_ONLY(rc =) walFindFrame(pWal, p->pgno, &iWrite); assert( rc==SQLITE_OK || iWrite==0 ); if( iWrite>=iFirst ){ i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; @@ -67338,6 +68122,29 @@ SQLITE_PRIVATE int sqlite3WalFrames( return rc; } +/* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +** +** The difference between this function and walFrames() is that this +** function wraps walFrames() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit, /* True if this is a commit */ + int sync_flags /* Flags to pass to OsSync() (or 0) */ +){ + int rc; + SEH_TRY { + rc = walFrames(pWal, szPage, pList, nTruncate, isCommit, sync_flags); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** This routine is called to implement sqlite3_wal_checkpoint() and ** related interfaces. @@ -67417,30 +68224,33 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( /* Read the wal-index header. */ - if( rc==SQLITE_OK ){ - walDisableBlocking(pWal); - rc = walIndexReadHdr(pWal, &isChanged); - (void)walEnableBlocking(pWal); - if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ - sqlite3OsUnfetch(pWal->pDbFd, 0, 0); - } - } - - /* Copy data from the log to the database file. */ - if( rc==SQLITE_OK ){ - - if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ - rc = SQLITE_CORRUPT_BKPT; - }else{ - rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); - } - - /* If no error occurred, set the output variables. */ - if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ - if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; - if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + SEH_TRY { + if( rc==SQLITE_OK ){ + walDisableBlocking(pWal); + rc = walIndexReadHdr(pWal, &isChanged); + (void)walEnableBlocking(pWal); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } + } + + /* Copy data from the log to the database file. */ + if( rc==SQLITE_OK ){ + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); + } + + /* If no error occurred, set the output variables. */ + if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ + if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; + SEH_INJECT_FAULT; + if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) if( isChanged ){ /* If a new wal-index header was loaded before the checkpoint was @@ -67517,7 +68327,9 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){ ** locks are taken in this case). Nor should the pager attempt to ** upgrade to exclusive-mode following such an error. */ +#ifndef SQLITE_USE_SEH assert( pWal->readLock>=0 || pWal->lockError ); +#endif assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); if( op==0 ){ @@ -67618,16 +68430,19 @@ SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ */ SQLITE_PRIVATE int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){ int rc; - rc = walLockShared(pWal, WAL_CKPT_LOCK); - if( rc==SQLITE_OK ){ - WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; - if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) - || pNew->mxFramenBackfillAttempted - ){ - rc = SQLITE_ERROR_SNAPSHOT; - walUnlockShared(pWal, WAL_CKPT_LOCK); + SEH_TRY { + rc = walLockShared(pWal, WAL_CKPT_LOCK); + if( rc==SQLITE_OK ){ + WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; + if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + || pNew->mxFramenBackfillAttempted + ){ + rc = SQLITE_ERROR_SNAPSHOT; + walUnlockShared(pWal, WAL_CKPT_LOCK); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) return rc; } @@ -67866,7 +68681,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ ** 0x81 0x00 becomes 0x00000080 ** 0x82 0x00 becomes 0x00000100 ** 0x80 0x7f becomes 0x0000007f -** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 +** 0x81 0x91 0xd1 0xac 0x78 becomes 0x12345678 ** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 ** ** Variable length integers are used for rowids and to hold the number of @@ -67949,7 +68764,7 @@ typedef struct CellInfo CellInfo; ** page that has been loaded into memory. The information in this object ** is derived from the raw on-disk page content. ** -** As each database page is loaded into memory, the pager allocats an +** As each database page is loaded into memory, the pager allocates an ** instance of this object and zeros the first 8 bytes. (This is the ** "extra" information associated with each page of the pager.) ** @@ -68405,7 +69220,7 @@ struct IntegrityCk { /* ** get2byteAligned(), unlike get2byte(), requires that its argument point to a -** two-byte aligned address. get2bytea() is only used for accessing the +** two-byte aligned address. get2byteAligned() is only used for accessing the ** cell addresses in a btree header. */ #if SQLITE_BYTEORDER==4321 @@ -68582,7 +69397,7 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){ ** ** There is a corresponding leave-all procedures. ** -** Enter the mutexes in accending order by BtShared pointer address +** Enter the mutexes in ascending order by BtShared pointer address ** to avoid the possibility of deadlock when two threads with ** two or more btrees in common both try to lock all their btrees ** at the same instant. @@ -68714,6 +69529,7 @@ SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){ /************** End of btmutex.c *********************************************/ /************** Begin file btree.c *******************************************/ + /* ** 2004 April 6 ** @@ -70249,7 +71065,7 @@ static void ptrmapPutOvflPtr(MemPage *pPage, MemPage *pSrc, u8 *pCell,int *pRC){ pPage->xParseCell(pPage, pCell, &info); if( info.nLocalaDataEnd, pCell, pCell+info.nLocal) ){ + if( SQLITE_OVERFLOW(pSrc->aDataEnd, pCell, pCell+info.nLocal) ){ testcase( pSrc!=pPage ); *pRC = SQLITE_CORRUPT_BKPT; return; @@ -70350,7 +71166,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ iCellStart = get2byte(&data[hdr+5]); if( nCell>0 ){ temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart); + memcpy(temp, data, usableSize); src = temp; for(i=0; iiPage. -** -** The page is fetched as read-write unless pCur is not NULL and is -** a read-only cursor. -** -** If an error occurs, then *ppPage is undefined. It -** may remain unchanged, or it may be set to an invalid value. */ static int getAndInitPage( BtShared *pBt, /* The database file */ Pgno pgno, /* Number of the page to get */ MemPage **ppPage, /* Write the page pointer here */ - BtCursor *pCur, /* Cursor to receive the page, or NULL */ int bReadOnly /* True for a read-only page */ ){ int rc; DbPage *pDbPage; + MemPage *pPage; assert( sqlite3_mutex_held(pBt->mutex) ); - assert( pCur==0 || ppPage==&pCur->pPage ); - assert( pCur==0 || bReadOnly==pCur->curPagerFlags ); - assert( pCur==0 || pCur->iPage>0 ); if( pgno>btreePagecount(pBt) ){ - rc = SQLITE_CORRUPT_BKPT; - goto getAndInitPage_error1; + *ppPage = 0; + return SQLITE_CORRUPT_BKPT; } rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); if( rc ){ - goto getAndInitPage_error1; + *ppPage = 0; + return rc; } - *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); - if( (*ppPage)->isInit==0 ){ + pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); + if( pPage->isInit==0 ){ btreePageFromDbPage(pDbPage, pgno, pBt); - rc = btreeInitPage(*ppPage); + rc = btreeInitPage(pPage); if( rc!=SQLITE_OK ){ - goto getAndInitPage_error2; + releasePage(pPage); + *ppPage = 0; + return rc; } } - assert( (*ppPage)->pgno==pgno || CORRUPT_DB ); - assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) ); - - /* If obtaining a child page for a cursor, we must verify that the page is - ** compatible with the root page. */ - if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ - rc = SQLITE_CORRUPT_PGNO(pgno); - goto getAndInitPage_error2; - } + assert( pPage->pgno==pgno || CORRUPT_DB ); + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); + *ppPage = pPage; return SQLITE_OK; - -getAndInitPage_error2: - releasePage(*ppPage); -getAndInitPage_error1: - if( pCur ){ - pCur->iPage--; - pCur->pPage = pCur->apPage[pCur->iPage]; - } - testcase( pgno==0 ); - assert( pgno!=0 || rc!=SQLITE_OK ); - return rc; } /* @@ -71177,7 +71966,7 @@ static void pageReinit(DbPage *pData){ ** call to btreeInitPage() will likely return SQLITE_CORRUPT. ** But no harm is done by this. And it is very important that ** btreeInitPage() be called on every btree page so we make - ** the call for every page that comes in for re-initing. */ + ** the call for every page that comes in for re-initializing. */ btreeInitPage(pPage); } } @@ -71356,6 +72145,9 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( assert( sizeof(u16)==2 ); assert( sizeof(Pgno)==4 ); + /* Suppress false-positive compiler warning from PVS-Studio */ + memset(&zDbHeader[16], 0, 8); + pBt = sqlite3MallocZero( sizeof(*pBt) ); if( pBt==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -71572,7 +72364,7 @@ static SQLITE_NOINLINE int allocateTempSpace(BtShared *pBt){ ** can mean that fillInCell() only initializes the first 2 or 3 ** bytes of pTmpSpace, but that the first 4 bytes are copied from ** it into a database page. This is not actually a problem, but it - ** does cause a valgrind error when the 1 or 2 bytes of unitialized + ** does cause a valgrind error when the 1 or 2 bytes of uninitialized ** data is passed to system call write(). So to avoid this error, ** zero the first 4 bytes of temp space here. ** @@ -71807,7 +72599,7 @@ SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ /* ** Return the number of bytes of space at the end of every page that -** are intentually left unused. This is the "reserved" space that is +** are intentionally left unused. This is the "reserved" space that is ** sometimes used by extensions. ** ** The value returned is the larger of the current reserve size and @@ -72054,7 +72846,6 @@ static int lockBtree(BtShared *pBt){ ){ goto page1_init_failed; } - pBt->btsFlags |= BTS_PAGESIZE_FIXED; assert( (pageSize & 7)==0 ); /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte ** integer at offset 20 is the number of bytes of space at the end of @@ -72074,6 +72865,7 @@ static int lockBtree(BtShared *pBt){ releasePageOne(pPage1); pBt->usableSize = usableSize; pBt->pageSize = pageSize; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; freeTempSpace(pBt); rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, pageSize-usableSize); @@ -72093,6 +72885,7 @@ static int lockBtree(BtShared *pBt){ if( usableSize<480 ){ goto page1_init_failed; } + pBt->btsFlags |= BTS_PAGESIZE_FIXED; pBt->pageSize = pageSize; pBt->usableSize = usableSize; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -72271,7 +73064,11 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ ** when A already has a read lock, we encourage A to give up and let B ** proceed. */ -SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ +static SQLITE_NOINLINE int btreeBeginTrans( + Btree *p, /* The btree in which to start the transaction */ + int wrflag, /* True to start a write transaction */ + int *pSchemaVersion /* Put schema version number here, if not NULL */ +){ BtShared *pBt = p->pBt; Pager *pPager = pBt->pPager; int rc = SQLITE_OK; @@ -72443,6 +73240,28 @@ trans_begun: sqlite3BtreeLeave(p); return rc; } +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ + BtShared *pBt; + if( p->sharable + || p->inTrans==TRANS_NONE + || (p->inTrans==TRANS_READ && wrflag!=0) + ){ + return btreeBeginTrans(p,wrflag,pSchemaVersion); + } + pBt = p->pBt; + if( pSchemaVersion ){ + *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]); + } + if( wrflag ){ + /* This call makes sure that the pager has the correct number of + ** open savepoints. If the second parameter is greater than 0 and + ** the sub-journal is not already open, then it will be opened here. + */ + return sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + }else{ + return SQLITE_OK; + } +} #ifndef SQLITE_OMIT_AUTOVACUUM @@ -73538,7 +74357,6 @@ SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor *pCur){ pCur->curFlags &= ~BTCF_Pinned; } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC /* ** Return the offset into the database file for the start of the ** payload to which the cursor is pointing. @@ -73550,7 +74368,6 @@ SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor *pCur){ return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + (i64)(pCur->info.pPayload - pCur->pPage->aData); } -#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* ** Return the number of bytes of payload for the entry that pCur is @@ -73576,7 +74393,7 @@ SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor *pCur){ ** routine always returns 2147483647 (which is the largest record ** that SQLite can handle) or more. But returning a smaller value might ** prevent large memory allocations when trying to interpret a -** corrupt datrabase. +** corrupt database. ** ** The current implementation merely returns the size of the underlying ** database file. @@ -74038,6 +74855,7 @@ SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){ ** vice-versa). */ static int moveToChild(BtCursor *pCur, u32 newPgno){ + int rc; assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageapPage[pCur->iPage] = pCur->pPage; pCur->ix = 0; pCur->iPage++; - return getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur, - pCur->curPagerFlags); + rc = getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur->curPagerFlags); + assert( pCur->pPage!=0 || rc!=SQLITE_OK ); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(newPgno); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + } + return rc; } #ifdef SQLITE_DEBUG @@ -74159,7 +74987,7 @@ static int moveToRoot(BtCursor *pCur){ sqlite3BtreeClearCursor(pCur); } rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage, - 0, pCur->curPagerFlags); + pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; return rc; @@ -74271,7 +75099,7 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ *pRes = 0; rc = moveToLeftmost(pCur); }else if( rc==SQLITE_EMPTY ){ - assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + assert( pCur->pgnoRoot==0 || (pCur->pPage!=0 && pCur->pPage->nCell==0) ); *pRes = 1; rc = SQLITE_OK; } @@ -74376,7 +75204,7 @@ SQLITE_PRIVATE int sqlite3BtreeTableMoveto( /* If the requested key is one more than the previous key, then ** try to get there using sqlite3BtreeNext() rather than a full ** binary search. This is an optimization only. The correct answer - ** is still obtained without this case, only a little more slowely */ + ** is still obtained without this case, only a little more slowly. */ if( pCur->info.nKey+1==intKey ){ *pRes = 0; rc = sqlite3BtreeNext(pCur, 0); @@ -74772,10 +75600,36 @@ bypass_moveto_root: }else{ chldPg = get4byte(findCell(pPage, lwr)); } - pCur->ix = (u16)lwr; - rc = moveToChild(pCur, chldPg); - if( rc ) break; - } + + /* This block is similar to an in-lined version of: + ** + ** pCur->ix = (u16)lwr; + ** rc = moveToChild(pCur, chldPg); + ** if( rc ) break; + */ + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ + return SQLITE_CORRUPT_BKPT; + } + pCur->aiIdx[pCur->iPage] = (u16)lwr; + pCur->apPage[pCur->iPage] = pCur->pPage; + pCur->ix = 0; + pCur->iPage++; + rc = getAndInitPage(pCur->pBt, chldPg, &pCur->pPage, pCur->curPagerFlags); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(chldPg); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + break; + } + /* + ***** End of in-lined moveToChild() call */ + } moveto_index_finish: pCur->info.nSize = 0; assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); @@ -75559,7 +76413,7 @@ static SQLITE_NOINLINE int clearCellOverflow( /* Call xParseCell to compute the size of a cell. If the cell contains ** overflow, then invoke cellClearOverflow to clear out that overflow. -** STore the result code (SQLITE_OK or some error code) in rc. +** Store the result code (SQLITE_OK or some error code) in rc. ** ** Implemented as macro to force inlining for performance. */ @@ -76175,7 +77029,7 @@ static int rebuildPage( if( NEVER(j>(u32)usableSize) ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[k]<=i; k++){} pSrcEnd = pCArray->apEnd[k]; pData = pEnd; @@ -76238,7 +77092,7 @@ static int rebuildPage( ** Finally, argument pBegin points to the byte immediately following the ** end of the space required by this page for the cell-pointer area (for ** all cells - not just those inserted by the current call). If the content -** area must be extended to before this point in order to accomodate all +** area must be extended to before this point in order to accommodate all ** cells in apCell[], then the cells do not fit and non-zero is returned. */ static int pageInsertArray( @@ -76258,7 +77112,7 @@ static int pageInsertArray( u8 *pEnd; /* Maximum extent of cell data */ assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ if( iEnd<=iFirst ) return 0; - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[k]<=i ; k++){} pEnd = pCArray->apEnd[k]; while( 1 /*Exit by break*/ ){ int sz, rc; @@ -76553,7 +77407,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ ** with entries for the new page, and any pointer from the ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents - ** of the parent page are still manipulated by thh code below. + ** of the parent page are still manipulated by the code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. @@ -76829,7 +77683,7 @@ static int balance_nonroot( pgno = get4byte(pRight); while( 1 ){ if( rc==SQLITE_OK ){ - rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0); + rc = getAndInitPage(pBt, pgno, &apOld[i], 0); } if( rc ){ memset(apOld, 0, (i+1)*sizeof(MemPage*)); @@ -77143,7 +77997,7 @@ static int balance_nonroot( } } - /* Sanity check: For a non-corrupt database file one of the follwing + /* Sanity check: For a non-corrupt database file one of the following ** must be true: ** (1) We found one or more cells (cntNew[0])>0), or ** (2) pPage is a virtual root page. A virtual root page is when @@ -77368,9 +78222,9 @@ static int balance_nonroot( iOvflSpace += sz; assert( sz<=pBt->maxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); - for(k=0; b.ixNx[k]<=j && ALWAYS(k=0 && iPg=1 || i>=0 ); + assert( iPg=0 /* On the upwards pass, or... */ || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ @@ -77760,7 +78616,7 @@ static int btreeOverwriteContent( ){ int nData = pX->nData - iOffset; if( nData<=0 ){ - /* Overwritting with zeros */ + /* Overwriting with zeros */ int i; for(i=0; ipData to write */ @@ -78543,7 +79399,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ MemPage *pRoot; Pgno pgnoRoot; int rc; - int ptfFlags; /* Page-type flage for the root page of new table */ + int ptfFlags; /* Page-type flags for the root page of new table */ assert( sqlite3BtreeHoldsMutex(p) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -78712,7 +79568,7 @@ static int clearDatabasePage( if( pgno>btreePagecount(pBt) ){ return SQLITE_CORRUPT_BKPT; } - rc = getAndInitPage(pBt, pgno, &pPage, 0, 0); + rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; if( (pBt->openFlags & BTREE_SINGLE)==0 && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) @@ -79378,7 +80234,7 @@ static int checkTreePage( if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; pCheck->zPfx = "Tree %u page %u: "; - pCheck->v0 = pCheck->v1 = iPage; + pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); @@ -79715,6 +80571,7 @@ SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif + sCheck.v0 = aRoot[i]; checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); } pBt->db->flags = savedDbFlags; @@ -81141,6 +81998,40 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ return SQLITE_OK; } +/* +** If pMem is already a string, detect if it is a zero-terminated +** string, or make it into one if possible, and mark it as such. +** +** This is an optimization. Correct operation continues even if +** this routine is a no-op. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ + if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ + /* pMem must be a string, and it cannot be an ephemeral or static string */ + return; + } + if( pMem->enc!=SQLITE_UTF8 ) return; + if( NEVER(pMem->z==0) ) return; + if( pMem->flags & MEM_Dyn ){ + if( pMem->xDel==sqlite3_free + && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) + ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } + if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){ + /* Blindly assume that all RCStr objects are zero-terminated */ + pMem->flags |= MEM_Term; + return; + } + }else if( pMem->szMalloc >= pMem->n+1 ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return; + } +} + /* ** It is already known that pMem contains an unterminated string. ** Add the zero terminator. @@ -81402,36 +82293,6 @@ SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem *p){ if( p->szMalloc ) vdbeMemClear(p); } -/* -** Convert a 64-bit IEEE double into a 64-bit signed integer. -** If the double is out of range of a 64-bit signed integer then -** return the closest available 64-bit signed integer. -*/ -static SQLITE_NOINLINE i64 doubleToInt64(double r){ -#ifdef SQLITE_OMIT_FLOATING_POINT - /* When floating-point is omitted, double and int64 are the same thing */ - return r; -#else - /* - ** Many compilers we encounter do not define constants for the - ** minimum and maximum 64-bit integers, or they define them - ** inconsistently. And many do not understand the "LL" notation. - ** So we define our own static constants here using nothing - ** larger than a 32-bit integer constant. - */ - static const i64 maxInt = LARGEST_INT64; - static const i64 minInt = SMALLEST_INT64; - - if( r<=(double)minInt ){ - return minInt; - }else if( r>=(double)maxInt ){ - return maxInt; - }else{ - return (i64)r; - } -#endif -} - /* ** Return some kind of integer value which is the best we can do ** at representing the value that *pMem describes as an integer. @@ -81458,7 +82319,7 @@ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem *pMem){ testcase( flags & MEM_IntReal ); return pMem->u.i; }else if( flags & MEM_Real ){ - return doubleToInt64(pMem->u.r); + return sqlite3RealToI64(pMem->u.r); }else if( (flags & (MEM_Str|MEM_Blob))!=0 && pMem->z!=0 ){ return memIntValue(pMem); }else{ @@ -81520,7 +82381,7 @@ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ if( pMem->flags & MEM_IntReal ){ MemSetTypeFlag(pMem, MEM_Int); }else{ - i64 ix = doubleToInt64(pMem->u.r); + i64 ix = sqlite3RealToI64(pMem->u.r); /* Only mark the value as an integer if ** @@ -81588,8 +82449,8 @@ SQLITE_PRIVATE int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ ** from UBSAN. */ SQLITE_PRIVATE i64 sqlite3RealToI64(double r){ - if( r<=(double)SMALLEST_INT64 ) return SMALLEST_INT64; - if( r>=(double)LARGEST_INT64) return LARGEST_INT64; + if( r<-9223372036854774784.0 ) return SMALLEST_INT64; + if( r>+9223372036854774784.0 ) return LARGEST_INT64; return (i64)r; } @@ -81660,6 +82521,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ break; } default: { + int rc; assert( aff==SQLITE_AFF_TEXT ); assert( MEM_Str==(MEM_Blob>>3) ); pMem->flags |= (pMem->flags&MEM_Blob)>>3; @@ -81667,7 +82529,9 @@ SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero); if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1; - return sqlite3VdbeChangeEncoding(pMem, encoding); + rc = sqlite3VdbeChangeEncoding(pMem, encoding); + if( rc ) return rc; + sqlite3VdbeMemZeroTerminateIfAble(pMem); } } return SQLITE_OK; @@ -82191,6 +83055,24 @@ SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ return valueToText(pVal, enc); } +/* Return true if sqlit3_value object pVal is a string or blob value +** that uses the destructor specified in the second argument. +** +** TODO: Maybe someday promote this interface into a published API so +** that third-party extensions can get access to it? +*/ +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){ + if( ALWAYS(pVal!=0) + && ALWAYS((pVal->flags & (MEM_Str|MEM_Blob))!=0) + && (pVal->flags & MEM_Dyn)!=0 + && pVal->xDel==xFree + ){ + return 1; + }else{ + return 0; + } +} + /* ** Create a new sqlite3_value object. */ @@ -82258,6 +83140,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ } pRec->nField = p->iVal+1; + sqlite3VdbeMemSetNull(&pRec->aMem[p->iVal]); return &pRec->aMem[p->iVal]; } #else @@ -83046,6 +83929,35 @@ static void test_addop_breakpoint(int pc, Op *pOp){ } #endif +/* +** Slow paths for sqlite3VdbeAddOp3() and sqlite3VdbeAddOp4Int() for the +** unusual case when we need to increase the size of the Vdbe.aOp[] array +** before adding the new opcode. +*/ +static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ + assert( p->nOpAlloc<=p->nOp ); + if( growOpArray(p, 1) ) return 1; + assert( p->nOpAlloc>p->nOp ); + return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +} +static SQLITE_NOINLINE int addOp4IntSlow( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } + return addr; +} + + /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. @@ -83056,17 +83968,16 @@ static void test_addop_breakpoint(int pc, Op *pOp){ ** ** op The opcode for this instruction ** -** p1, p2, p3 Operands -** -** Use the sqlite3VdbeResolveLabel() function to fix an address and -** the sqlite3VdbeChangeP4() function to change the value of the P4 -** operand. +** p1, p2, p3, p4 Operands */ -static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ - assert( p->nOpAlloc<=p->nOp ); - if( growOpArray(p, 1) ) return 1; - assert( p->nOpAlloc>p->nOp ); - return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ + return sqlite3VdbeAddOp3(p, op, 0, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ + return sqlite3VdbeAddOp3(p, op, p1, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ + return sqlite3VdbeAddOp3(p, op, p1, p2, 0); } SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ int i; @@ -83089,6 +84000,9 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; + + /* Replicate this logic in sqlite3VdbeAddOp4Int() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif @@ -83105,16 +84019,59 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ #ifdef SQLITE_VDBE_COVERAGE pOp->iSrcLine = 0; #endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp4Int() */ + return i; } -SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ - return sqlite3VdbeAddOp3(p, op, 0, 0, 0); -} -SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ - return sqlite3VdbeAddOp3(p, op, p1, 0, 0); -} -SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ - return sqlite3VdbeAddOp3(p, op, p1, p2, 0); +SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int i; + VdbeOp *pOp; + + i = p->nOp; + if( p->nOpAlloc<=i ){ + return addOp4IntSlow(p, op, p1, p2, p3, p4); + } + p->nOp++; + pOp = &p->aOp[i]; + assert( pOp!=0 ); + pOp->opcode = (u8)op; + pOp->p5 = 0; + pOp->p1 = p1; + pOp->p2 = p2; + pOp->p3 = p3; + pOp->p4.i = p4; + pOp->p4type = P4_INT32; + + /* Replicate this logic in sqlite3VdbeAddOp3() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOp->zComment = 0; +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(i, &p->aOp[i]); + } +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; +#endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp3() */ + + return i; } /* Generate code for an unconditional jump to instruction iDest @@ -83292,7 +84249,7 @@ SQLITE_PRIVATE int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, if( bPush){ pParse->addrExplain = iThis; } - sqlite3VdbeScanStatus(v, iThis, 0, 0, 0, 0); + sqlite3VdbeScanStatus(v, iThis, -1, -1, 0, 0); } return addr; } @@ -83322,26 +84279,6 @@ SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, sqlite3MayAbort(p->pParse); } -/* -** Add an opcode that includes the p4 value as an integer. -*/ -SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( - Vdbe *p, /* Add the opcode to this VM */ - int op, /* The new opcode */ - int p1, /* The P1 operand */ - int p2, /* The P2 operand */ - int p3, /* The P3 operand */ - int p4 /* The P4 operand as an integer */ -){ - int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); - if( p->db->mallocFailed==0 ){ - VdbeOp *pOp = &p->aOp[addr]; - pOp->p4type = P4_INT32; - pOp->p4.i = p4; - } - return addr; -} - /* Insert the end of a co-routine */ SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){ @@ -83654,7 +84591,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 0; pOp = &p->aOp[p->nOp-1]; assert( p->aOp[0].opcode==OP_Init ); - while( 1 /* Loop termates when it reaches the OP_Init opcode */ ){ + while( 1 /* Loop terminates when it reaches the OP_Init opcode */ ){ /* Only JUMP opcodes and the short list of special opcodes in the switch ** below need to be considered. The mkopcodeh.tcl generator script groups ** all these opcodes together near the front of the opcode list. Skip @@ -84024,8 +84961,8 @@ SQLITE_PRIVATE void sqlite3VdbeScanStatusCounters( pScan = 0; } if( pScan ){ - pScan->addrLoop = addrLoop; - pScan->addrVisit = addrVisit; + if( addrLoop>0 ) pScan->addrLoop = addrLoop; + if( addrVisit>0 ) pScan->addrVisit = addrVisit; } } } @@ -84108,7 +85045,7 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ /* ** If the input FuncDef structure is ephemeral, then free it. If -** the FuncDef is not ephermal, then do nothing. +** the FuncDef is not ephemeral, then do nothing. */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ assert( db!=0 ); @@ -84272,7 +85209,6 @@ SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters( } #endif /* SQLITE_DEBUG */ - /* ** Change the value of the P4 operand for a specific instruction. ** This routine is useful when a large program is loaded from a @@ -85193,7 +86129,7 @@ SQLITE_PRIVATE int sqlite3VdbeList( sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 4; + assert( p->nResColumn==4 ); }else{ sqlite3VdbeMemSetInt64(pMem+0, i); sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), @@ -85212,7 +86148,7 @@ SQLITE_PRIVATE int sqlite3VdbeList( sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 8; + assert( p->nResColumn==8 ); } p->pResultRow = pMem; if( db->mallocFailed ){ @@ -85426,26 +86362,9 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ - static const char * const azColName[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "id", "parent", "notused", "detail" - }; - int iFirst, mx, i; if( nMem<10 ) nMem = 10; p->explain = pParse->explain; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(p, 4); - iFirst = 8; - mx = 12; - }else{ - sqlite3VdbeSetNumCols(p, 8); - iFirst = 0; - mx = 8; - } - for(i=iFirst; inResColumn = 12 - 4*p->explain; } p->expired = 0; @@ -85497,7 +86416,23 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); } +static SQLITE_NOINLINE void freeCursorWithCache(Vdbe *p, VdbeCursor *pCx){ + VdbeTxtBlbCache *pCache = pCx->pCache; + assert( pCx->colCache ); + pCx->colCache = 0; + pCx->pCache = 0; + if( pCache->pCValue ){ + sqlite3RCStrUnref(pCache->pCValue); + pCache->pCValue = 0; + } + sqlite3DbFree(p->db, pCache); + sqlite3VdbeFreeCursorNN(p, pCx); +} SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ + if( pCx->colCache ){ + freeCursorWithCache(p, pCx); + return; + } switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); @@ -85598,12 +86533,12 @@ SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; - if( p->nResColumn ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->nResAlloc ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbFree(db, p->aColName); } n = nResColumn*COLNAME_N; - p->nResColumn = (u16)nResColumn; + p->nResColumn = p->nResAlloc = (u16)nResColumn; p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; initMemArray(p->aColName, n, db, MEM_Null); @@ -85628,14 +86563,14 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( ){ int rc; Mem *pColName; - assert( idxnResColumn ); + assert( idxnResAlloc ); assert( vardb->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); - pColName = &(p->aColName[idx+var*p->nResColumn]); + pColName = &(p->aColName[idx+var*p->nResAlloc]); rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; @@ -86148,6 +87083,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ + sqlite3SystemError(db, rc); p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; @@ -86459,7 +87395,7 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbNNFreeNN(db, p->aColName); } for(pSub=p->pProgram; pSub; pSub=pNext){ @@ -87059,6 +87995,15 @@ static int vdbeRecordCompareDebug( if( d1+(u64)serial_type1+2>(u64)nKey1 && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 ){ + if( serial_type1>=1 + && serial_type1<=7 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)<=(u64)nKey1+8 + && CORRUPT_DB + ){ + return 1; /* corrupt record not detected by + ** sqlite3VdbeRecordCompareWithSkip(). Return true + ** to avoid firing the assert() */ + } break; } @@ -87502,7 +88447,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* Serial types 12 or greater are strings and blobs (greater than ** numbers). Types 10 and 11 are currently "reserved for future ** use", so it doesn't really matter what the results of comparing - ** them to numberic values are. */ + ** them to numeric values are. */ rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; @@ -88759,6 +89704,7 @@ SQLITE_API void sqlite3_result_text64( (void)invokeValueDestructor(z, xDel, pCtx); }else{ setResultStrOrError(pCtx, z, (int)n, enc, xDel); + sqlite3VdbeMemZeroTerminateIfAble(pCtx->pOut); } } #ifndef SQLITE_OMIT_UTF16 @@ -89131,7 +90077,7 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context *p){ ** The destructor function for a ValueList object. This needs to be ** a separate function, unknowable to the application, to ensure that ** calls to sqlite3_vtab_in_first()/sqlite3_vtab_in_next() that are not -** preceeded by activation of IN processing via sqlite3_vtab_int() do not +** preceded by activation of IN processing via sqlite3_vtab_int() do not ** try to access a fake ValueList object inserted by a hostile extension. */ SQLITE_PRIVATE void sqlite3VdbeValueListFree(void *pToDelete){ @@ -89371,7 +90317,8 @@ SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){ */ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - return pVm ? pVm->nResColumn : 0; + if( pVm==0 ) return 0; + return pVm->nResColumn; } /* @@ -89460,7 +90407,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ ** sqlite3_column_real() ** sqlite3_column_bytes() ** sqlite3_column_bytes16() -** sqiite3_column_blob() +** sqlite3_column_blob() */ static void columnMallocFailure(sqlite3_stmt *pStmt) { @@ -89544,6 +90491,32 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ return iType; } +/* +** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. +*/ +static const char * const azExplainColNames8[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ + "id", "parent", "notused", "detail" /* EQP */ +}; +static const u16 azExplainColNames16data[] = { + /* 0 */ 'a', 'd', 'd', 'r', 0, + /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, + /* 12 */ 'p', '1', 0, + /* 15 */ 'p', '2', 0, + /* 18 */ 'p', '3', 0, + /* 21 */ 'p', '4', 0, + /* 24 */ 'p', '5', 0, + /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, + /* 35 */ 'i', 'd', 0, + /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, + /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, + /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 +}; +static const u8 iExplainColNames16[] = { + 0, 5, 12, 15, 18, 21, 24, 27, + 35, 38, 45, 53 +}; + /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. @@ -89576,15 +90549,29 @@ static const void *columnName( return 0; } #endif + if( N<0 ) return 0; ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); - n = sqlite3_column_count(pStmt); - if( N=0 ){ + sqlite3_mutex_enter(db->mutex); + + if( p->explain ){ + if( useType>0 ) goto columnName_end; + n = p->explain==1 ? 8 : 4; + if( N>=n ) goto columnName_end; + if( useUtf16 ){ + int i = iExplainColNames16[N + 8*p->explain - 8]; + ret = (void*)&azExplainColNames16data[i]; + }else{ + ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; + } + goto columnName_end; + } + n = p->nResColumn; + if( NmallocFailed; N += useType*n; - sqlite3_mutex_enter(db->mutex); #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); @@ -89601,8 +90588,9 @@ static const void *columnName( sqlite3OomClear(db); ret = 0; } - sqlite3_mutex_leave(db->mutex); } +columnName_end: + sqlite3_mutex_leave(db->mutex); return ret; } @@ -89695,7 +90683,7 @@ SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ /* ** Unbind the value bound to variable i in virtual machine p. This is the ** the same as binding a NULL value to the column. If the "i" parameter is -** out of range, then SQLITE_RANGE is returned. Othewise SQLITE_OK. +** out of range, then SQLITE_RANGE is returned. Otherwise SQLITE_OK. ** ** A successful evaluation of this routine acquires the mutex on p. ** the mutex is released if any kind of error occurs. @@ -90059,6 +91047,39 @@ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } +/* +** Set the explain mode for a statement. +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ + Vdbe *v = (Vdbe*)pStmt; + int rc; + sqlite3_mutex_enter(v->db->mutex); + if( ((int)v->explain)==eMode ){ + rc = SQLITE_OK; + }else if( eMode<0 || eMode>2 ){ + rc = SQLITE_ERROR; + }else if( (v->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + rc = SQLITE_ERROR; + }else if( v->eVdbeState!=VDBE_READY_STATE ){ + rc = SQLITE_BUSY; + }else if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ + /* No reprepare necessary */ + v->explain = eMode; + rc = SQLITE_OK; + }else{ + v->explain = eMode; + rc = sqlite3Reprepare(v); + v->haveEqpOps = eMode==2; + } + if( v->explain ){ + v->nResColumn = 12 - 4*v->explain; + }else{ + v->nResColumn = v->nResAlloc; + } + sqlite3_mutex_leave(v->db->mutex); + return rc; +} + /* ** Return true if the prepared statement is in need of being reset. */ @@ -91298,6 +92319,9 @@ SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){ sqlite3_str_appendchar(pStr, 1, (c>=0x20&&c<=0x7f) ? c : '.'); } sqlite3_str_appendf(pStr, "]%s", encnames[pMem->enc]); + if( f & MEM_Term ){ + sqlite3_str_appendf(pStr, "(0-term)"); + } } } #endif @@ -91434,6 +92458,93 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){ return h; } + +/* +** For OP_Column, factor out the case where content is loaded from +** overflow pages, so that the code to implement this case is separate +** the common case where all content fits on the page. Factoring out +** the code reduces register pressure and helps the common case +** to run faster. +*/ +static SQLITE_NOINLINE int vdbeColumnFromOverflow( + VdbeCursor *pC, /* The BTree cursor from which we are reading */ + int iCol, /* The column to read */ + int t, /* The serial-type code for the column value */ + i64 iOffset, /* Offset to the start of the content value */ + u32 cacheStatus, /* Current Vdbe.cacheCtr value */ + u32 colCacheCtr, /* Current value of the column cache counter */ + Mem *pDest /* Store the value into this register. */ +){ + int rc; + sqlite3 *db = pDest->db; + int encoding = pDest->enc; + int len = sqlite3VdbeSerialTypeLen(t); + assert( pC->eCurType==CURTYPE_BTREE ); + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) return SQLITE_TOOBIG; + if( len > 4000 && pC->pKeyInfo==0 ){ + /* Cache large column values that are on overflow pages using + ** an RCStr (reference counted string) so that if they are reloaded, + ** that do not have to be copied a second time. The overhead of + ** creating and managing the cache is such that this is only + ** profitable for larger TEXT and BLOB values. + ** + ** Only do this on table-btrees so that writes to index-btrees do not + ** need to clear the cache. This buys performance in the common case + ** in exchange for generality. + */ + VdbeTxtBlbCache *pCache; + char *pBuf; + if( pC->colCache==0 ){ + pC->pCache = sqlite3DbMallocZero(db, sizeof(VdbeTxtBlbCache) ); + if( pC->pCache==0 ) return SQLITE_NOMEM; + pC->colCache = 1; + } + pCache = pC->pCache; + if( pCache->pCValue==0 + || pCache->iCol!=iCol + || pCache->cacheStatus!=cacheStatus + || pCache->colCacheCtr!=colCacheCtr + || pCache->iOffset!=sqlite3BtreeOffset(pC->uc.pCursor) + ){ + if( pCache->pCValue ) sqlite3RCStrUnref(pCache->pCValue); + pBuf = pCache->pCValue = sqlite3RCStrNew( len+3 ); + if( pBuf==0 ) return SQLITE_NOMEM; + rc = sqlite3BtreePayload(pC->uc.pCursor, iOffset, len, pBuf); + if( rc ) return rc; + pBuf[len] = 0; + pBuf[len+1] = 0; + pBuf[len+2] = 0; + pCache->iCol = iCol; + pCache->cacheStatus = cacheStatus; + pCache->colCacheCtr = colCacheCtr; + pCache->iOffset = sqlite3BtreeOffset(pC->uc.pCursor); + }else{ + pBuf = pCache->pCValue; + } + assert( t>=12 ); + sqlite3RCStrRef(pBuf); + if( t&1 ){ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, + (void(*)(void*))sqlite3RCStrUnref); + pDest->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, + (void(*)(void*))sqlite3RCStrUnref); + } + }else{ + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); + if( rc ) return rc; + sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); + if( (t&1)!=0 && encoding==SQLITE_UTF8 ){ + pDest->z[len] = 0; + pDest->flags |= MEM_Term; + } + } + pDest->flags &= ~MEM_Ephem; + return rc; +} + + /* ** Return the symbolic name for the data type of a pMem */ @@ -91476,6 +92587,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec( Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ + u32 colCacheCtr = 0; /* Column cache counter */ #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) u64 *pnCycle = 0; int bStmtScanStatus = IS_STMT_SCANSTATUS(db)!=0; @@ -91671,8 +92783,8 @@ SQLITE_PRIVATE int sqlite3VdbeExec( case OP_Goto: { /* jump */ #ifdef SQLITE_DEBUG - /* In debuggging mode, when the p5 flags is set on an OP_Goto, that - ** means we should really jump back to the preceeding OP_ReleaseReg + /* In debugging mode, when the p5 flags is set on an OP_Goto, that + ** means we should really jump back to the preceding OP_ReleaseReg ** instruction. */ if( pOp->p5 ){ assert( pOp->p2 < (int)(pOp - aOp) ); @@ -91880,7 +92992,7 @@ case OP_HaltIfNull: { /* in3 */ ** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. ** ** 0: (no change) -** 1: NOT NULL contraint failed: P4 +** 1: NOT NULL constraint failed: P4 ** 2: UNIQUE constraint failed: P4 ** 3: CHECK constraint failed: P4 ** 4: FOREIGN KEY constraint failed: P4 @@ -93011,10 +94123,10 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** opcodes are allowed to occur between this instruction and the previous ** OP_Lt or OP_Gt. ** -** If result of an OP_Eq comparison on the same two operands as the -** prior OP_Lt or OP_Gt would have been true, then jump to P2. -** If the result of an OP_Eq comparison on the two previous -** operands would have been false or NULL, then fall through. +** If the result of an OP_Eq comparison on the same two operands as +** the prior OP_Lt or OP_Gt would have been true, then jump to P2. If +** the result of an OP_Eq comparison on the two previous operands +** would have been false or NULL, then fall through. */ case OP_ElseEq: { /* same as TK_ESCAPE, jump */ @@ -93444,7 +94556,7 @@ case OP_IsType: { /* jump */ /* Opcode: ZeroOrNull P1 P2 P3 * * ** Synopsis: r[P2] = 0 OR NULL ** -** If all both registers P1 and P3 are NOT NULL, then store a zero in +** If both registers P1 and P3 are NOT NULL, then store a zero in ** register P2. If either registers P1 or P3 are NULL then put ** a NULL in register P2. */ @@ -93798,11 +94910,16 @@ op_column_restart: pDest->flags = aFlag[t&1]; } }else{ + u8 p5; pDest->enc = encoding; + assert( pDest->db==db ); /* This branch happens only when content is on overflow pages */ - if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 - && ((t>=12 && (t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0)) - || (len = sqlite3VdbeSerialTypeLen(t))==0 + if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 + && (p5==OPFLAG_TYPEOFARG + || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) + ) + ) + || sqlite3VdbeSerialTypeLen(t)==0 ){ /* Content is irrelevant for ** 1. the typeof() function, @@ -93819,11 +94936,13 @@ op_column_restart: */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ - if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; - rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); - pDest->flags &= ~MEM_Ephem; + rc = vdbeColumnFromOverflow(pC, p2, t, aOffset[p2], + p->cacheCtr, colCacheCtr, pDest); + if( rc ){ + if( rc==SQLITE_NOMEM ) goto no_mem; + if( rc==SQLITE_TOOBIG ) goto too_big; + goto abort_due_to_error; + } } } @@ -95107,7 +96226,7 @@ case OP_OpenEphemeral: { /* ncycle */ } pCx = p->apCsr[pOp->p1]; if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ - /* If the ephermeral table is already open and has no duplicates from + /* If the ephemeral table is already open and has no duplicates from ** OP_OpenDup, then erase all existing content so that the table is ** empty again, rather than creating a new table. */ assert( pCx->isEphemeral ); @@ -95598,7 +96717,7 @@ seek_not_found: ** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If ** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 ** case occurs when there are no inequality constraints to the right of -** the IN constraing. The jump to SeekGE.P2 ends the loop. The P5!=0 case +** the IN constraint. The jump to SeekGE.P2 ends the loop. The P5!=0 case ** occurs when there are inequality constraints to the right of the IN ** operator. In that case, the This.P2 will point either directly to or ** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for @@ -95606,7 +96725,7 @@ seek_not_found: ** ** Possible outcomes from this opcode:
      ** -**
    1. If the cursor is initally not pointed to any valid row, then +**
    2. If the cursor is initially not pointed to any valid row, then ** fall through into the subsequent OP_SeekGE opcode. ** **
    3. If the cursor is left pointing to a row that is before the target @@ -95838,13 +96957,13 @@ case OP_IfNotOpen: { /* jump */ ** operands to OP_NotFound and OP_IdxGT. ** ** This opcode is an optimization attempt only. If this opcode always -** falls through, the correct answer is still obtained, but extra works +** falls through, the correct answer is still obtained, but extra work ** is performed. ** ** A value of N in the seekHit flag of cursor P1 means that there exists ** a key P3:N that will match some record in the index. We want to know ** if it is possible for a record P3:P4 to match some record in the -** index. If it is not possible, we can skips some work. So if seekHit +** index. If it is not possible, we can skip some work. So if seekHit ** is less than P4, attempt to find out if a match is possible by running ** OP_NotFound. ** @@ -96356,6 +97475,7 @@ case OP_Insert: { ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; + colCacheCtr++; /* Invoke the update-hook if required. */ if( rc ) goto abort_due_to_error; @@ -96409,10 +97529,10 @@ case OP_RowCell: { ** left in an undefined state. ** ** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this -** delete one of several associated with deleting a table row and all its -** associated index entries. Exactly one of those deletes is the "primary" -** delete. The others are all on OPFLAG_FORDELETE cursors or else are -** marked with the AUXDELETE flag. +** delete is one of several associated with deleting a table row and +** all its associated index entries. Exactly one of those deletes is +** the "primary" delete. The others are all on OPFLAG_FORDELETE +** cursors or else are marked with the AUXDELETE flag. ** ** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row ** change count is incremented (otherwise not). @@ -96516,6 +97636,7 @@ case OP_Delete: { rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + colCacheCtr++; pC->seekResult = 0; if( rc ) goto abort_due_to_error; @@ -96583,13 +97704,13 @@ case OP_SorterCompare: { ** Write into register P2 the current sorter data for sorter cursor P1. ** Then clear the column header cache on cursor P3. ** -** This opcode is normally use to move a record out of the sorter and into +** This opcode is normally used to move a record out of the sorter and into ** a register that is the source for a pseudo-table cursor created using ** OpenPseudo. That pseudo-table cursor is the one that is identified by ** parameter P3. Clearing the P3 column cache as part of this opcode saves ** us from having to issue a separate NullRow instruction to clear that cache. */ -case OP_SorterData: { +case OP_SorterData: { /* ncycle */ VdbeCursor *pC; pOut = &aMem[pOp->p2]; @@ -96864,8 +97985,8 @@ case OP_IfSmaller: { /* jump */ ** regression tests can determine whether or not the optimizer is ** correctly optimizing out sorts. */ -case OP_SorterSort: /* jump */ -case OP_Sort: { /* jump */ +case OP_SorterSort: /* jump ncycle */ +case OP_Sort: { /* jump ncycle */ #ifdef SQLITE_TEST sqlite3_sort_count++; sqlite3_search_count--; @@ -97392,7 +98513,7 @@ case OP_IdxGE: { /* jump, ncycle */ ** file is given by P1. ** ** The table being destroyed is in the main database file if P3==0. If -** P3==1 then the table to be clear is in the auxiliary database file +** P3==1 then the table to be destroyed is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** ** If AUTOVACUUM is enabled then it is possible that another root page @@ -97452,8 +98573,8 @@ case OP_Destroy: { /* out2 */ ** in the database file is given by P1. But, unlike Destroy, do not ** remove the table or index from the database file. ** -** The table being clear is in the main database file if P2==0. If -** P2==1 then the table to be clear is in the auxiliary database file +** The table being cleared is in the main database file if P2==0. If +** P2==1 then the table to be cleared is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** ** If the P3 value is non-zero, then the row change count is incremented @@ -98279,7 +99400,7 @@ case OP_AggStep1: { /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ if( pCtx->pMem != pMem ){ pCtx->pMem = pMem; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; @@ -99157,7 +100278,7 @@ case OP_MaxPgcnt: { /* out2 */ ** This opcode works exactly like OP_Function. The only difference is in ** its name. This opcode is used in places where the function must be ** purely non-deterministic. Some built-in date/time functions can be -** either determinitic of non-deterministic, depending on their arguments. +** either deterministic of non-deterministic, depending on their arguments. ** When those function are used in a non-deterministic way, they will check ** to see if they were called using OP_PureFunc instead of OP_Function, and ** if they were, they throw an error. @@ -99175,7 +100296,7 @@ case OP_Function: { /* group */ /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ pOut = &aMem[pOp->p3]; if( pCtx->pOut != pOut ){ pCtx->pVdbe = p; @@ -99251,7 +100372,7 @@ case OP_FilterAdd: { printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); } #endif - h %= pIn1->n; + h %= (pIn1->n*8); pIn1->z[h/8] |= 1<<(h&7); break; } @@ -99287,7 +100408,7 @@ case OP_Filter: { /* jump */ printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); } #endif - h %= pIn1->n; + h %= (pIn1->n*8); if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ VdbeBranchTaken(1, 2); p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; @@ -99539,7 +100660,7 @@ default: { /* This is really OP_Noop, OP_Explain */ } if( opProperty==0xff ){ /* Never happens. This code exists to avoid a harmless linkage - ** warning aboud sqlite3VdbeRegisterDump() being defined but not + ** warning about sqlite3VdbeRegisterDump() being defined but not ** used. */ sqlite3VdbeRegisterDump(p); } @@ -100257,7 +101378,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** The threshold for the amount of main memory to use before flushing ** records to a PMA is roughly the same as the limit configured for the ** page-cache of the main database. Specifically, the threshold is set to -** the value returned by "PRAGMA main.page_size" multipled by +** the value returned by "PRAGMA main.page_size" multiplied by ** that returned by "PRAGMA main.cache_size", in bytes. ** ** If the sorter is running in single-threaded mode, then all PMAs generated @@ -100280,7 +101401,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** ** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the ** sorter is running in single-threaded mode, then these PMAs are merged -** incrementally as keys are retreived from the sorter by the VDBE. The +** incrementally as keys are retrieved from the sorter by the VDBE. The ** MergeEngine object, described in further detail below, performs this ** merge. ** @@ -100443,7 +101564,7 @@ struct MergeEngine { ** ** Essentially, this structure contains all those fields of the VdbeSorter ** structure for which each thread requires a separate instance. For example, -** each thread requries its own UnpackedRecord object to unpack records in +** each thread requeries its own UnpackedRecord object to unpack records in ** as part of comparison operations. ** ** Before a background thread is launched, variable bDone is set to 0. Then, @@ -100515,7 +101636,7 @@ struct VdbeSorter { ** PMA, in sorted order. The next key to be read is cached in nKey/aKey. ** aKey might point into aMap or into aBuffer. If neither of those locations ** contain a contiguous representation of the key, then aAlloc is allocated -** and the key is copied into aAlloc and aKey is made to poitn to aAlloc. +** and the key is copied into aAlloc and aKey is made to point to aAlloc. ** ** pFd==0 at EOF. */ @@ -101886,7 +103007,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ ** the background thread from a sub-tasks previous turn is still running, ** skip it. If the first (pSorter->nTask-1) sub-tasks are all still busy, ** fall back to using the final sub-task. The first (pSorter->nTask-1) - ** sub-tasks are prefered as they use background threads - the final + ** sub-tasks are preferred as they use background threads - the final ** sub-task uses the main thread. */ for(i=0; iiPrev + i + 1) % nWorker; @@ -102370,7 +103491,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode); - /* Set up the required files for pIncr. A multi-theaded IncrMerge object + /* Set up the required files for pIncr. A multi-threaded IncrMerge object ** requires two temp files to itself, whereas a single-threaded object ** only requires a region of pTask->file2. */ if( rc==SQLITE_OK ){ @@ -103010,6 +104131,8 @@ static int bytecodevtabConnect( "p5 INT," "comment TEXT," "subprog TEXT," + "nexec INT," + "ncycle INT," "stmt HIDDEN" ");", @@ -103172,7 +104295,7 @@ static int bytecodevtabColumn( } } } - i += 10; + i += 20; } } switch( i ){ @@ -103222,16 +104345,31 @@ static int bytecodevtabColumn( } break; } - case 10: /* tables_used.type */ + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + case 9: /* nexec */ + sqlite3_result_int(ctx, pOp->nExec); + break; + case 10: /* ncycle */ + sqlite3_result_int(ctx, pOp->nCycle); + break; +#else + case 9: /* nexec */ + case 10: /* ncycle */ + sqlite3_result_int(ctx, 0); + break; +#endif + + case 20: /* tables_used.type */ sqlite3_result_text(ctx, pCur->zType, -1, SQLITE_STATIC); break; - case 11: /* tables_used.schema */ + case 21: /* tables_used.schema */ sqlite3_result_text(ctx, pCur->zSchema, -1, SQLITE_STATIC); break; - case 12: /* tables_used.name */ + case 22: /* tables_used.name */ sqlite3_result_text(ctx, pCur->zName, -1, SQLITE_STATIC); break; - case 13: /* tables_used.wr */ + case 23: /* tables_used.wr */ sqlite3_result_int(ctx, pOp->opcode==OP_OpenWrite); break; } @@ -103305,7 +104443,7 @@ static int bytecodevtabBestIndex( int rc = SQLITE_CONSTRAINT; struct sqlite3_index_constraint *p; bytecodevtab *pVTab = (bytecodevtab*)tab; - int iBaseCol = pVTab->bTablesUsed ? 4 : 8; + int iBaseCol = pVTab->bTablesUsed ? 4 : 10; pIdxInfo->estimatedCost = (double)100; pIdxInfo->estimatedRows = 100; pIdxInfo->idxNum = 0; @@ -103876,7 +105014,7 @@ static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ ** The return value from this routine is WRC_Abort to abandon the tree walk ** and WRC_Continue to continue. */ -static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3WalkExprNN(Walker *pWalker, Expr *pExpr){ int rc; testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); @@ -103885,7 +105023,9 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ if( rc ) return rc & WRC_Abort; if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ assert( pExpr->x.pList==0 || pExpr->pRight==0 ); - if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; + if( pExpr->pLeft && sqlite3WalkExprNN(pWalker, pExpr->pLeft) ){ + return WRC_Abort; + } if( pExpr->pRight ){ assert( !ExprHasProperty(pExpr, EP_WinFunc) ); pExpr = pExpr->pRight; @@ -103909,7 +105049,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ - return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue; + return pExpr ? sqlite3WalkExprNN(pWalker,pExpr) : WRC_Continue; } /* @@ -104035,7 +105175,7 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ } /* Increase the walkerDepth when entering a subquery, and -** descrease when leaving the subquery. +** decrease when leaving the subquery. */ SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker *pWalker, Select *pSelect){ UNUSED_PARAMETER(pSelect); @@ -105769,7 +106909,7 @@ static int resolveOrderGroupBy( } for(j=0; jpEList->nExpr; j++){ if( sqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ - /* Since this expresion is being changed into a reference + /* Since this expression is being changed into a reference ** to an identical expression in the result set, remove all Window ** objects belonging to the expression from the Select.pWin list. */ windowRemoveExprFromSelect(pSelect, pE); @@ -106092,7 +107232,8 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( return SQLITE_ERROR; } #endif - sqlite3WalkExpr(&w, pExpr); + assert( pExpr!=0 ); + sqlite3WalkExprNN(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight -= pExpr->nHeight; #endif @@ -106134,7 +107275,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames( return WRC_Abort; } #endif - sqlite3WalkExpr(&w, pExpr); + sqlite3WalkExprNN(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight -= pExpr->nHeight; #endif @@ -106156,7 +107297,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames( /* ** Resolve all names in all expressions of a SELECT and in all -** decendents of the SELECT, including compounds off of p->pPrior, +** descendants of the SELECT, including compounds off of p->pPrior, ** subqueries in expressions, and subqueries used as FROM clause ** terms. ** @@ -106306,6 +107447,7 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ if( op==TK_SELECT_COLUMN ){ assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iColumn >= 0 ); assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); return sqlite3ExprAffinity( pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr @@ -106542,7 +107684,7 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ /* ** Return the collation sequence for the expression pExpr. If ** there is no defined collating sequence, return a pointer to the -** defautl collation sequence. +** default collation sequence. ** ** See also: sqlite3ExprCollSeq() ** @@ -106672,7 +107814,7 @@ SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq( return pColl; } -/* Expresssion p is a comparison operator. Return a collation sequence +/* Expression p is a comparison operator. Return a collation sequence ** appropriate for the comparison operator. ** ** This is normally just a wrapper around sqlite3BinaryCompareCollSeq(). @@ -107128,6 +108270,15 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ #define exprSetHeight(y) #endif /* SQLITE_MAX_EXPR_DEPTH>0 */ +/* +** Set the error offset for an Expr node, if possible. +*/ +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr *pExpr, int iOfst){ + if( pExpr==0 ) return; + if( NEVER(ExprUseWJoin(pExpr)) ) return; + pExpr->w.iOfst = iOfst; +} + /* ** This routine is the core allocator for Expr nodes. ** @@ -107588,7 +108739,7 @@ SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ /* ** Arrange to cause pExpr to be deleted when the pParse is deleted. ** This is similar to sqlite3ExprDelete() except that the delete is -** deferred untilthe pParse is deleted. +** deferred until the pParse is deleted. ** ** The pExpr might be deleted immediately on an OOM error. ** @@ -108430,7 +109581,7 @@ SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ ** and 0 if it is FALSE. */ SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr *pExpr){ - pExpr = sqlite3ExprSkipCollate((Expr*)pExpr); + pExpr = sqlite3ExprSkipCollateAndLikely((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 @@ -109023,7 +110174,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** IN_INDEX_INDEX_ASC - The cursor was opened on an ascending index. ** IN_INDEX_INDEX_DESC - The cursor was opened on a descending index. ** IN_INDEX_EPH - The cursor was opened on a specially created and -** populated epheremal table. +** populated ephemeral table. ** IN_INDEX_NOOP - No cursor was allocated. The IN operator must be ** implemented as a sequence of comparisons. ** @@ -109036,7 +110187,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** an ephemeral table might need to be generated from the RHS and then ** pX->iTable made to point to the ephemeral table instead of an ** existing table. In this case, the creation and initialization of the -** ephmeral table might be put inside of a subroutine, the EP_Subrtn flag +** ephemeral table might be put inside of a subroutine, the EP_Subrtn flag ** will be set on pX and the pX->y.sub fields will be set to show where ** the subroutine is coded. ** @@ -109048,12 +110199,12 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate ** through the set members) then the b-tree must not contain duplicates. -** An epheremal table will be created unless the selected columns are guaranteed +** An ephemeral table will be created unless the selected columns are guaranteed ** to be unique - either because it is an INTEGER PRIMARY KEY or due to ** a UNIQUE constraint or index. ** ** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used -** for fast set membership tests) then an epheremal table must +** for fast set membership tests) then an ephemeral table must ** be used unless is a single INTEGER PRIMARY KEY column or an ** index can be found with the specified as its left-most. ** @@ -109386,7 +110537,7 @@ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ ** x IN (SELECT a FROM b) -- IN operator with subquery on the right ** ** The pExpr parameter is the IN operator. The cursor number for the -** constructed ephermeral table is returned. The first time the ephemeral +** constructed ephemeral table is returned. The first time the ephemeral ** table is computed, the cursor number is also stored in pExpr->iTable, ** however the cursor number returned might not be the same, as it might ** have been duplicated using OP_OpenDup. @@ -110201,10 +111352,13 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( u8 p5 /* P5 value for OP_Column + FLAGS */ ){ assert( pParse->pVdbe!=0 ); + assert( (p5 & (OPFLAG_NOCHNG|OPFLAG_TYPEOFARG|OPFLAG_LENGTHARG))==p5 ); + assert( IsVirtual(pTab) || (p5 & OPFLAG_NOCHNG)==0 ); sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg); if( p5 ){ VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); if( pOp->opcode==OP_Column ) pOp->p5 = p5; + if( pOp->opcode==OP_VColumn ) pOp->p5 = (p5 & OPFLAG_NOCHNG); } return iReg; } @@ -110233,7 +111387,7 @@ static void exprToRegister(Expr *pExpr, int iReg){ /* ** Evaluate an expression (either a vector or a scalar expression) and store -** the result in continguous temporary registers. Return the index of +** the result in contiguous temporary registers. Return the index of ** the first register used to store the result. ** ** If the returned result register is a temporary scalar, then also write @@ -110273,7 +111427,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ */ static void setDoNotMergeFlagOnCopy(Vdbe *v){ if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){ - sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergable */ + sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergeable */ } } @@ -110363,13 +111517,13 @@ static int exprCodeInlineFunction( } case INLINEFUNC_implies_nonnull_row: { - /* REsult of sqlite3ExprImpliesNonNullRow() */ + /* Result of sqlite3ExprImpliesNonNullRow() */ Expr *pA1; assert( nFarg==2 ); pA1 = pFarg->a[1].pExpr; if( pA1->op==TK_COLUMN ){ sqlite3VdbeAddOp2(v, OP_Integer, - sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable), + sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable,1), target); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); @@ -110545,7 +111699,7 @@ expr_code_doover: if( ExprHasProperty(pExpr, EP_FixedCol) ){ /* This COLUMN expression is really a constant due to WHERE clause ** constraints, and that constant is coded by the pExpr->pLeft - ** expresssion. However, make sure the constant has the correct + ** expression. However, make sure the constant has the correct ** datatype by applying the Affinity of the table column to the ** constant. */ @@ -110871,7 +112025,7 @@ expr_code_doover: sqlite3ErrorMsg(pParse, "unknown function: %#T()", pExpr); break; } - if( pDef->funcFlags & SQLITE_FUNC_INLINE ){ + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)!=0 && ALWAYS(pFarg!=0) ){ assert( (pDef->funcFlags & SQLITE_FUNC_UNSAFE)==0 ); assert( (pDef->funcFlags & SQLITE_FUNC_DIRECT)==0 ); return exprCodeInlineFunction(pParse, pFarg, @@ -110897,10 +112051,10 @@ expr_code_doover: r1 = sqlite3GetTempRange(pParse, nFarg); } - /* For length() and typeof() functions with a column argument, + /* For length() and typeof() and octet_length() functions, ** set the P5 parameter to the OP_Column opcode to OPFLAG_LENGTHARG - ** or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data - ** loading. + ** or OPFLAG_TYPEOFARG or OPFLAG_BYTELENARG respectively, to avoid + ** unnecessary data loading. */ if( (pDef->funcFlags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ u8 exprOp; @@ -110910,14 +112064,16 @@ expr_code_doover: if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); - testcase( pDef->funcFlags & OPFLAG_LENGTHARG ); - pFarg->a[0].pExpr->op2 = - pDef->funcFlags & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG); + assert( SQLITE_FUNC_BYTELEN==OPFLAG_BYTELENARG ); + assert( (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG)==OPFLAG_BYTELENARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_LENGTHARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_TYPEOFARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_BYTELENARG); + pFarg->a[0].pExpr->op2 = pDef->funcFlags & OPFLAG_BYTELENARG; } } - sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, - SQLITE_ECEL_DUP|SQLITE_ECEL_FACTOR); + sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, SQLITE_ECEL_FACTOR); }else{ r1 = 0; } @@ -111274,7 +112430,7 @@ expr_code_doover: ** ** If regDest>=0 then the result is always stored in that register and the ** result is not reusable. If regDest<0 then this routine is free to -** store the value whereever it wants. The register where the expression +** store the value wherever it wants. The register where the expression ** is stored is returned. When regDest<0, two identical expressions might ** code to the same register, if they do not contain function calls and hence ** are factored out into the initialization section at the end of the @@ -112192,7 +113348,7 @@ static int exprImpliesNotNull( ** pE1: x!=123 pE2: x IS NOT NULL Result: true ** pE1: x!=?1 pE2: x IS NOT NULL Result: true ** pE1: x IS NULL pE2: x IS NOT NULL Result: false -** pE1: x IS ?2 pE2: x IS NOT NULL Reuslt: false +** pE1: x IS ?2 pE2: x IS NOT NULL Result: false ** ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has ** Expr.iTable<0 then assume a table number given by iTab. @@ -112229,11 +113385,29 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr( return 0; } +/* This is a helper function to impliesNotNullRow(). In this routine, +** set pWalker->eCode to one only if *both* of the input expressions +** separately have the implies-not-null-row property. +*/ +static void bothImplyNotNullRow(Walker *pWalker, Expr *pE1, Expr *pE2){ + if( pWalker->eCode==0 ){ + sqlite3WalkExpr(pWalker, pE1); + if( pWalker->eCode ){ + pWalker->eCode = 0; + sqlite3WalkExpr(pWalker, pE2); + } + } +} + /* ** This is the Expr node callback for sqlite3ExprImpliesNonNullRow(). ** If the expression node requires that the table at pWalker->iCur ** have one or more non-NULL column, then set pWalker->eCode to 1 and abort. ** +** pWalker->mWFlags is non-zero if this inquiry is being undertaking on +** behalf of a RIGHT JOIN (or FULL JOIN). That makes a difference when +** evaluating terms in the ON clause of an inner join. +** ** This routine controls an optimization. False positives (setting ** pWalker->eCode to 1 when it should not be) are deadly, but false-negatives ** (never setting pWalker->eCode) is a harmless missed optimization. @@ -112242,28 +113416,33 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_InnerON) && pWalker->mWFlags ){ + /* If iCur is used in an inner-join ON clause to the left of a + ** RIGHT JOIN, that does *not* mean that the table must be non-null. + ** But it is difficult to check for that condition precisely. + ** To keep things simple, any use of iCur from any inner-join is + ** ignored while attempting to simplify a RIGHT JOIN. */ + return WRC_Prune; + } switch( pExpr->op ){ case TK_ISNOT: case TK_ISNULL: case TK_NOTNULL: case TK_IS: - case TK_OR: case TK_VECTOR: - case TK_CASE: - case TK_IN: case TK_FUNCTION: case TK_TRUTH: + case TK_CASE: testcase( pExpr->op==TK_ISNOT ); testcase( pExpr->op==TK_ISNULL ); testcase( pExpr->op==TK_NOTNULL ); testcase( pExpr->op==TK_IS ); - testcase( pExpr->op==TK_OR ); testcase( pExpr->op==TK_VECTOR ); - testcase( pExpr->op==TK_CASE ); - testcase( pExpr->op==TK_IN ); testcase( pExpr->op==TK_FUNCTION ); testcase( pExpr->op==TK_TRUTH ); + testcase( pExpr->op==TK_CASE ); return WRC_Prune; + case TK_COLUMN: if( pWalker->u.iCur==pExpr->iTable ){ pWalker->eCode = 1; @@ -112271,21 +113450,38 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ } return WRC_Prune; + case TK_OR: case TK_AND: - if( pWalker->eCode==0 ){ + /* Both sides of an AND or OR must separately imply non-null-row. + ** Consider these cases: + ** 1. NOT (x AND y) + ** 2. x OR y + ** If only one of x or y is non-null-row, then the overall expression + ** can be true if the other arm is false (case 1) or true (case 2). + */ + testcase( pExpr->op==TK_OR ); + testcase( pExpr->op==TK_AND ); + bothImplyNotNullRow(pWalker, pExpr->pLeft, pExpr->pRight); + return WRC_Prune; + + case TK_IN: + /* Beware of "x NOT IN ()" and "x NOT IN (SELECT 1 WHERE false)", + ** both of which can be true. But apart from these cases, if + ** the left-hand side of the IN is NULL then the IN itself will be + ** NULL. */ + if( ExprUseXList(pExpr) && ALWAYS(pExpr->x.pList->nExpr>0) ){ sqlite3WalkExpr(pWalker, pExpr->pLeft); - if( pWalker->eCode ){ - pWalker->eCode = 0; - sqlite3WalkExpr(pWalker, pExpr->pRight); - } } return WRC_Prune; case TK_BETWEEN: - if( sqlite3WalkExpr(pWalker, pExpr->pLeft)==WRC_Abort ){ - assert( pWalker->eCode ); - return WRC_Abort; - } + /* In "x NOT BETWEEN y AND z" either x must be non-null-row or else + ** both y and z must be non-null row */ + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + sqlite3WalkExpr(pWalker, pExpr->pLeft); + bothImplyNotNullRow(pWalker, pExpr->x.pList->a[0].pExpr, + pExpr->x.pList->a[1].pExpr); return WRC_Prune; /* Virtual tables are allowed to use constraints like x=NULL. So @@ -112347,7 +113543,7 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** be non-NULL, then the LEFT JOIN can be safely converted into an ** ordinary join. */ -SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab, int isRJ){ Walker w; p = sqlite3ExprSkipCollateAndLikely(p); if( p==0 ) return 0; @@ -112355,7 +113551,7 @@ SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ p = p->pLeft; }else{ while( p->op==TK_AND ){ - if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab) ) return 1; + if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab, isRJ) ) return 1; p = p->pRight; } } @@ -112363,6 +113559,7 @@ SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ w.xSelectCallback = 0; w.xSelectCallback2 = 0; w.eCode = 0; + w.mWFlags = isRJ!=0; w.u.iCur = iTab; sqlite3WalkExpr(&w, p); return w.eCode; @@ -112423,7 +113620,7 @@ SQLITE_PRIVATE int sqlite3ExprCoveredByIndex( } -/* Structure used to pass information throught the Walker in order to +/* Structure used to pass information throughout the Walker in order to ** implement sqlite3ReferencesSrcList(). */ struct RefSrcList { @@ -112639,7 +113836,7 @@ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ ** Return the index in aCol[] of the entry that describes that column. ** ** If no prior entry is found, create a new one and return -1. The -** new column will have an idex of pAggInfo->nColumn-1. +** new column will have an index of pAggInfo->nColumn-1. */ static void findOrCreateAggInfoColumn( Parse *pParse, /* Parsing context */ @@ -112652,6 +113849,7 @@ static void findOrCreateAggInfoColumn( assert( pAggInfo->iFirstReg==0 ); pCol = pAggInfo->aCol; for(k=0; knColumn; k++, pCol++){ + if( pCol->pCExpr==pExpr ) return; if( pCol->iTable==pExpr->iTable && pCol->iColumn==pExpr->iColumn && pExpr->op!=TK_IF_NULL_ROW @@ -113532,7 +114730,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); pNew->pSchema = db->aDb[iDb].pSchema; pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; - pNew->nTabRef = 1; + assert( pNew->nTabRef==1 ); exit_begin_add_column: sqlite3SrcListDelete(db, pSrc); @@ -114037,7 +115235,7 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ } /* -** An error occured while parsing or otherwise processing a database +** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an ** ALTER TABLE RENAME COLUMN program. The error message emitted by the ** sub-routine is currently stored in pParse->zErrMsg. This function @@ -117143,14 +118341,15 @@ static int loadStatTbl( decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); - /* Take a copy of the sample. Add two 0x00 bytes the end of the buffer. + /* Take a copy of the sample. Add 8 extra 0x00 bytes the end of the buffer. ** This is in case the sample record is corrupted. In that case, the ** sqlite3VdbeRecordCompare() may read up to two varints past the ** end of the allocated buffer before it realizes it is dealing with - ** a corrupt record. Adding the two 0x00 bytes prevents this from causing + ** a corrupt record. Or it might try to read a large integer from the + ** buffer. In any case, eight 0x00 bytes prevents this from causing ** a buffer overread. */ pSample->n = sqlite3_column_bytes(pStmt, 4); - pSample->p = sqlite3DbMallocZero(db, pSample->n + 2); + pSample->p = sqlite3DbMallocZero(db, pSample->n + 8); if( pSample->p==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; @@ -118108,7 +119307,7 @@ SQLITE_PRIVATE int sqlite3AuthCheck( sqlite3 *db = pParse->db; int rc; - /* Don't do any authorization checks if the database is initialising + /* Don't do any authorization checks if the database is initializing ** or if the parser is being invoked from within sqlite3_declare_vtab. */ assert( !IN_RENAME_OBJECT || db->xAuth==0 ); @@ -118409,15 +119608,17 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ pParse->nVtabLock = 0; #endif +#ifndef SQLITE_OMIT_SHARED_CACHE /* Once all the cookies have been verified and transactions opened, ** obtain the required table-locks. This is a no-op unless the ** shared-cache feature is enabled. */ - codeTableLocks(pParse); + if( pParse->nTableLock ) codeTableLocks(pParse); +#endif /* Initialize any AUTOINCREMENT data structures required. */ - sqlite3AutoincrementBegin(pParse); + if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse); /* Code constant expressions that where factored out of inner loops. ** @@ -118930,7 +120131,7 @@ SQLITE_PRIVATE void sqlite3ColumnSetColl( } /* -** Return the collating squence name for a column +** Return the collating sequence name for a column */ SQLITE_PRIVATE const char *sqlite3ColumnColl(Column *pCol){ const char *z; @@ -119688,7 +120889,7 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ } if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); - /* Because keywords GENERATE ALWAYS can be converted into indentifiers + /* Because keywords GENERATE ALWAYS can be converted into identifiers ** by the parser, we can sometimes end up with a typename that ends ** with "generated always". Check for this case and omit the surplus ** text. */ @@ -119909,7 +121110,7 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The parsed expression of the default value */ const char *zStart, /* Start of the default value text */ - const char *zEnd /* First character past end of defaut value text */ + const char *zEnd /* First character past end of default value text */ ){ Table *p; Column *pCol; @@ -120257,7 +121458,7 @@ static int identLength(const char *z){ ** to the specified offset in the buffer and updates *pIdx to refer ** to the first byte after the last byte written before returning. ** -** If the string zSignedIdent consists entirely of alpha-numeric +** If the string zSignedIdent consists entirely of alphanumeric ** characters, does not begin with a digit and is not an SQL keyword, ** then it is copied to the output buffer exactly as it is. Otherwise, ** it is quoted using double-quotes. @@ -120409,7 +121610,7 @@ static void estimateIndexWidth(Index *pIdx){ for(i=0; inColumn; i++){ i16 x = pIdx->aiColumn[i]; assert( xpTable->nCol ); - wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst; + wIndex += x<0 ? 1 : aCol[x].szEst; } pIdx->szIdxRow = sqlite3LogEst(wIndex*4); } @@ -122147,7 +123348,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the table ** is a temp table. If so, set the database to 1. Do not do this - ** if initialising a database schema. + ** if initializing a database schema. */ if( !db->init.busy ){ pTab = sqlite3SrcListLookup(pParse, pTblName); @@ -123804,7 +125005,7 @@ SQLITE_PRIVATE void sqlite3CteDelete(sqlite3 *db, Cte *pCte){ /* ** This routine is invoked once per CTE by the parser while parsing a -** WITH clause. The CTE described by teh third argument is added to +** WITH clause. The CTE described by the third argument is added to ** the WITH clause of the second argument. If the second argument is ** NULL, then a new WITH argument is created. */ @@ -124446,8 +125647,9 @@ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ Table *pTab; assert( pItem && pSrc->nSrc>=1 ); pTab = sqlite3LocateTableItem(pParse, 0, pItem); - sqlite3DeleteTable(pParse->db, pItem->pTab); + if( pItem->pTab ) sqlite3DeleteTable(pParse->db, pItem->pTab); pItem->pTab = pTab; + pItem->fg.notCte = 1; if( pTab ){ pTab->nTabRef++; if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ @@ -124600,7 +125802,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( sqlite3 *db = pParse->db; Expr *pLhs = NULL; /* LHS of IN(SELECT...) operator */ Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ - ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ + ExprList *pEList = NULL; /* Expression list containing only pSelectRowid*/ SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */ Select *pSelect = NULL; /* Complete SELECT tree */ Table *pTab; @@ -124638,14 +125840,20 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( ); }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol>=1 ); if( pPk->nKeyCol==1 ){ - const char *zName = pTab->aCol[pPk->aiColumn[0]].zCnName; + const char *zName; + assert( pPk->aiColumn[0]>=0 && pPk->aiColumn[0]nCol ); + zName = pTab->aCol[pPk->aiColumn[0]].zCnName; pLhs = sqlite3Expr(db, TK_ID, zName); pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); }else{ int i; for(i=0; inKeyCol; i++){ - Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); + Expr *p; + assert( pPk->aiColumn[i]>=0 && pPk->aiColumn[i]nCol ); + p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); pEList = sqlite3ExprListAppend(pParse, pEList, p); } pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -124674,7 +125882,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( pOrderBy,0,pLimit ); - /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ + /* now generate the new WHERE rowid IN clause for the DELETE/UPDATE */ pInClause = sqlite3PExpr(pParse, TK_IN, pLhs, 0); sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; @@ -124903,7 +126111,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( HasRowid(pTab) ){ /* For a rowid table, initialize the RowSet to an empty set */ pPk = 0; - nPk = 1; + assert( nPk==1 ); iRowSet = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); }else{ @@ -124931,7 +126139,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( pWInfo==0 ) goto delete_from_cleanup; eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); - assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF ); + assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF + || OptimizationDisabled(db, SQLITE_OnePass) ); if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse); if( sqlite3WhereUsesDeferredSeek(pWInfo) ){ sqlite3VdbeAddOp1(v, OP_FinishSeek, iTabCur); @@ -125268,9 +126477,11 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ - sqlite3CodeRowTrigger(pParse, pTrigger, - TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel - ); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel + ); + } /* Jump here if the row had already been deleted before any BEFORE ** trigger programs were invoked. Or if a trigger program throws a @@ -125583,6 +126794,42 @@ static void lengthFunc( } } +/* +** Implementation of the octet_length() function +*/ +static void bytelengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 m = sqlite3_context_db_handle(context)->enc<=SQLITE_UTF8 ? 1 : 2; + sqlite3_result_int64(context, sqlite3_value_bytes(argv[0])*m); + break; + } + case SQLITE_TEXT: { + if( sqlite3_value_encoding(argv[0])<=SQLITE_UTF8 ){ + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + }else{ + sqlite3_result_int(context, sqlite3_value_bytes16(argv[0])); + } + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + /* ** Implementation of the abs() function. ** @@ -125859,7 +127106,7 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ }else if( n==0 ){ r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); }else{ - zBuf = sqlite3_mprintf("%.*f",n,r); + zBuf = sqlite3_mprintf("%!.*f",n,r); if( zBuf==0 ){ sqlite3_result_error_nomem(context); return; @@ -126059,7 +127306,7 @@ struct compareInfo { /* ** For LIKE and GLOB matching on EBCDIC machines, assume that every -** character is exactly one byte in size. Also, provde the Utf8Read() +** character is exactly one byte in size. Also, provide the Utf8Read() ** macro for fast reading of the next character in the common case where ** the next character is ASCII. */ @@ -126292,7 +127539,7 @@ SQLITE_API int sqlite3_like_count = 0; /* ** Implementation of the like() SQL function. This function implements -** the build-in LIKE operator. The first argument to the function is the +** the built-in LIKE operator. The first argument to the function is the ** pattern and the second argument is the string. So, the SQL statements: ** ** A LIKE B @@ -126625,6 +127872,7 @@ static void charFunc( *zOut++ = 0x80 + (u8)(c & 0x3F); } \ } + *zOut = 0; sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); } @@ -126678,7 +127926,7 @@ static int strContainsChar(const u8 *zStr, int nStr, u32 ch){ ** decoded and returned as a blob. ** ** If there is only a single argument, then it must consist only of an -** even number of hexadeximal digits. Otherwise, return NULL. +** even number of hexadecimal digits. Otherwise, return NULL. ** ** Or, if there is a second argument, then any character that appears in ** the second argument is also allowed to appear between pairs of hexadecimal @@ -127068,13 +128316,68 @@ static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ */ typedef struct SumCtx SumCtx; struct SumCtx { - double rSum; /* Floating point sum */ - i64 iSum; /* Integer sum */ + double rSum; /* Running sum as as a double */ + double rErr; /* Error term for Kahan-Babushka-Neumaier summation */ + i64 iSum; /* Running sum as a signed integer */ i64 cnt; /* Number of elements summed */ - u8 overflow; /* True if integer overflow seen */ - u8 approx; /* True if non-integer value was input to the sum */ + u8 approx; /* True if any non-integer value was input to the sum */ + u8 ovrfl; /* Integer overflow seen */ }; +/* +** Do one step of the Kahan-Babushka-Neumaier summation. +** +** https://en.wikipedia.org/wiki/Kahan_summation_algorithm +** +** Variables are marked "volatile" to defeat c89 x86 floating point +** optimizations can mess up this algorithm. +*/ +static void kahanBabuskaNeumaierStep( + volatile SumCtx *pSum, + volatile double r +){ + volatile double s = pSum->rSum; + volatile double t = s + r; + if( fabs(s) > fabs(r) ){ + pSum->rErr += (s - t) + r; + }else{ + pSum->rErr += (r - t) + s; + } + pSum->rSum = t; +} + +/* +** Add a (possibly large) integer to the running sum. +*/ +static void kahanBabuskaNeumaierStepInt64(volatile SumCtx *pSum, i64 iVal){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iBig, iSm; + iSm = iVal % 16384; + iBig = iVal - iSm; + kahanBabuskaNeumaierStep(pSum, iBig); + kahanBabuskaNeumaierStep(pSum, iSm); + }else{ + kahanBabuskaNeumaierStep(pSum, (double)iVal); + } +} + +/* +** Initialize the Kahan-Babaska-Neumaier sum from a 64-bit integer +*/ +static void kahanBabuskaNeumaierInit( + volatile SumCtx *p, + i64 iVal +){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iSm = iVal % 16384; + p->rSum = (double)(iVal - iSm); + p->rErr = (double)iSm; + }else{ + p->rSum = (double)iVal; + p->rErr = 0.0; + } +} + /* ** Routines used to compute the sum, average, and total. ** @@ -127094,15 +128397,29 @@ static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){ type = sqlite3_value_numeric_type(argv[0]); if( p && type!=SQLITE_NULL ){ p->cnt++; - if( type==SQLITE_INTEGER ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum += v; - if( (p->approx|p->overflow)==0 && sqlite3AddInt64(&p->iSum, v) ){ - p->approx = p->overflow = 1; + if( p->approx==0 ){ + if( type!=SQLITE_INTEGER ){ + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + }else{ + i64 x = p->iSum; + if( sqlite3AddInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ + p->iSum = x; + }else{ + p->ovrfl = 1; + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + } } }else{ - p->rSum += sqlite3_value_double(argv[0]); - p->approx = 1; + if( type==SQLITE_INTEGER ){ + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + }else{ + p->ovrfl = 0; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + } } } } @@ -127119,13 +128436,18 @@ static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ if( ALWAYS(p) && type!=SQLITE_NULL ){ assert( p->cnt>0 ); p->cnt--; - assert( type==SQLITE_INTEGER || p->approx ); - if( type==SQLITE_INTEGER && p->approx==0 ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum -= v; - p->iSum -= v; + if( !p->approx ){ + p->iSum -= sqlite3_value_int64(argv[0]); + }else if( type==SQLITE_INTEGER ){ + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal!=SMALLEST_INT64 ){ + kahanBabuskaNeumaierStepInt64(p, -iVal); + }else{ + kahanBabuskaNeumaierStepInt64(p, LARGEST_INT64); + kahanBabuskaNeumaierStepInt64(p, 1); + } }else{ - p->rSum -= sqlite3_value_double(argv[0]); + kahanBabuskaNeumaierStep(p, -sqlite3_value_double(argv[0])); } } } @@ -127136,10 +128458,14 @@ static void sumFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - if( p->overflow ){ - sqlite3_result_error(context,"integer overflow",-1); - }else if( p->approx ){ - sqlite3_result_double(context, p->rSum); + if( p->approx ){ + if( p->ovrfl ){ + sqlite3_result_error(context,"integer overflow",-1); + }else if( !sqlite3IsNaN(p->rErr) ){ + sqlite3_result_double(context, p->rSum+p->rErr); + }else{ + sqlite3_result_double(context, p->rSum); + } }else{ sqlite3_result_int64(context, p->iSum); } @@ -127149,14 +128475,29 @@ static void avgFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - sqlite3_result_double(context, p->rSum/(double)p->cnt); + double r; + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + sqlite3_result_double(context, r/(double)p->cnt); } } static void totalFinalize(sqlite3_context *context){ SumCtx *p; + double r = 0.0; p = sqlite3_aggregate_context(context, 0); - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - sqlite3_result_double(context, p ? p->rSum : (double)0); + if( p ){ + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + } + sqlite3_result_double(context, r); } /* @@ -127378,7 +128719,7 @@ static void groupConcatInverse( if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); /* pGCC is always non-NULL since groupConcatStep() will have always - ** run frist to initialize it */ + ** run first to initialize it */ if( ALWAYS(pGCC) ){ int nVS; /* Must call sqlite3_value_text() to convert the argument into text prior @@ -127462,8 +128803,10 @@ SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ ** sensitive. */ SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ + FuncDef *pDef; struct compareInfo *pInfo; int flags; + int nArg; if( caseSensitive ){ pInfo = (struct compareInfo*)&likeInfoAlt; flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE; @@ -127471,10 +128814,13 @@ SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive) pInfo = (struct compareInfo*)&likeInfoNorm; flags = SQLITE_FUNC_LIKE; } - sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3FindFunction(db, "like", 2, SQLITE_UTF8, 0)->funcFlags |= flags; - sqlite3FindFunction(db, "like", 3, SQLITE_UTF8, 0)->funcFlags |= flags; + for(nArg=2; nArg<=3; nArg++){ + sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, + 0, 0, 0, 0, 0); + pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); + pDef->funcFlags |= flags; + pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; + } } /* @@ -127746,6 +129092,37 @@ static void signFunc( sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); } +#ifdef SQLITE_DEBUG +/* +** Implementation of fpdecode(x,y,z) function. +** +** x is a real number that is to be decoded. y is the precision. +** z is the maximum real precision. +*/ +static void fpdecodeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FpDecode s; + double x; + int y, z; + char zBuf[100]; + UNUSED_PARAMETER(argc); + assert( argc==3 ); + x = sqlite3_value_double(argv[0]); + y = sqlite3_value_int(argv[1]); + z = sqlite3_value_int(argv[2]); + sqlite3FpDecode(&s, x, y, z); + if( s.isSpecial==2 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "NaN"); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%c%.*s/%d", s.sign, s.n, s.z, s.iDP); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} +#endif /* SQLITE_DEBUG */ + /* ** All of the FuncDef structures in the aBuiltinFunc[] array above ** to the global function hash table. This occurs at start-time (as @@ -127810,12 +129187,16 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(subtype, 1, 0, 0, subtypeFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), + FUNCTION2(octet_length, 1, 0, 0, bytelengthFunc,SQLITE_FUNC_BYTELEN), FUNCTION(instr, 2, 0, 0, instrFunc ), FUNCTION(printf, -1, 0, 0, printfFunc ), FUNCTION(format, -1, 0, 0, printfFunc ), FUNCTION(unicode, 1, 0, 0, unicodeFunc ), FUNCTION(char, -1, 0, 0, charFunc ), FUNCTION(abs, 1, 0, 0, absFunc ), +#ifdef SQLITE_DEBUG + FUNCTION(fpdecode, 3, 0, 0, fpdecodeFunc ), +#endif #ifndef SQLITE_OMIT_FLOATING_POINT FUNCTION(round, 1, 0, 0, roundFunc ), FUNCTION(round, 2, 0, 0, roundFunc ), @@ -129386,9 +130767,8 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){ if( pFKey->pPrevTo ){ pFKey->pPrevTo->pNextTo = pFKey->pNextTo; }else{ - void *p = (void *)pFKey->pNextTo; - const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo); - sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p); + const char *z = (pFKey->pNextTo ? pFKey->pNextTo->zTo : pFKey->zTo); + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, pFKey->pNextTo); } if( pFKey->pNextTo ){ pFKey->pNextTo->pPrevTo = pFKey->pPrevTo; @@ -129451,8 +130831,10 @@ SQLITE_PRIVATE void sqlite3OpenTable( assert( pParse->pVdbe!=0 ); v = pParse->pVdbe; assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); - sqlite3TableLock(pParse, iDb, pTab->tnum, - (opcode==OP_OpenWrite)?1:0, pTab->zName); + if( !pParse->db->noSharedCache ){ + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + } if( HasRowid(pTab) ){ sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nNVCol); VdbeComment((v, "%s", pTab->zName)); @@ -129581,7 +130963,7 @@ SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ ** For STRICT tables: ** ------------------ ** -** Generate an appropropriate OP_TypeCheck opcode that will verify the +** Generate an appropriate OP_TypeCheck opcode that will verify the ** datatypes against the column definitions in pTab. If iReg==0, that ** means an OP_MakeRecord opcode has already been generated and should be ** the last opcode generated. The new OP_TypeCheck needs to be inserted @@ -130873,7 +132255,7 @@ insert_cleanup: /* This is the Walker callback from sqlite3ExprReferencesUpdatedColumn(). * Set bit 0x01 of pWalker->eCode if pWalker->eCode to 0 and if this ** expression node references any of the -** columns that are being modifed by an UPDATE statement. +** columns that are being modified by an UPDATE statement. */ static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_COLUMN ){ @@ -131096,7 +132478,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int *aiChng, /* column i is unchanged if aiChng[i]<0 */ Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */ ){ - Vdbe *v; /* VDBE under constrution */ + Vdbe *v; /* VDBE under construction */ Index *pIdx; /* Pointer to one of the indices */ Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */ sqlite3 *db; /* Database connection */ @@ -131579,7 +132961,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( pIdx; pIdx = indexIteratorNext(&sIdxIter, &ix) ){ - int regIdx; /* Range of registers hold conent for pIdx */ + int regIdx; /* Range of registers holding content for pIdx */ int regR; /* Range of registers holding conflicting PK */ int iThisCur; /* Cursor for this UNIQUE index */ int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ @@ -132074,6 +133456,8 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( assert( op==OP_OpenRead || op==OP_OpenWrite ); assert( op==OP_OpenWrite || p5==0 ); + assert( piDataCur!=0 ); + assert( piIdxCur!=0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output ** variables *piDataCur and *piIdxCur set to illegal cursor numbers @@ -132086,18 +133470,18 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( assert( v!=0 ); if( iBase<0 ) iBase = pParse->nTab; iDataCur = iBase++; - if( piDataCur ) *piDataCur = iDataCur; + *piDataCur = iDataCur; if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){ sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op); - }else{ + }else if( pParse->db->noSharedCache==0 ){ sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); } - if( piIdxCur ) *piIdxCur = iBase; + *piIdxCur = iBase; for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - if( piDataCur ) *piDataCur = iIdxCur; + *piDataCur = iIdxCur; p5 = 0; } if( aToOpen==0 || aToOpen[i+1] ){ @@ -132395,7 +133779,7 @@ static int xferOptimization( } #endif #ifndef SQLITE_OMIT_FOREIGN_KEY - /* Disallow the transfer optimization if the destination table constains + /* Disallow the transfer optimization if the destination table contains ** any foreign key constraints. This is more restrictive than necessary. ** But the main beneficiary of the transfer optimization is the VACUUM ** command, and the VACUUM command disables foreign key constraints. So @@ -133105,6 +134489,8 @@ struct sqlite3_api_routines { int (*value_encoding)(sqlite3_value*); /* Version 3.41.0 and later */ int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); }; /* @@ -133433,6 +134819,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_value_encoding sqlite3_api->value_encoding /* Version 3.41.0 and later */ #define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -133949,7 +135337,9 @@ static const sqlite3_api_routines sqlite3Apis = { /* Version 3.40.0 and later */ sqlite3_value_encoding, /* Version 3.41.0 and later */ - sqlite3_is_interrupted + sqlite3_is_interrupted, + /* Version 3.43.0 and later */ + sqlite3_stmt_explain }; /* True if x is the directory separator character @@ -134029,6 +135419,10 @@ static int sqlite3LoadExtension( */ if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + /* Do not allow sqlite3_load_extension() to link to a copy of the + ** running application, by passing in an empty filename. */ + if( nMsg==0 ) goto extension_not_found; + handle = sqlite3OsDlOpen(pVfs, zFile); #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; iipParse; db->pParse = &sParse; sParse.db = db; - sParse.pReprepare = pReprepare; + if( pReprepare ){ + sParse.pReprepare = pReprepare; + sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare); + }else{ + assert( sParse.pReprepare==0 ); + } assert( ppStmt && *ppStmt==0 ); if( db->mallocFailed ){ sqlite3ErrorMsg(&sParse, "out of memory"); @@ -139227,7 +140626,7 @@ static Select *findRightmost(Select *p){ ** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT ** ** To preserve historical compatibly, SQLite also accepts a variety -** of other non-standard and in many cases non-sensical join types. +** of other non-standard and in many cases nonsensical join types. ** This routine makes as much sense at it can from the nonsense join ** type and returns a result. Examples of accepted nonsense join types ** include but are not limited to: @@ -139498,7 +140897,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; - /* If this is a NATURAL join, synthesize an approprate USING clause + /* If this is a NATURAL join, synthesize an appropriate USING clause ** to specify which columns should be joined. */ if( pRight->fg.jointype & JT_NATURAL ){ @@ -139714,7 +141113,7 @@ static void pushOntoSorter( ** (3) Some output columns are omitted from the sort record due to ** the SQLITE_ENABLE_SORTER_REFERENCES optimization, or due to the ** SQLITE_ECEL_OMITREF optimization, or due to the - ** SortCtx.pDeferredRowLoad optimiation. In any of these cases + ** SortCtx.pDeferredRowLoad optimization. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy ** values that might not yet exist. */ @@ -139770,7 +141169,7 @@ static void pushOntoSorter( testcase( pKI->nAllField > pKI->nKeyField+2 ); pOp->p4.pKeyInfo = sqlite3KeyInfoFromExprList(pParse,pSort->pOrderBy,nOBSat, pKI->nAllField-pKI->nKeyField-1); - pOp = 0; /* Ensure pOp not used after sqltie3VdbeAddOp3() */ + pOp = 0; /* Ensure pOp not used after sqlite3VdbeAddOp3() */ addrJmp = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); pSort->labelBkOut = sqlite3VdbeMakeLabel(pParse); @@ -139864,7 +141263,7 @@ static void codeOffset( ** The returned value in this case is a copy of parameter iTab. ** ** WHERE_DISTINCT_ORDERED: -** In this case rows are being delivered sorted order. The ephermal +** In this case rows are being delivered sorted order. The ephemeral ** table is not required. Instead, the current set of values ** is compared against previous row. If they match, the new row ** is not distinct and control jumps to VM address addrRepeat. Otherwise, @@ -140293,6 +141692,16 @@ static void selectInnerLoop( testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open @@ -141096,13 +142505,6 @@ SQLITE_PRIVATE void sqlite3GenerateColumnNames( int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ -#ifndef SQLITE_OMIT_EXPLAIN - /* If this is an EXPLAIN, skip this step */ - if( pParse->explain ){ - return; - } -#endif - if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; @@ -141289,7 +142691,7 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( ** kind (maybe a parenthesized subquery in the FROM clause of a larger ** query, or a VIEW, or a CTE). This routine computes type information ** for that Table object based on the Select object that implements the -** subquery. For the purposes of this routine, "type infomation" means: +** subquery. For the purposes of this routine, "type information" means: ** ** * The datatype name, as it might appear in a CREATE TABLE statement ** * Which collating sequence to use for the column @@ -141618,7 +143020,7 @@ static void generateWithRecursiveQuery( int iQueue; /* The Queue table */ int iDistinct = 0; /* To ensure unique results if UNION */ int eDest = SRT_Fifo; /* How to write to Queue */ - SelectDest destQueue; /* SelectDest targetting the Queue table */ + SelectDest destQueue; /* SelectDest targeting the Queue table */ int i; /* Loop counter */ int rc; /* Result code */ ExprList *pOrderBy; /* The ORDER BY clause */ @@ -142218,7 +143620,7 @@ SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ /* ** Code an output subroutine for a coroutine implementation of a -** SELECT statment. +** SELECT statement. ** ** The data to be output is contained in pIn->iSdst. There are ** pIn->nSdst columns to be output. pDest is where the output should @@ -142440,7 +143842,7 @@ static int generateOutputSubroutine( ** ** We call AltB, AeqB, AgtB, EofA, and EofB "subroutines" but they are not ** actually called using Gosub and they do not Return. EofA and EofB loop -** until all data is exhausted then jump to the "end" labe. AltB, AeqB, +** until all data is exhausted then jump to the "end" label. AltB, AeqB, ** and AgtB jump to either L2 or to one of EofA or EofB. */ #ifndef SQLITE_OMIT_COMPOUND_SELECT @@ -142477,7 +143879,7 @@ static int multiSelectOrderBy( int savedOffset; /* Saved value of p->iOffset */ int labelCmpr; /* Label for the start of the merge algorithm */ int labelEnd; /* Label for the end of the overall SELECT stmt */ - int addr1; /* Jump instructions that get retargetted */ + int addr1; /* Jump instructions that get retargeted */ int op; /* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */ KeyInfo *pKeyDup = 0; /* Comparison information for duplicate removal */ KeyInfo *pKeyMerge; /* Comparison information for merging rows */ @@ -142846,11 +144248,14 @@ static Expr *substExpr( #endif { Expr *pNew; - int iColumn = pExpr->iColumn; - Expr *pCopy = pSubst->pEList->a[iColumn].pExpr; + int iColumn; + Expr *pCopy; Expr ifNullRow; + iColumn = pExpr->iColumn; + assert( iColumn>=0 ); assert( pSubst->pEList!=0 && iColumnpEList->nExpr ); assert( pExpr->pRight==0 ); + pCopy = pSubst->pEList->a[iColumn].pExpr; if( sqlite3ExprIsVector(pCopy) ){ sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ @@ -143199,7 +144604,7 @@ static int compoundHasDifferentAffinities(Select *p){ ** (9) If the subquery uses LIMIT then the outer query may not be aggregate. ** ** (**) Restriction (10) was removed from the code on 2005-02-05 but we -** accidently carried the comment forward until 2014-09-15. Original +** accidentally carried the comment forward until 2014-09-15. Original ** constraint: "If the subquery is aggregate then the outer query ** may not use LIMIT." ** @@ -143291,7 +144696,8 @@ static int compoundHasDifferentAffinities(Select *p){ ** (27b) the subquery is a compound query and the RIGHT JOIN occurs ** in any arm of the compound query. (See also (17g).) ** -** (28) The subquery is not a MATERIALIZED CTE. +** (28) The subquery is not a MATERIALIZED CTE. (This is handled +** in the caller before ever reaching this routine.) ** ** ** In this routine, the "p" parameter is a pointer to the outer query. @@ -143401,9 +144807,9 @@ static int flattenSubquery( if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ return 0; /* Restriction (27a) */ } - if( pSubitem->fg.isCte && pSubitem->u2.pCteUse->eM10d==M10d_Yes ){ - return 0; /* (28) */ - } + + /* Condition (28) is blocked by the caller */ + assert( !pSubitem->fg.isCte || pSubitem->u2.pCteUse->eM10d!=M10d_Yes ); /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries @@ -143473,7 +144879,7 @@ static int flattenSubquery( testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; - /* Delete the transient structures associated with thesubquery */ + /* Delete the transient structures associated with the subquery */ pSub1 = pSubitem->pSelect; sqlite3DbFree(db, pSubitem->zDatabase); sqlite3DbFree(db, pSubitem->zName); @@ -143655,7 +145061,7 @@ static int flattenSubquery( ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values ** do not necessarily correspond to columns in SELECT statement pParent, - ** zero them before transfering the ORDER BY clause. + ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this ** function attempts to flatten a compound sub-query into pParent @@ -143715,8 +145121,7 @@ static int flattenSubquery( } } - /* Finially, delete what is left of the subquery and return - ** success. + /* Finally, delete what is left of the subquery and return success. */ sqlite3AggInfoPersistWalkerInit(&w, pParse); sqlite3WalkSelect(&w,pSub1); @@ -143751,7 +145156,7 @@ struct WhereConst { /* ** Add a new entry to the pConst object. Except, do not add duplicate -** pColumn entires. Also, do not add if doing so would not be appropriate. +** pColumn entries. Also, do not add if doing so would not be appropriate. ** ** The caller guarantees the pColumn is a column and pValue is a constant. ** This routine has to do some additional checks before completing the @@ -143937,7 +145342,7 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** SELECT * FROM t1 WHERE a=123 AND b=123; ** ** The two SELECT statements above should return different answers. b=a -** is alway true because the comparison uses numeric affinity, but b=123 +** is always true because the comparison uses numeric affinity, but b=123 ** is false because it uses text affinity and '0123' is not the same as '123'. ** To work around this, the expression tree is not actually changed from ** "b=a" to "b=123" but rather the "a" in "b=a" is tagged with EP_FixedCol @@ -144021,7 +145426,7 @@ static int propagateConstants( ** At the time this function is called it is guaranteed that ** ** * the sub-query uses only one distinct window frame, and -** * that the window frame has a PARTITION BY clase. +** * that the window frame has a PARTITION BY clause. */ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ assert( pSubq->pWin->pPartition ); @@ -144290,12 +145695,12 @@ static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ assert( pItem->pSelect!=0 ); pSub = pItem->pSelect; assert( pSub->pEList->nExpr==pTab->nCol ); - if( (pSub->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ - testcase( pSub->selFlags & SF_Distinct ); - testcase( pSub->selFlags & SF_Aggregate ); - return 0; - } for(pX=pSub; pX; pX=pX->pPrior){ + if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ + testcase( pX->selFlags & SF_Distinct ); + testcase( pX->selFlags & SF_Aggregate ); + return 0; + } if( pX->pPrior && pX->op!=TK_ALL ){ /* This optimization does not work for compound subqueries that ** use UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed. */ @@ -145101,10 +146506,16 @@ static int selectExpander(Walker *pWalker, Select *p){ ** expanded. */ int tableSeen = 0; /* Set to 1 when TABLE matches */ char *zTName = 0; /* text of name of TABLE */ + int iErrOfst; if( pE->op==TK_DOT ){ assert( pE->pLeft!=0 ); assert( !ExprHasProperty(pE->pLeft, EP_IntValue) ); zTName = pE->pLeft->u.zToken; + assert( ExprUseWOfst(pE->pLeft) ); + iErrOfst = pE->pRight->w.iOfst; + }else{ + assert( ExprUseWOfst(pE) ); + iErrOfst = pE->w.iOfst; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; /* Table for this data source */ @@ -145141,6 +146552,7 @@ static int selectExpander(Walker *pWalker, Select *p){ for(ii=0; iinId; ii++){ const char *zUName = pUsing->a[ii].zName; pRight = sqlite3Expr(db, TK_ID, zUName); + sqlite3ExprSetErrorOffset(pRight, iErrOfst); pNew = sqlite3ExprListAppend(pParse, pNew, pRight); if( pNew ){ struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; @@ -145213,6 +146625,7 @@ static int selectExpander(Walker *pWalker, Select *p){ }else{ pExpr = pRight; } + sqlite3ExprSetErrorOffset(pExpr, iErrOfst); pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); if( pNew==0 ){ break; /* OOM */ @@ -145529,7 +146942,7 @@ static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ pExpr->op = TK_AGG_COLUMN; pExpr->iTable = pCol->iTable; pExpr->iColumn = pCol->iColumn; - ExprClearProperty(pExpr, EP_Skip|EP_Collate); + ExprClearProperty(pExpr, EP_Skip|EP_Collate|EP_Unlikely); return WRC_Prune; } @@ -145560,7 +146973,7 @@ static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ ** * The aCol[] and aFunc[] arrays may be modified ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used ** -** After clling this routine: +** After calling this routine: ** ** * The aCol[] and aFunc[] arrays are fixed ** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used @@ -146214,22 +147627,59 @@ SQLITE_PRIVATE int sqlite3Select( ** to a real table */ assert( pTab!=0 ); - /* Convert LEFT JOIN into JOIN if there are terms of the right table - ** of the LEFT JOIN used in the WHERE clause. + /* Try to simplify joins: + ** + ** LEFT JOIN -> JOIN + ** RIGHT JOIN -> JOIN + ** FULL JOIN -> RIGHT JOIN + ** + ** If terms of the i-th table are used in the WHERE clause in such a + ** way that the i-th table cannot be the NULL row of a join, then + ** perform the appropriate simplification. This is called + ** "OUTER JOIN strength reduction" in the SQLite documentation. */ - if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT - && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) + if( (pItem->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 + && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor, + pItem->fg.jointype & JT_LTORJ) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ - TREETRACE(0x1000,pParse,p, - ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); - pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + if( pItem->fg.jointype & JT_LEFT ){ + if( pItem->fg.jointype & JT_RIGHT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to RIGHT-JOIN on term %d\n",i)); + pItem->fg.jointype &= ~JT_LEFT; + }else{ + TREETRACE(0x1000,pParse,p, + ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); + pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + } + } + if( pItem->fg.jointype & JT_LTORJ ){ + for(j=i+1; jnSrc; j++){ + SrcItem *pI2 = &pTabList->a[j]; + if( pI2->fg.jointype & JT_RIGHT ){ + if( pI2->fg.jointype & JT_LEFT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to LEFT-JOIN on term %d\n",j)); + pI2->fg.jointype &= ~JT_RIGHT; + }else{ + TREETRACE(0x1000,pParse,p, + ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); + pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + } + } + } + for(j=pTabList->nSrc-1; j>=i; j--){ + pTabList->a[j].fg.jointype &= ~JT_LTORJ; + if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; + } + } assert( pItem->iCursor>=0 ); unsetJoinExpr(p->pWhere, pItem->iCursor, pTabList->a[0].fg.jointype & JT_LTORJ); } - /* No futher action if this term of the FROM clause is not a subquery */ + /* No further action if this term of the FROM clause is not a subquery */ if( pSub==0 ) continue; /* Catch mismatch in the declared columns of a view and the number of @@ -146240,6 +147690,14 @@ SQLITE_PRIVATE int sqlite3Select( goto select_end; } + /* Do not attempt the usual optimizations (flattening and ORDER BY + ** elimination) on a MATERIALIZED common table expression because + ** a MATERIALIZED common table expression is an optimization fence. + */ + if( pItem->fg.isCte && pItem->u2.pCteUse->eM10d==M10d_Yes ){ + continue; + } + /* Do not try to flatten an aggregate subquery. ** ** Flattening an aggregate subquery is only possible if the outer query @@ -146269,6 +147727,8 @@ SQLITE_PRIVATE int sqlite3Select( ** (a) The outer query has a different ORDER BY clause ** (b) The subquery is part of a join ** See forum post 062d576715d277c8 + ** + ** Also retain the ORDER BY if the OmitOrderBy optimization is disabled. */ if( pSub->pOrderBy!=0 && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ @@ -146483,7 +147943,7 @@ SQLITE_PRIVATE int sqlite3Select( }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){ /* This is a CTE for which materialization code has already been ** generated. Invoke the subroutine to compute the materialization, - ** the make the pItem->iCursor be a copy of the ephemerial table that + ** the make the pItem->iCursor be a copy of the ephemeral table that ** holds the result of the materialization. */ CteUse *pCteUse = pItem->u2.pCteUse; sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); @@ -146866,7 +148326,7 @@ SQLITE_PRIVATE int sqlite3Select( */ if( pGroupBy ){ KeyInfo *pKeyInfo; /* Keying information for the group by clause */ - int addr1; /* A-vs-B comparision jump */ + int addr1; /* A-vs-B comparison jump */ int addrOutputRow; /* Start of subroutine that outputs a result row */ int regOutputRow; /* Return address register for output subroutine */ int addrSetAbort; /* Set the abort flag and return */ @@ -146957,9 +148417,13 @@ SQLITE_PRIVATE int sqlite3Select( int nCol; int nGroupBy; - explainTempTable(pParse, +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp; /* Address of OP_Explain instruction */ +#endif + ExplainQueryPlan2(addrExp, (pParse, 0, "USE TEMP B-TREE FOR %s", (sDistinct.isTnct && (p->selFlags&SF_Distinct)==0) ? - "DISTINCT" : "GROUP BY"); + "DISTINCT" : "GROUP BY" + )); groupBySort = 1; nGroupBy = pGroupBy->nExpr; @@ -146984,18 +148448,23 @@ SQLITE_PRIVATE int sqlite3Select( } pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, 0, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); + sqlite3VdbeScanStatusRange(v, addrExp, sqlite3VdbeCurrentAddr(v)-2, -1); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, sqlite3VdbeCurrentAddr(v), 0); sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); sqlite3VdbeAddOp2(v, OP_SorterSort, pAggInfo->sortingIdx, addrEnd); VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); pAggInfo->useSortingIdx = 1; + sqlite3VdbeScanStatusRange(v, addrExp, -1, sortPTab); + sqlite3VdbeScanStatusRange(v, addrExp, -1, pAggInfo->sortingIdx); } /* If there are entries in pAgggInfo->aFunc[] that contain subexpressions @@ -149246,7 +150715,7 @@ static void updateFromSelect( assert( pTabList->nSrc>1 ); if( pSrc ){ - pSrc->a[0].fg.notCte = 1; + assert( pSrc->a[0].fg.notCte ); pSrc->a[0].iCursor = -1; pSrc->a[0].pTab->nTabRef--; pSrc->a[0].pTab = 0; @@ -149763,7 +151232,7 @@ SQLITE_PRIVATE void sqlite3Update( && !hasFK && !chngKey && !bReplace - && (sNC.ncFlags & NC_Subquery)==0 + && (pWhere==0 || !ExprHasProperty(pWhere, EP_Subquery)) ){ flags |= WHERE_ONEPASS_MULTIROW; } @@ -149835,6 +151304,8 @@ SQLITE_PRIVATE void sqlite3Update( if( !isView ){ int addrOnce = 0; + int iNotUsed1 = 0; + int iNotUsed2 = 0; /* Open every index that needs updating. */ if( eOnePass!=ONEPASS_OFF ){ @@ -149846,7 +151317,7 @@ SQLITE_PRIVATE void sqlite3Update( addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, - aToOpen, 0, 0); + aToOpen, &iNotUsed1, &iNotUsed2); if( addrOnce ){ sqlite3VdbeJumpHereOrPopInst(v, addrOnce); } @@ -150137,8 +151608,10 @@ SQLITE_PRIVATE void sqlite3Update( sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + } /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. @@ -150233,7 +151706,7 @@ static void updateVirtualTable( int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */ int regArg; /* First register in VUpdate arg array */ int regRec; /* Register in which to assemble record */ - int regRowid; /* Register for ephem table rowid */ + int regRowid; /* Register for ephemeral table rowid */ int iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */ int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */ int eOnePass; /* True to use onepass strategy */ @@ -150277,7 +151750,9 @@ static void updateVirtualTable( sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0) ); }else{ - pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); + Expr *pRowExpr = exprRowColumn(pParse, i); + if( pRowExpr ) pRowExpr->op2 = OPFLAG_NOCHNG; + pList = sqlite3ExprListAppend(pParse, pList, pRowExpr); } } @@ -150354,7 +151829,7 @@ static void updateVirtualTable( sqlite3WhereEnd(pWInfo); } - /* Begin scannning through the ephemeral table. */ + /* Begin scanning through the ephemeral table. */ addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v); /* Extract arguments from the current row of the ephemeral table and @@ -150562,7 +152037,7 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( pExpr = &sCol[0]; } for(jj=0; jja[jj].pExpr,pExpr,iCursor)<2 ){ + if( sqlite3ExprCompare(0,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ break; /* Column ii of the index matches column jj of target */ } } @@ -150911,7 +152386,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( ** (possibly synchronous) transaction opened on the main database before ** sqlite3BtreeCopyFile() is called. ** - ** An optimisation would be to use a non-journaled pager. + ** An optimization would be to use a non-journaled pager. ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but ** that actually made the VACUUM run slower. Very little journalling ** actually occurs when doing a vacuum since the vacuum_db is initially @@ -151600,7 +153075,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ ** the information we've collected. ** ** The VM register number pParse->regRowid holds the rowid of an - ** entry in the sqlite_schema table tht was created for this vtab + ** entry in the sqlite_schema table that was created for this vtab ** by sqlite3StartTable(). */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -152344,7 +153819,7 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ ** ** An eponymous virtual table instance is one that is named after its ** module, and more importantly, does not require a CREATE VIRTUAL TABLE -** statement in order to come into existance. Eponymous virtual table +** statement in order to come into existence. Eponymous virtual table ** instances always exist. They cannot be DROP-ed. ** ** Any virtual table module for which xConnect and xCreate are the same @@ -152535,7 +154010,7 @@ typedef struct WhereRightJoin WhereRightJoin; /* ** This object is a header on a block of allocated memory that will be -** automatically freed when its WInfo oject is destructed. +** automatically freed when its WInfo object is destructed. */ struct WhereMemBlock { WhereMemBlock *pNext; /* Next block in the chain */ @@ -152596,7 +154071,7 @@ struct WhereLevel { int iCur; /* The VDBE cursor used by this IN operator */ int addrInTop; /* Top of the IN loop */ int iBase; /* Base register of multi-key index record */ - int nPrefix; /* Number of prior entires in the key */ + int nPrefix; /* Number of prior entries in the key */ u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ } *aInLoop; /* Information about each nested IN operator */ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ @@ -152846,7 +154321,7 @@ struct WhereClause { int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ int nBase; /* Number of terms through the last non-Virtual */ - WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ + WhereTerm *a; /* Each a[] describes a term of the WHERE clause */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ #else @@ -153434,6 +154909,12 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus( if( wsFlags & WHERE_INDEXED ){ sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); } + }else{ + int addr = pSrclist->a[pLvl->iFrom].addrFillSub; + VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); + sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); } } } @@ -153931,7 +155412,7 @@ static int codeAllEqualityTerms( /* Figure out how many memory cells we will need then allocate them. */ regBase = pParse->nMem + 1; - nReg = pLoop->u.btree.nEq + nExtraReg; + nReg = nEq + nExtraReg; pParse->nMem += nReg; zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx)); @@ -153978,9 +155459,6 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } - } - for(j=nSkip; jaLTerm[j]; if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value @@ -154123,7 +155601,7 @@ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ ** 2) transform the expression node to a TK_REGISTER node that reads ** from the newly populated register. ** -** Also, if the node is a TK_COLUMN that does access the table idenified +** Also, if the node is a TK_COLUMN that does access the table identified ** by pCCurHint.iTabCur, and an index is being used (which we will ** know because CCurHint.pIdx!=0) then transform the TK_COLUMN into ** an access of the index rather than the original table. @@ -154741,7 +156219,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( }; assert( TK_LE==TK_GT+1 ); /* Make sure the ordering.. */ assert( TK_LT==TK_GT+2 ); /* ... of the TK_xx values... */ - assert( TK_GE==TK_GT+3 ); /* ... is correcct. */ + assert( TK_GE==TK_GT+3 ); /* ... is correct. */ assert( (pStart->wtFlags & TERM_VNULL)==0 ); testcase( pStart->wtFlags & TERM_VIRTUAL ); @@ -155921,7 +157399,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( ** the WHERE clause of SQL statements. ** ** This file was originally part of where.c but was split out to improve -** readability and editabiliity. This file contains utility routines for +** readability and editability. This file contains utility routines for ** analyzing Expr objects in the WHERE clause. */ /* #include "sqliteInt.h" */ @@ -156137,7 +157615,7 @@ static int isLikeOrGlob( ** range search. The third is because the caller assumes that the pattern ** consists of at least one character after all escapes have been ** removed. */ - if( cnt!=0 && 255!=(u8)z[cnt-1] && (cnt>1 || z[0]!=wc[3]) ){ + if( (cnt>1 || (cnt>0 && z[0]!=wc[3])) && 255!=(u8)z[cnt-1] ){ Expr *pPrefix; /* A "complete" match if the pattern ends with "*" or "%" */ @@ -156710,7 +158188,7 @@ static void exprAnalyzeOrTerm( pOrTerm->leftCursor))==0 ){ /* This term must be of the form t1.a==t2.b where t2 is in the ** chngToIN set but t1 is not. This term will be either preceded - ** or follwed by an inverted copy (t2.b==t1.a). Skip this term + ** or followed by an inverted copy (t2.b==t1.a). Skip this term ** and use its inversion. */ testcase( pOrTerm->wtFlags & TERM_COPIED ); testcase( pOrTerm->wtFlags & TERM_VIRTUAL ); @@ -156972,8 +158450,8 @@ static void exprAnalyze( WhereTerm *pTerm; /* The term to be analyzed */ WhereMaskSet *pMaskSet; /* Set of table index masks */ Expr *pExpr; /* The expression to be analyzed */ - Bitmask prereqLeft; /* Prerequesites of the pExpr->pLeft */ - Bitmask prereqAll; /* Prerequesites of pExpr */ + Bitmask prereqLeft; /* Prerequisites of the pExpr->pLeft */ + Bitmask prereqAll; /* Prerequisites of pExpr */ Bitmask extraRight = 0; /* Extra dependencies on LEFT JOIN */ Expr *pStr1 = 0; /* RHS of LIKE/GLOB operator */ int isComplete = 0; /* RHS of LIKE/GLOB ends with wildcard */ @@ -159534,7 +161012,7 @@ SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCo ** Value pLoop->nOut is currently set to the estimated number of rows ** visited for scanning (a=? AND b=?). This function reduces that estimate ** by some factor to account for the (c BETWEEN ? AND ?) expression based -** on the stat4 data for the index. this scan will be peformed multiple +** on the stat4 data for the index. this scan will be performed multiple ** times (once for each (a,b) combination that matches a=?) is dealt with ** by the caller. ** @@ -160289,7 +161767,7 @@ static WhereLoop **whereLoopFindLesser( ** rSetup. Call this SETUP-INVARIANT */ assert( p->rSetup>=pTemplate->rSetup ); - /* Any loop using an appliation-defined index (or PRIMARY KEY or + /* Any loop using an application-defined index (or PRIMARY KEY or ** UNIQUE constraint) with one or more == constraints is better ** than an automatic index. Unless it is a skip-scan. */ if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 @@ -160316,7 +161794,7 @@ static WhereLoop **whereLoopFindLesser( /* If pTemplate is always better than p, then cause p to be overwritten ** with pTemplate. pTemplate is better than p if: - ** (1) pTemplate has no more dependences than p, and + ** (1) pTemplate has no more dependencies than p, and ** (2) pTemplate has an equal or lower cost than p. */ if( (p->prereq & pTemplate->prereq)==pTemplate->prereq /* (1) */ @@ -160434,7 +161912,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ }else{ /* We will be overwriting WhereLoop p[]. But before we do, first ** go through the rest of the list and delete any other entries besides - ** p[] that are also supplated by pTemplate */ + ** p[] that are also supplanted by pTemplate */ WhereLoop **ppTail = &p->pNextLoop; WhereLoop *pToDel; while( *ppTail ){ @@ -160634,7 +162112,7 @@ static int whereRangeVectorLen( } /* -** Adjust the cost C by the costMult facter T. This only occurs if +** Adjust the cost C by the costMult factor T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT */ #ifdef SQLITE_ENABLE_COSTMULT @@ -160661,7 +162139,7 @@ static int whereLoopAddBtreeIndex( Index *pProbe, /* An index on pSrc */ LogEst nInMul /* log(Number of iterations due to IN) */ ){ - WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyse context */ + WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyze context */ Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection malloc context */ WhereLoop *pNew; /* Template WhereLoop under construction */ @@ -160971,7 +162449,7 @@ static int whereLoopAddBtreeIndex( assert( pSrc->pTab->szTabRow>0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ /* The pProbe->szIdxRow is low for an IPK table since the interior - ** pages are small. Thuse szIdxRow gives a good estimate of seek cost. + ** pages are small. Thus szIdxRow gives a good estimate of seek cost. ** But the leaf pages are full-size, so pProbe->szIdxRow would badly ** under-estimate the scanning cost. */ rCostIdx = pNew->nOut + 16; @@ -161316,7 +162794,7 @@ static SQLITE_NOINLINE u32 whereIsCoveringIndex( */ static int whereLoopAddBtree( WhereLoopBuilder *pBuilder, /* WHERE clause information */ - Bitmask mPrereq /* Extra prerequesites for using this table */ + Bitmask mPrereq /* Extra prerequisites for using this table */ ){ WhereInfo *pWInfo; /* WHERE analysis context */ Index *pProbe; /* An index we are evaluating */ @@ -161823,7 +163301,7 @@ static int whereLoopAddVirtualOne( ** ** Return a pointer to the collation name: ** -** 1. If there is an explicit COLLATE operator on the constaint, return it. +** 1. If there is an explicit COLLATE operator on the constraint, return it. ** ** 2. Else, if the column has an alternative collation, return that. ** @@ -162784,7 +164262,8 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** For joins of 3 or more tables, track the 10 best paths */ mxChoice = (nLoop<=1) ? 1 : (nLoop==2 ? 5 : 10); assert( nLoop<=pWInfo->pTabList->nSrc ); - WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d)\n", nRowEst)); + WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d, nQueryLoop=%d)\n", + nRowEst, pParse->nQueryLoop)); /* If nRowEst is zero and there is an ORDER BY clause, ignore it. In this ** case the purpose of this call is to estimate the number of rows returned @@ -162887,7 +164366,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ); } /* TUNING: Add a small extra penalty (3) to sorting as an - ** extra encouragment to the query planner to select a plan + ** extra encouragement to the query planner to select a plan ** where the rows emerge in the correct order without any sorting ** required. */ rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3; @@ -162903,9 +164382,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ /* TUNING: A full-scan of a VIEW or subquery in the outer loop ** is not so bad. */ - if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 ){ + if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 && nLoop>1 ){ rCost += -10; nOut += -30; + WHERETRACE(0x80,("VIEWSCAN cost reduction for %c\n",pWLoop->cId)); } /* Check to see if pWLoop should be added to the set of @@ -163537,6 +165017,28 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( } } +/* +** Set the reverse-scan order mask to one for all tables in the query +** with the exception of MATERIALIZED common table expressions that have +** their own internal ORDER BY clauses. +** +** This implements the PRAGMA reverse_unordered_selects=ON setting. +** (Also SQLITE_DBCONFIG_REVERSE_SCANORDER). +*/ +static SQLITE_NOINLINE void whereReverseScanOrder(WhereInfo *pWInfo){ + int ii; + for(ii=0; iipTabList->nSrc; ii++){ + SrcItem *pItem = &pWInfo->pTabList->a[ii]; + if( !pItem->fg.isCte + || pItem->u2.pCteUse->eM10d!=M10d_Yes + || NEVER(pItem->pSelect==0) + || pItem->pSelect->pOrderBy==0 + ){ + pWInfo->revMask |= MASKBIT(ii); + } + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -163595,7 +165097,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( ** ** OUTER JOINS ** -** An outer join of tables t1 and t2 is conceptally coded as follows: +** An outer join of tables t1 and t2 is conceptually coded as follows: ** ** foreach row1 in t1 do ** flag = 0 @@ -163750,7 +165252,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ** ** The N-th term of the FROM clause is assigned a bitmask of 1<mallocFailed ) goto whereBeginError; } } + assert( pWInfo->pTabList!=0 ); if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ - pWInfo->revMask = ALLBITS; + whereReverseScanOrder(pWInfo); } if( pParse->nErr ){ goto whereBeginError; @@ -164002,6 +165505,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW) && !IsVirtual(pTabList->a[0].pTab) && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK)) + && OptimizationEnabled(db, SQLITE_OnePass) )){ pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ @@ -164731,7 +166235,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** ** These are the same built-in window functions supported by Postgres. ** Although the behaviour of aggregate window functions (functions that -** can be used as either aggregates or window funtions) allows them to +** can be used as either aggregates or window functions) allows them to ** be implemented using an API, built-in window functions are much more ** esoteric. Additionally, some window functions (e.g. nth_value()) ** may only be implemented by caching the entire partition in memory. @@ -165261,7 +166765,7 @@ static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ ** is the Window object representing the associated OVER clause. This ** function updates the contents of pWin as follows: ** -** * If the OVER clause refered to a named window (as in "max(x) OVER win"), +** * If the OVER clause referred to a named window (as in "max(x) OVER win"), ** search list pList for a matching WINDOW definition, and update pWin ** accordingly. If no such WINDOW clause can be found, leave an error ** in pParse. @@ -165882,7 +167386,7 @@ SQLITE_PRIVATE Window *sqlite3WindowAssemble( } /* -** Window *pWin has just been created from a WINDOW clause. Tokne pBase +** Window *pWin has just been created from a WINDOW clause. Token pBase ** is the base window. Earlier windows from the same WINDOW clause are ** stored in the linked list starting at pWin->pNextWin. This function ** either updates *pWin according to the base specification, or else @@ -166188,7 +167692,7 @@ struct WindowCsrAndReg { ** ** (ORDER BY a, b GROUPS BETWEEN 2 PRECEDING AND 2 FOLLOWING) ** -** The windows functions implmentation caches the input rows in a temp +** The windows functions implementation caches the input rows in a temp ** table, sorted by "a, b" (it actually populates the cache lazily, and ** aggressively removes rows once they are no longer required, but that's ** a mere detail). It keeps three cursors open on the temp table. One @@ -167197,7 +168701,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** ** For the most part, the patterns above are adapted to support UNBOUNDED by ** assuming that it is equivalent to "infinity PRECEDING/FOLLOWING" and -** CURRENT ROW by assuming that it is equivilent to "0 PRECEDING/FOLLOWING". +** CURRENT ROW by assuming that it is equivalent to "0 PRECEDING/FOLLOWING". ** This is optimized of course - branches that will never be taken and ** conditions that are always true are omitted from the VM code. The only ** exceptional case is: @@ -167476,7 +168980,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } /* Allocate registers for the array of values from the sub-query, the - ** samve values in record form, and the rowid used to insert said record + ** same values in record form, and the rowid used to insert said record ** into the ephemeral table. */ regNew = pParse->nMem+1; pParse->nMem += nInput; @@ -167717,7 +169221,8 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( /************** End of window.c **********************************************/ /************** Begin file parse.c *******************************************/ /* This file is automatically generated by Lemon from input grammar -** source file "parse.y". */ +** source file "parse.y". +*/ /* ** 2001-09-15 ** @@ -167734,7 +169239,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( ** The canonical source code to this file ("parse.y") is a Lemon grammar ** file that specifies the input grammar and actions to take while parsing. ** That input file is processed by Lemon to generate a C-language -** implementation of a parser for the given grammer. You might be reading +** implementation of a parser for the given grammar. You might be reading ** this comment as part of the translated C-code. Edits should be made ** to the original parse.y sources. */ @@ -168230,7 +169735,7 @@ typedef union { #define YYFALLBACK 1 #define YYNSTATE 575 #define YYNRULE 403 -#define YYNRULE_WITH_ACTION 340 +#define YYNRULE_WITH_ACTION 338 #define YYNTOKEN 185 #define YY_MAX_SHIFT 574 #define YY_MIN_SHIFTREDUCE 833 @@ -168312,106 +169817,106 @@ static const YYACTIONTYPE yy_action[] = { /* 10 */ 568, 1310, 377, 1289, 408, 562, 562, 562, 568, 409, /* 20 */ 378, 1310, 1272, 41, 41, 41, 41, 208, 1520, 71, /* 30 */ 71, 969, 419, 41, 41, 491, 303, 279, 303, 970, - /* 40 */ 397, 71, 71, 125, 126, 80, 1212, 1212, 1047, 1050, + /* 40 */ 397, 71, 71, 125, 126, 80, 1210, 1210, 1047, 1050, /* 50 */ 1037, 1037, 123, 123, 124, 124, 124, 124, 476, 409, /* 60 */ 1237, 1, 1, 574, 2, 1241, 550, 118, 115, 229, /* 70 */ 317, 480, 146, 480, 524, 118, 115, 229, 529, 1323, - /* 80 */ 417, 523, 142, 125, 126, 80, 1212, 1212, 1047, 1050, + /* 80 */ 417, 523, 142, 125, 126, 80, 1210, 1210, 1047, 1050, /* 90 */ 1037, 1037, 123, 123, 124, 124, 124, 124, 118, 115, /* 100 */ 229, 327, 122, 122, 122, 122, 121, 121, 120, 120, /* 110 */ 120, 119, 116, 444, 284, 284, 284, 284, 442, 442, - /* 120 */ 442, 1561, 376, 1563, 1188, 375, 1159, 565, 1159, 565, - /* 130 */ 409, 1561, 537, 259, 226, 444, 101, 145, 449, 316, + /* 120 */ 442, 1559, 376, 1561, 1186, 375, 1157, 565, 1157, 565, + /* 130 */ 409, 1559, 537, 259, 226, 444, 101, 145, 449, 316, /* 140 */ 559, 240, 122, 122, 122, 122, 121, 121, 120, 120, - /* 150 */ 120, 119, 116, 444, 125, 126, 80, 1212, 1212, 1047, + /* 150 */ 120, 119, 116, 444, 125, 126, 80, 1210, 1210, 1047, /* 160 */ 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, 142, - /* 170 */ 294, 1188, 339, 448, 120, 120, 120, 119, 116, 444, - /* 180 */ 127, 1188, 1189, 1188, 148, 441, 440, 568, 119, 116, + /* 170 */ 294, 1186, 339, 448, 120, 120, 120, 119, 116, 444, + /* 180 */ 127, 1186, 1187, 1186, 148, 441, 440, 568, 119, 116, /* 190 */ 444, 124, 124, 124, 124, 117, 122, 122, 122, 122, /* 200 */ 121, 121, 120, 120, 120, 119, 116, 444, 454, 113, /* 210 */ 13, 13, 546, 122, 122, 122, 122, 121, 121, 120, - /* 220 */ 120, 120, 119, 116, 444, 422, 316, 559, 1188, 1189, - /* 230 */ 1188, 149, 1220, 409, 1220, 124, 124, 124, 124, 122, + /* 220 */ 120, 120, 119, 116, 444, 422, 316, 559, 1186, 1187, + /* 230 */ 1186, 149, 1218, 409, 1218, 124, 124, 124, 124, 122, /* 240 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, /* 250 */ 444, 465, 342, 1034, 1034, 1048, 1051, 125, 126, 80, - /* 260 */ 1212, 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, - /* 270 */ 124, 124, 1275, 522, 222, 1188, 568, 409, 224, 514, + /* 260 */ 1210, 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, + /* 270 */ 124, 124, 1275, 522, 222, 1186, 568, 409, 224, 514, /* 280 */ 175, 82, 83, 122, 122, 122, 122, 121, 121, 120, - /* 290 */ 120, 120, 119, 116, 444, 1005, 16, 16, 1188, 133, - /* 300 */ 133, 125, 126, 80, 1212, 1212, 1047, 1050, 1037, 1037, + /* 290 */ 120, 120, 119, 116, 444, 1005, 16, 16, 1186, 133, + /* 300 */ 133, 125, 126, 80, 1210, 1210, 1047, 1050, 1037, 1037, /* 310 */ 123, 123, 124, 124, 124, 124, 122, 122, 122, 122, /* 320 */ 121, 121, 120, 120, 120, 119, 116, 444, 1038, 546, - /* 330 */ 1188, 373, 1188, 1189, 1188, 252, 1429, 399, 504, 501, + /* 330 */ 1186, 373, 1186, 1187, 1186, 252, 1429, 399, 504, 501, /* 340 */ 500, 111, 560, 566, 4, 924, 924, 433, 499, 340, - /* 350 */ 460, 328, 360, 394, 1233, 1188, 1189, 1188, 563, 568, + /* 350 */ 460, 328, 360, 394, 1231, 1186, 1187, 1186, 563, 568, /* 360 */ 122, 122, 122, 122, 121, 121, 120, 120, 120, 119, - /* 370 */ 116, 444, 284, 284, 369, 1574, 1600, 441, 440, 154, - /* 380 */ 409, 445, 71, 71, 1282, 565, 1217, 1188, 1189, 1188, - /* 390 */ 85, 1219, 271, 557, 543, 515, 1555, 568, 98, 1218, - /* 400 */ 6, 1274, 472, 142, 125, 126, 80, 1212, 1212, 1047, + /* 370 */ 116, 444, 284, 284, 369, 1572, 1598, 441, 440, 154, + /* 380 */ 409, 445, 71, 71, 1282, 565, 1215, 1186, 1187, 1186, + /* 390 */ 85, 1217, 271, 557, 543, 515, 515, 568, 98, 1216, + /* 400 */ 6, 1274, 472, 142, 125, 126, 80, 1210, 1210, 1047, /* 410 */ 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, 550, - /* 420 */ 13, 13, 1024, 507, 1220, 1188, 1220, 549, 109, 109, - /* 430 */ 222, 568, 1234, 175, 568, 427, 110, 197, 445, 569, - /* 440 */ 445, 430, 1546, 1014, 325, 551, 1188, 270, 287, 368, + /* 420 */ 13, 13, 1024, 507, 1218, 1186, 1218, 549, 109, 109, + /* 430 */ 222, 568, 1232, 175, 568, 427, 110, 197, 445, 569, + /* 440 */ 445, 430, 1546, 1014, 325, 551, 1186, 270, 287, 368, /* 450 */ 510, 363, 509, 257, 71, 71, 543, 71, 71, 359, - /* 460 */ 316, 559, 1606, 122, 122, 122, 122, 121, 121, 120, + /* 460 */ 316, 559, 1604, 122, 122, 122, 122, 121, 121, 120, /* 470 */ 120, 120, 119, 116, 444, 1014, 1014, 1016, 1017, 27, - /* 480 */ 284, 284, 1188, 1189, 1188, 1154, 568, 1605, 409, 899, - /* 490 */ 190, 550, 356, 565, 550, 935, 533, 517, 1154, 516, - /* 500 */ 413, 1154, 552, 1188, 1189, 1188, 568, 544, 1548, 51, - /* 510 */ 51, 214, 125, 126, 80, 1212, 1212, 1047, 1050, 1037, - /* 520 */ 1037, 123, 123, 124, 124, 124, 124, 1188, 474, 135, + /* 480 */ 284, 284, 1186, 1187, 1186, 1152, 568, 1603, 409, 899, + /* 490 */ 190, 550, 356, 565, 550, 935, 533, 517, 1152, 516, + /* 500 */ 413, 1152, 552, 1186, 1187, 1186, 568, 544, 544, 51, + /* 510 */ 51, 214, 125, 126, 80, 1210, 1210, 1047, 1050, 1037, + /* 520 */ 1037, 123, 123, 124, 124, 124, 124, 1186, 474, 135, /* 530 */ 135, 409, 284, 284, 1484, 505, 121, 121, 120, 120, - /* 540 */ 120, 119, 116, 444, 1005, 565, 518, 217, 541, 1555, - /* 550 */ 316, 559, 142, 6, 532, 125, 126, 80, 1212, 1212, + /* 540 */ 120, 119, 116, 444, 1005, 565, 518, 217, 541, 541, + /* 550 */ 316, 559, 142, 6, 532, 125, 126, 80, 1210, 1210, /* 560 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, - /* 570 */ 1549, 122, 122, 122, 122, 121, 121, 120, 120, 120, - /* 580 */ 119, 116, 444, 485, 1188, 1189, 1188, 482, 281, 1263, - /* 590 */ 955, 252, 1188, 373, 504, 501, 500, 1188, 340, 570, - /* 600 */ 1188, 570, 409, 292, 499, 955, 874, 191, 480, 316, + /* 570 */ 1548, 122, 122, 122, 122, 121, 121, 120, 120, 120, + /* 580 */ 119, 116, 444, 485, 1186, 1187, 1186, 482, 281, 1263, + /* 590 */ 955, 252, 1186, 373, 504, 501, 500, 1186, 340, 570, + /* 600 */ 1186, 570, 409, 292, 499, 955, 874, 191, 480, 316, /* 610 */ 559, 384, 290, 380, 122, 122, 122, 122, 121, 121, - /* 620 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1212, - /* 630 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 640 */ 124, 409, 394, 1132, 1188, 867, 100, 284, 284, 1188, - /* 650 */ 1189, 1188, 373, 1089, 1188, 1189, 1188, 1188, 1189, 1188, - /* 660 */ 565, 455, 32, 373, 233, 125, 126, 80, 1212, 1212, + /* 620 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 630 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 640 */ 124, 409, 394, 1132, 1186, 867, 100, 284, 284, 1186, + /* 650 */ 1187, 1186, 373, 1089, 1186, 1187, 1186, 1186, 1187, 1186, + /* 660 */ 565, 455, 32, 373, 233, 125, 126, 80, 1210, 1210, /* 670 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, /* 680 */ 1428, 957, 568, 228, 956, 122, 122, 122, 122, 121, - /* 690 */ 121, 120, 120, 120, 119, 116, 444, 1154, 228, 1188, - /* 700 */ 157, 1188, 1189, 1188, 1547, 13, 13, 301, 955, 1228, - /* 710 */ 1154, 153, 409, 1154, 373, 1577, 1172, 5, 369, 1574, - /* 720 */ 429, 1234, 3, 955, 122, 122, 122, 122, 121, 121, - /* 730 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1212, - /* 740 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 750 */ 124, 409, 208, 567, 1188, 1025, 1188, 1189, 1188, 1188, + /* 690 */ 121, 120, 120, 120, 119, 116, 444, 1152, 228, 1186, + /* 700 */ 157, 1186, 1187, 1186, 1547, 13, 13, 301, 955, 1226, + /* 710 */ 1152, 153, 409, 1152, 373, 1575, 1170, 5, 369, 1572, + /* 720 */ 429, 1232, 3, 955, 122, 122, 122, 122, 121, 121, + /* 730 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 740 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 750 */ 124, 409, 208, 567, 1186, 1025, 1186, 1187, 1186, 1186, /* 760 */ 388, 850, 155, 1546, 286, 402, 1094, 1094, 488, 568, - /* 770 */ 465, 342, 1315, 1315, 1546, 125, 126, 80, 1212, 1212, + /* 770 */ 465, 342, 1315, 1315, 1546, 125, 126, 80, 1210, 1210, /* 780 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, /* 790 */ 129, 568, 13, 13, 374, 122, 122, 122, 122, 121, /* 800 */ 121, 120, 120, 120, 119, 116, 444, 302, 568, 453, - /* 810 */ 528, 1188, 1189, 1188, 13, 13, 1188, 1189, 1188, 1293, + /* 810 */ 528, 1186, 1187, 1186, 13, 13, 1186, 1187, 1186, 1293, /* 820 */ 463, 1263, 409, 1313, 1313, 1546, 1010, 453, 452, 200, /* 830 */ 299, 71, 71, 1261, 122, 122, 122, 122, 121, 121, - /* 840 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1212, - /* 850 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 860 */ 124, 409, 227, 1069, 1154, 284, 284, 419, 312, 278, - /* 870 */ 278, 285, 285, 1415, 406, 405, 382, 1154, 565, 568, - /* 880 */ 1154, 1191, 565, 1594, 565, 125, 126, 80, 1212, 1212, + /* 840 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 850 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 860 */ 124, 409, 227, 1069, 1152, 284, 284, 419, 312, 278, + /* 870 */ 278, 285, 285, 1415, 406, 405, 382, 1152, 565, 568, + /* 880 */ 1152, 1189, 565, 1592, 565, 125, 126, 80, 1210, 1210, /* 890 */ 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, 124, /* 900 */ 453, 1476, 13, 13, 1530, 122, 122, 122, 122, 121, /* 910 */ 121, 120, 120, 120, 119, 116, 444, 201, 568, 354, - /* 920 */ 1580, 574, 2, 1241, 838, 839, 840, 1556, 317, 1207, - /* 930 */ 146, 6, 409, 255, 254, 253, 206, 1323, 9, 1191, + /* 920 */ 1578, 574, 2, 1241, 838, 839, 840, 1554, 317, 1205, + /* 930 */ 146, 6, 409, 255, 254, 253, 206, 1323, 9, 1189, /* 940 */ 262, 71, 71, 424, 122, 122, 122, 122, 121, 121, - /* 950 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1212, - /* 960 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 970 */ 124, 568, 284, 284, 568, 1208, 409, 573, 313, 1241, - /* 980 */ 349, 1292, 352, 419, 317, 565, 146, 491, 525, 1637, + /* 950 */ 120, 120, 120, 119, 116, 444, 125, 126, 80, 1210, + /* 960 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 970 */ 124, 568, 284, 284, 568, 1206, 409, 573, 313, 1241, + /* 980 */ 349, 1292, 352, 419, 317, 565, 146, 491, 525, 1635, /* 990 */ 395, 371, 491, 1323, 70, 70, 1291, 71, 71, 240, - /* 1000 */ 1321, 104, 80, 1212, 1212, 1047, 1050, 1037, 1037, 123, + /* 1000 */ 1321, 104, 80, 1210, 1210, 1047, 1050, 1037, 1037, 123, /* 1010 */ 123, 124, 124, 124, 124, 122, 122, 122, 122, 121, /* 1020 */ 121, 120, 120, 120, 119, 116, 444, 1110, 284, 284, - /* 1030 */ 428, 448, 1519, 1208, 439, 284, 284, 1483, 1348, 311, + /* 1030 */ 428, 448, 1519, 1206, 439, 284, 284, 1483, 1348, 311, /* 1040 */ 474, 565, 1111, 969, 491, 491, 217, 1259, 565, 1532, /* 1050 */ 568, 970, 207, 568, 1024, 240, 383, 1112, 519, 122, /* 1060 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116, @@ -168419,29 +169924,29 @@ static const YYACTIONTYPE yy_action[] = { /* 1080 */ 1489, 568, 284, 284, 97, 526, 491, 448, 911, 1322, /* 1090 */ 1318, 545, 409, 284, 284, 565, 151, 209, 1489, 1491, /* 1100 */ 262, 450, 55, 55, 56, 56, 565, 1014, 1014, 1016, - /* 1110 */ 443, 332, 409, 527, 12, 295, 125, 126, 80, 1212, - /* 1120 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 1130 */ 124, 347, 409, 862, 1528, 1208, 125, 126, 80, 1212, - /* 1140 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, - /* 1150 */ 124, 1133, 1635, 474, 1635, 371, 125, 114, 80, 1212, - /* 1160 */ 1212, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1110 */ 443, 332, 409, 527, 12, 295, 125, 126, 80, 1210, + /* 1120 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1130 */ 124, 347, 409, 862, 1528, 1206, 125, 126, 80, 1210, + /* 1140 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, + /* 1150 */ 124, 1133, 1633, 474, 1633, 371, 125, 114, 80, 1210, + /* 1160 */ 1210, 1047, 1050, 1037, 1037, 123, 123, 124, 124, 124, /* 1170 */ 124, 1489, 329, 474, 331, 122, 122, 122, 122, 121, /* 1180 */ 121, 120, 120, 120, 119, 116, 444, 203, 1415, 568, - /* 1190 */ 1290, 862, 464, 1208, 436, 122, 122, 122, 122, 121, - /* 1200 */ 121, 120, 120, 120, 119, 116, 444, 553, 1133, 1636, - /* 1210 */ 539, 1636, 15, 15, 890, 122, 122, 122, 122, 121, + /* 1190 */ 1290, 862, 464, 1206, 436, 122, 122, 122, 122, 121, + /* 1200 */ 121, 120, 120, 120, 119, 116, 444, 553, 1133, 1634, + /* 1210 */ 539, 1634, 15, 15, 890, 122, 122, 122, 122, 121, /* 1220 */ 121, 120, 120, 120, 119, 116, 444, 568, 298, 538, - /* 1230 */ 1131, 1415, 1553, 1554, 1327, 409, 6, 6, 1165, 1264, + /* 1230 */ 1131, 1415, 1552, 1553, 1327, 409, 6, 6, 1163, 1264, /* 1240 */ 415, 320, 284, 284, 1415, 508, 565, 525, 300, 457, /* 1250 */ 43, 43, 568, 891, 12, 565, 330, 478, 425, 407, - /* 1260 */ 126, 80, 1212, 1212, 1047, 1050, 1037, 1037, 123, 123, - /* 1270 */ 124, 124, 124, 124, 568, 57, 57, 288, 1188, 1415, - /* 1280 */ 496, 458, 392, 392, 391, 273, 389, 1131, 1552, 847, - /* 1290 */ 1165, 407, 6, 568, 321, 1154, 470, 44, 44, 1551, - /* 1300 */ 1110, 426, 234, 6, 323, 256, 540, 256, 1154, 431, - /* 1310 */ 568, 1154, 322, 17, 487, 1111, 58, 58, 122, 122, + /* 1260 */ 126, 80, 1210, 1210, 1047, 1050, 1037, 1037, 123, 123, + /* 1270 */ 124, 124, 124, 124, 568, 57, 57, 288, 1186, 1415, + /* 1280 */ 496, 458, 392, 392, 391, 273, 389, 1131, 1551, 847, + /* 1290 */ 1163, 407, 6, 568, 321, 1152, 470, 44, 44, 1550, + /* 1300 */ 1110, 426, 234, 6, 323, 256, 540, 256, 1152, 431, + /* 1310 */ 568, 1152, 322, 17, 487, 1111, 58, 58, 122, 122, /* 1320 */ 122, 122, 121, 121, 120, 120, 120, 119, 116, 444, - /* 1330 */ 1112, 216, 481, 59, 59, 1188, 1189, 1188, 111, 560, + /* 1330 */ 1112, 216, 481, 59, 59, 1186, 1187, 1186, 111, 560, /* 1340 */ 324, 4, 236, 456, 526, 568, 237, 456, 568, 437, /* 1350 */ 168, 556, 420, 141, 479, 563, 568, 293, 568, 1091, /* 1360 */ 568, 293, 568, 1091, 531, 568, 870, 8, 60, 60, @@ -168455,7 +169960,7 @@ static const YYACTIONTYPE yy_action[] = { /* 1440 */ 1014, 132, 132, 67, 67, 568, 467, 568, 930, 471, /* 1450 */ 1360, 283, 226, 929, 315, 1359, 407, 568, 459, 407, /* 1460 */ 1014, 1014, 1016, 239, 407, 86, 213, 1346, 52, 52, - /* 1470 */ 68, 68, 1014, 1014, 1016, 1017, 27, 1579, 1176, 447, + /* 1470 */ 68, 68, 1014, 1014, 1016, 1017, 27, 1577, 1174, 447, /* 1480 */ 69, 69, 288, 97, 108, 1535, 106, 392, 392, 391, /* 1490 */ 273, 389, 568, 877, 847, 881, 568, 111, 560, 466, /* 1500 */ 4, 568, 152, 30, 38, 568, 1128, 234, 396, 323, @@ -168464,7 +169969,7 @@ static const YYACTIONTYPE yy_action[] = { /* 1530 */ 568, 289, 1508, 568, 31, 1507, 568, 445, 338, 483, /* 1540 */ 100, 54, 54, 344, 72, 72, 296, 236, 1076, 557, /* 1550 */ 445, 877, 1356, 134, 134, 168, 73, 73, 141, 161, - /* 1560 */ 161, 1568, 557, 535, 568, 319, 568, 348, 536, 1007, + /* 1560 */ 161, 1566, 557, 535, 568, 319, 568, 348, 536, 1007, /* 1570 */ 473, 261, 261, 889, 888, 235, 535, 568, 1024, 568, /* 1580 */ 475, 534, 261, 367, 109, 109, 521, 136, 136, 130, /* 1590 */ 130, 1024, 110, 366, 445, 569, 445, 109, 109, 1014, @@ -168472,7 +169977,7 @@ static const YYACTIONTYPE yy_action[] = { /* 1610 */ 410, 351, 1014, 568, 353, 316, 559, 568, 343, 568, /* 1620 */ 100, 497, 357, 258, 100, 896, 897, 140, 140, 355, /* 1630 */ 1306, 1014, 1014, 1016, 1017, 27, 139, 139, 362, 451, - /* 1640 */ 137, 137, 138, 138, 1014, 1014, 1016, 1017, 27, 1176, + /* 1640 */ 137, 137, 138, 138, 1014, 1014, 1016, 1017, 27, 1174, /* 1650 */ 447, 568, 372, 288, 111, 560, 1018, 4, 392, 392, /* 1660 */ 391, 273, 389, 568, 1137, 847, 568, 1072, 568, 258, /* 1670 */ 492, 563, 568, 211, 75, 75, 555, 960, 234, 261, @@ -168480,44 +169985,44 @@ static const YYACTIONTYPE yy_action[] = { /* 1690 */ 74, 42, 42, 1369, 445, 48, 48, 1414, 563, 972, /* 1700 */ 973, 1088, 1087, 1088, 1087, 860, 557, 150, 928, 1342, /* 1710 */ 113, 1354, 554, 1419, 1018, 1271, 1262, 1250, 236, 1249, - /* 1720 */ 1251, 445, 1587, 1339, 308, 276, 168, 309, 11, 141, + /* 1720 */ 1251, 445, 1585, 1339, 308, 276, 168, 309, 11, 141, /* 1730 */ 393, 310, 232, 557, 1401, 1024, 335, 291, 1396, 219, /* 1740 */ 336, 109, 109, 934, 297, 1406, 235, 341, 477, 110, /* 1750 */ 502, 445, 569, 445, 1389, 1405, 1014, 400, 1289, 365, /* 1760 */ 223, 1480, 1024, 1479, 1351, 1352, 1350, 1349, 109, 109, - /* 1770 */ 204, 1590, 1228, 558, 265, 218, 110, 205, 445, 569, + /* 1770 */ 204, 1588, 1226, 558, 265, 218, 110, 205, 445, 569, /* 1780 */ 445, 410, 387, 1014, 1527, 179, 316, 559, 1014, 1014, - /* 1790 */ 1016, 1017, 27, 230, 1525, 1225, 79, 560, 85, 4, + /* 1790 */ 1016, 1017, 27, 230, 1525, 1223, 79, 560, 85, 4, /* 1800 */ 418, 215, 548, 81, 84, 188, 1402, 173, 181, 461, /* 1810 */ 451, 35, 462, 563, 183, 1014, 1014, 1016, 1017, 27, /* 1820 */ 184, 1485, 185, 186, 495, 242, 98, 398, 1408, 36, /* 1830 */ 1407, 484, 91, 469, 401, 1410, 445, 192, 1474, 246, /* 1840 */ 1496, 490, 346, 277, 248, 196, 493, 511, 557, 350, /* 1850 */ 1252, 249, 250, 403, 1309, 1308, 111, 560, 432, 4, - /* 1860 */ 1307, 1300, 93, 1604, 881, 1603, 224, 404, 434, 520, - /* 1870 */ 263, 435, 1573, 563, 1279, 1278, 364, 1024, 306, 1277, - /* 1880 */ 264, 1602, 1559, 109, 109, 370, 1299, 307, 1558, 438, + /* 1860 */ 1307, 1300, 93, 1602, 881, 1601, 224, 404, 434, 520, + /* 1870 */ 263, 435, 1571, 563, 1279, 1278, 364, 1024, 306, 1277, + /* 1880 */ 264, 1600, 1557, 109, 109, 370, 1299, 307, 1556, 438, /* 1890 */ 128, 110, 1374, 445, 569, 445, 445, 546, 1014, 10, /* 1900 */ 1461, 105, 381, 1373, 34, 571, 99, 1332, 557, 314, - /* 1910 */ 1182, 530, 272, 274, 379, 210, 1331, 547, 385, 386, + /* 1910 */ 1180, 530, 272, 274, 379, 210, 1331, 547, 385, 386, /* 1920 */ 275, 572, 1247, 1242, 411, 412, 1512, 165, 178, 1513, /* 1930 */ 1014, 1014, 1016, 1017, 27, 1511, 1510, 1024, 78, 147, /* 1940 */ 166, 220, 221, 109, 109, 834, 304, 167, 446, 212, /* 1950 */ 318, 110, 231, 445, 569, 445, 144, 1086, 1014, 1084, - /* 1960 */ 326, 180, 169, 1207, 182, 334, 238, 913, 241, 1100, + /* 1960 */ 326, 180, 169, 1205, 182, 334, 238, 913, 241, 1100, /* 1970 */ 187, 170, 171, 421, 87, 88, 423, 189, 89, 90, /* 1980 */ 172, 1103, 243, 1099, 244, 158, 18, 245, 345, 247, - /* 1990 */ 1014, 1014, 1016, 1017, 27, 261, 1092, 193, 1222, 489, + /* 1990 */ 1014, 1014, 1016, 1017, 27, 261, 1092, 193, 1220, 489, /* 2000 */ 194, 37, 366, 849, 494, 251, 195, 506, 92, 19, /* 2010 */ 498, 358, 20, 503, 879, 361, 94, 892, 305, 159, - /* 2020 */ 513, 39, 95, 1170, 160, 1053, 964, 1139, 96, 174, - /* 2030 */ 1138, 225, 280, 282, 198, 958, 113, 1160, 1156, 260, - /* 2040 */ 21, 22, 23, 1158, 1164, 1163, 1144, 24, 33, 25, + /* 2020 */ 513, 39, 95, 1168, 160, 1053, 964, 1139, 96, 174, + /* 2030 */ 1138, 225, 280, 282, 198, 958, 113, 1158, 1154, 260, + /* 2040 */ 21, 22, 23, 1156, 1162, 1161, 1143, 24, 33, 25, /* 2050 */ 202, 542, 26, 100, 1067, 102, 1054, 103, 7, 1052, /* 2060 */ 1056, 1109, 1057, 1108, 266, 267, 28, 40, 390, 1019, - /* 2070 */ 861, 112, 29, 564, 1178, 1177, 268, 176, 143, 923, + /* 2070 */ 861, 112, 29, 564, 1176, 1175, 268, 176, 143, 923, /* 2080 */ 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, - /* 2090 */ 1238, 1238, 1238, 1238, 269, 1595, + /* 2090 */ 1238, 1238, 1238, 1238, 269, 1593, }; static const YYCODETYPE yy_lookahead[] = { /* 0 */ 193, 193, 193, 274, 275, 276, 193, 274, 275, 276, @@ -168860,14 +170365,14 @@ static const short yy_reduce_ofst[] = { /* 400 */ 1722, 1723, 1733, 1717, 1724, 1727, 1728, 1725, 1740, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1641, 1641, 1641, 1469, 1236, 1347, 1236, 1236, 1236, 1469, + /* 0 */ 1639, 1639, 1639, 1469, 1236, 1347, 1236, 1236, 1236, 1469, /* 10 */ 1469, 1469, 1236, 1377, 1377, 1522, 1269, 1236, 1236, 1236, /* 20 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1468, 1236, 1236, - /* 30 */ 1236, 1236, 1557, 1557, 1236, 1236, 1236, 1236, 1236, 1236, + /* 30 */ 1236, 1236, 1555, 1555, 1236, 1236, 1236, 1236, 1236, 1236, /* 40 */ 1236, 1236, 1386, 1236, 1393, 1236, 1236, 1236, 1236, 1236, /* 50 */ 1470, 1471, 1236, 1236, 1236, 1521, 1523, 1486, 1400, 1399, /* 60 */ 1398, 1397, 1504, 1365, 1391, 1384, 1388, 1465, 1466, 1464, - /* 70 */ 1619, 1471, 1470, 1236, 1387, 1433, 1449, 1432, 1236, 1236, + /* 70 */ 1617, 1471, 1470, 1236, 1387, 1433, 1449, 1432, 1236, 1236, /* 80 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 90 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 100 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, @@ -168876,47 +170381,47 @@ static const YYACTIONTYPE yy_default[] = { /* 130 */ 1441, 1448, 1447, 1446, 1455, 1445, 1442, 1435, 1434, 1436, /* 140 */ 1437, 1236, 1236, 1260, 1236, 1236, 1257, 1311, 1236, 1236, /* 150 */ 1236, 1236, 1236, 1541, 1540, 1236, 1438, 1236, 1269, 1427, - /* 160 */ 1426, 1452, 1439, 1451, 1450, 1529, 1593, 1592, 1487, 1236, - /* 170 */ 1236, 1236, 1236, 1236, 1236, 1557, 1236, 1236, 1236, 1236, + /* 160 */ 1426, 1452, 1439, 1451, 1450, 1529, 1591, 1590, 1487, 1236, + /* 170 */ 1236, 1236, 1236, 1236, 1236, 1555, 1236, 1236, 1236, 1236, /* 180 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 190 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1367, - /* 200 */ 1557, 1557, 1236, 1269, 1557, 1557, 1368, 1368, 1265, 1265, + /* 200 */ 1555, 1555, 1236, 1269, 1555, 1555, 1368, 1368, 1265, 1265, /* 210 */ 1371, 1236, 1536, 1338, 1338, 1338, 1338, 1347, 1338, 1236, /* 220 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 230 */ 1236, 1236, 1236, 1236, 1526, 1524, 1236, 1236, 1236, 1236, /* 240 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 250 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 260 */ 1236, 1236, 1236, 1343, 1236, 1236, 1236, 1236, 1236, 1236, - /* 270 */ 1236, 1236, 1236, 1236, 1236, 1586, 1236, 1499, 1325, 1343, - /* 280 */ 1343, 1343, 1343, 1345, 1326, 1324, 1337, 1270, 1243, 1633, - /* 290 */ 1403, 1392, 1344, 1392, 1630, 1390, 1403, 1403, 1390, 1403, - /* 300 */ 1344, 1630, 1286, 1608, 1281, 1377, 1377, 1377, 1367, 1367, - /* 310 */ 1367, 1367, 1371, 1371, 1467, 1344, 1337, 1236, 1633, 1633, - /* 320 */ 1353, 1353, 1632, 1632, 1353, 1487, 1616, 1412, 1314, 1320, - /* 330 */ 1320, 1320, 1320, 1353, 1254, 1390, 1616, 1616, 1390, 1412, - /* 340 */ 1314, 1390, 1314, 1390, 1353, 1254, 1503, 1627, 1353, 1254, + /* 270 */ 1236, 1236, 1236, 1236, 1236, 1584, 1236, 1499, 1325, 1343, + /* 280 */ 1343, 1343, 1343, 1345, 1326, 1324, 1337, 1270, 1243, 1631, + /* 290 */ 1403, 1392, 1344, 1392, 1628, 1390, 1403, 1403, 1390, 1403, + /* 300 */ 1344, 1628, 1286, 1606, 1281, 1377, 1377, 1377, 1367, 1367, + /* 310 */ 1367, 1367, 1371, 1371, 1467, 1344, 1337, 1236, 1631, 1631, + /* 320 */ 1353, 1353, 1630, 1630, 1353, 1487, 1614, 1412, 1314, 1320, + /* 330 */ 1320, 1320, 1320, 1353, 1254, 1390, 1614, 1614, 1390, 1412, + /* 340 */ 1314, 1390, 1314, 1390, 1353, 1254, 1503, 1625, 1353, 1254, /* 350 */ 1477, 1353, 1254, 1353, 1254, 1477, 1312, 1312, 1312, 1301, - /* 360 */ 1236, 1236, 1477, 1312, 1286, 1312, 1301, 1312, 1312, 1575, - /* 370 */ 1236, 1481, 1481, 1477, 1353, 1567, 1567, 1380, 1380, 1385, - /* 380 */ 1371, 1472, 1353, 1236, 1385, 1383, 1381, 1390, 1304, 1589, - /* 390 */ 1589, 1585, 1585, 1585, 1638, 1638, 1536, 1601, 1269, 1269, - /* 400 */ 1269, 1269, 1601, 1288, 1288, 1270, 1270, 1269, 1601, 1236, - /* 410 */ 1236, 1236, 1236, 1236, 1236, 1596, 1236, 1531, 1488, 1357, + /* 360 */ 1236, 1236, 1477, 1312, 1286, 1312, 1301, 1312, 1312, 1573, + /* 370 */ 1236, 1481, 1481, 1477, 1353, 1565, 1565, 1380, 1380, 1385, + /* 380 */ 1371, 1472, 1353, 1236, 1385, 1383, 1381, 1390, 1304, 1587, + /* 390 */ 1587, 1583, 1583, 1583, 1636, 1636, 1536, 1599, 1269, 1269, + /* 400 */ 1269, 1269, 1599, 1288, 1288, 1270, 1270, 1269, 1599, 1236, + /* 410 */ 1236, 1236, 1236, 1236, 1236, 1594, 1236, 1531, 1488, 1357, /* 420 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 430 */ 1236, 1236, 1236, 1236, 1542, 1236, 1236, 1236, 1236, 1236, /* 440 */ 1236, 1236, 1236, 1236, 1236, 1417, 1236, 1239, 1533, 1236, /* 450 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1394, 1395, 1358, /* 460 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1409, 1236, 1236, /* 470 */ 1236, 1404, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, - /* 480 */ 1629, 1236, 1236, 1236, 1236, 1236, 1236, 1502, 1501, 1236, + /* 480 */ 1627, 1236, 1236, 1236, 1236, 1236, 1236, 1502, 1501, 1236, /* 490 */ 1236, 1355, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 500 */ 1236, 1236, 1236, 1236, 1236, 1284, 1236, 1236, 1236, 1236, /* 510 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, /* 520 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1382, /* 530 */ 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, - /* 540 */ 1236, 1236, 1236, 1236, 1572, 1372, 1236, 1236, 1236, 1236, - /* 550 */ 1620, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, - /* 560 */ 1236, 1236, 1236, 1236, 1236, 1612, 1328, 1418, 1236, 1421, + /* 540 */ 1236, 1236, 1236, 1236, 1570, 1372, 1236, 1236, 1236, 1236, + /* 550 */ 1618, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, 1236, + /* 560 */ 1236, 1236, 1236, 1236, 1236, 1610, 1328, 1418, 1236, 1421, /* 570 */ 1258, 1236, 1248, 1236, 1236, }; /********** End of lemon-generated parsing tables *****************************/ @@ -169845,100 +171350,100 @@ static const char *const yyRuleName[] = { /* 306 */ "wqitem ::= nm eidlist_opt wqas LP select RP", /* 307 */ "wqlist ::= wqitem", /* 308 */ "wqlist ::= wqlist COMMA wqitem", - /* 309 */ "windowdefn_list ::= windowdefn", - /* 310 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", - /* 311 */ "windowdefn ::= nm AS LP window RP", - /* 312 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", - /* 313 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", - /* 314 */ "window ::= ORDER BY sortlist frame_opt", - /* 315 */ "window ::= nm ORDER BY sortlist frame_opt", - /* 316 */ "window ::= frame_opt", - /* 317 */ "window ::= nm frame_opt", - /* 318 */ "frame_opt ::=", - /* 319 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", - /* 320 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", - /* 321 */ "range_or_rows ::= RANGE|ROWS|GROUPS", - /* 322 */ "frame_bound_s ::= frame_bound", - /* 323 */ "frame_bound_s ::= UNBOUNDED PRECEDING", - /* 324 */ "frame_bound_e ::= frame_bound", - /* 325 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", - /* 326 */ "frame_bound ::= expr PRECEDING|FOLLOWING", - /* 327 */ "frame_bound ::= CURRENT ROW", - /* 328 */ "frame_exclude_opt ::=", - /* 329 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", - /* 330 */ "frame_exclude ::= NO OTHERS", - /* 331 */ "frame_exclude ::= CURRENT ROW", - /* 332 */ "frame_exclude ::= GROUP|TIES", - /* 333 */ "window_clause ::= WINDOW windowdefn_list", - /* 334 */ "filter_over ::= filter_clause over_clause", - /* 335 */ "filter_over ::= over_clause", - /* 336 */ "filter_over ::= filter_clause", - /* 337 */ "over_clause ::= OVER LP window RP", - /* 338 */ "over_clause ::= OVER nm", - /* 339 */ "filter_clause ::= FILTER LP WHERE expr RP", - /* 340 */ "input ::= cmdlist", - /* 341 */ "cmdlist ::= cmdlist ecmd", - /* 342 */ "cmdlist ::= ecmd", - /* 343 */ "ecmd ::= SEMI", - /* 344 */ "ecmd ::= cmdx SEMI", - /* 345 */ "ecmd ::= explain cmdx SEMI", - /* 346 */ "trans_opt ::=", - /* 347 */ "trans_opt ::= TRANSACTION", - /* 348 */ "trans_opt ::= TRANSACTION nm", - /* 349 */ "savepoint_opt ::= SAVEPOINT", - /* 350 */ "savepoint_opt ::=", - /* 351 */ "cmd ::= create_table create_table_args", - /* 352 */ "table_option_set ::= table_option", - /* 353 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 354 */ "columnlist ::= columnname carglist", - /* 355 */ "nm ::= ID|INDEXED|JOIN_KW", - /* 356 */ "nm ::= STRING", - /* 357 */ "typetoken ::= typename", - /* 358 */ "typename ::= ID|STRING", - /* 359 */ "signed ::= plus_num", - /* 360 */ "signed ::= minus_num", - /* 361 */ "carglist ::= carglist ccons", - /* 362 */ "carglist ::=", - /* 363 */ "ccons ::= NULL onconf", - /* 364 */ "ccons ::= GENERATED ALWAYS AS generated", - /* 365 */ "ccons ::= AS generated", - /* 366 */ "conslist_opt ::= COMMA conslist", - /* 367 */ "conslist ::= conslist tconscomma tcons", - /* 368 */ "conslist ::= tcons", - /* 369 */ "tconscomma ::=", - /* 370 */ "defer_subclause_opt ::= defer_subclause", - /* 371 */ "resolvetype ::= raisetype", - /* 372 */ "selectnowith ::= oneselect", - /* 373 */ "oneselect ::= values", - /* 374 */ "sclp ::= selcollist COMMA", - /* 375 */ "as ::= ID|STRING", - /* 376 */ "indexed_opt ::= indexed_by", - /* 377 */ "returning ::=", - /* 378 */ "expr ::= term", - /* 379 */ "likeop ::= LIKE_KW|MATCH", - /* 380 */ "case_operand ::= expr", - /* 381 */ "exprlist ::= nexprlist", - /* 382 */ "nmnum ::= plus_num", - /* 383 */ "nmnum ::= nm", - /* 384 */ "nmnum ::= ON", - /* 385 */ "nmnum ::= DELETE", - /* 386 */ "nmnum ::= DEFAULT", - /* 387 */ "plus_num ::= INTEGER|FLOAT", - /* 388 */ "foreach_clause ::=", - /* 389 */ "foreach_clause ::= FOR EACH ROW", - /* 390 */ "trnm ::= nm", - /* 391 */ "tridxby ::=", - /* 392 */ "database_kw_opt ::= DATABASE", - /* 393 */ "database_kw_opt ::=", - /* 394 */ "kwcolumn_opt ::=", - /* 395 */ "kwcolumn_opt ::= COLUMNKW", - /* 396 */ "vtabarglist ::= vtabarg", - /* 397 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 398 */ "vtabarg ::= vtabarg vtabargtoken", - /* 399 */ "anylist ::=", - /* 400 */ "anylist ::= anylist LP anylist RP", - /* 401 */ "anylist ::= anylist ANY", - /* 402 */ "with ::=", + /* 309 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", + /* 310 */ "windowdefn ::= nm AS LP window RP", + /* 311 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", + /* 312 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", + /* 313 */ "window ::= ORDER BY sortlist frame_opt", + /* 314 */ "window ::= nm ORDER BY sortlist frame_opt", + /* 315 */ "window ::= nm frame_opt", + /* 316 */ "frame_opt ::=", + /* 317 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", + /* 318 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", + /* 319 */ "range_or_rows ::= RANGE|ROWS|GROUPS", + /* 320 */ "frame_bound_s ::= frame_bound", + /* 321 */ "frame_bound_s ::= UNBOUNDED PRECEDING", + /* 322 */ "frame_bound_e ::= frame_bound", + /* 323 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", + /* 324 */ "frame_bound ::= expr PRECEDING|FOLLOWING", + /* 325 */ "frame_bound ::= CURRENT ROW", + /* 326 */ "frame_exclude_opt ::=", + /* 327 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", + /* 328 */ "frame_exclude ::= NO OTHERS", + /* 329 */ "frame_exclude ::= CURRENT ROW", + /* 330 */ "frame_exclude ::= GROUP|TIES", + /* 331 */ "window_clause ::= WINDOW windowdefn_list", + /* 332 */ "filter_over ::= filter_clause over_clause", + /* 333 */ "filter_over ::= over_clause", + /* 334 */ "filter_over ::= filter_clause", + /* 335 */ "over_clause ::= OVER LP window RP", + /* 336 */ "over_clause ::= OVER nm", + /* 337 */ "filter_clause ::= FILTER LP WHERE expr RP", + /* 338 */ "input ::= cmdlist", + /* 339 */ "cmdlist ::= cmdlist ecmd", + /* 340 */ "cmdlist ::= ecmd", + /* 341 */ "ecmd ::= SEMI", + /* 342 */ "ecmd ::= cmdx SEMI", + /* 343 */ "ecmd ::= explain cmdx SEMI", + /* 344 */ "trans_opt ::=", + /* 345 */ "trans_opt ::= TRANSACTION", + /* 346 */ "trans_opt ::= TRANSACTION nm", + /* 347 */ "savepoint_opt ::= SAVEPOINT", + /* 348 */ "savepoint_opt ::=", + /* 349 */ "cmd ::= create_table create_table_args", + /* 350 */ "table_option_set ::= table_option", + /* 351 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 352 */ "columnlist ::= columnname carglist", + /* 353 */ "nm ::= ID|INDEXED|JOIN_KW", + /* 354 */ "nm ::= STRING", + /* 355 */ "typetoken ::= typename", + /* 356 */ "typename ::= ID|STRING", + /* 357 */ "signed ::= plus_num", + /* 358 */ "signed ::= minus_num", + /* 359 */ "carglist ::= carglist ccons", + /* 360 */ "carglist ::=", + /* 361 */ "ccons ::= NULL onconf", + /* 362 */ "ccons ::= GENERATED ALWAYS AS generated", + /* 363 */ "ccons ::= AS generated", + /* 364 */ "conslist_opt ::= COMMA conslist", + /* 365 */ "conslist ::= conslist tconscomma tcons", + /* 366 */ "conslist ::= tcons", + /* 367 */ "tconscomma ::=", + /* 368 */ "defer_subclause_opt ::= defer_subclause", + /* 369 */ "resolvetype ::= raisetype", + /* 370 */ "selectnowith ::= oneselect", + /* 371 */ "oneselect ::= values", + /* 372 */ "sclp ::= selcollist COMMA", + /* 373 */ "as ::= ID|STRING", + /* 374 */ "indexed_opt ::= indexed_by", + /* 375 */ "returning ::=", + /* 376 */ "expr ::= term", + /* 377 */ "likeop ::= LIKE_KW|MATCH", + /* 378 */ "case_operand ::= expr", + /* 379 */ "exprlist ::= nexprlist", + /* 380 */ "nmnum ::= plus_num", + /* 381 */ "nmnum ::= nm", + /* 382 */ "nmnum ::= ON", + /* 383 */ "nmnum ::= DELETE", + /* 384 */ "nmnum ::= DEFAULT", + /* 385 */ "plus_num ::= INTEGER|FLOAT", + /* 386 */ "foreach_clause ::=", + /* 387 */ "foreach_clause ::= FOR EACH ROW", + /* 388 */ "trnm ::= nm", + /* 389 */ "tridxby ::=", + /* 390 */ "database_kw_opt ::= DATABASE", + /* 391 */ "database_kw_opt ::=", + /* 392 */ "kwcolumn_opt ::=", + /* 393 */ "kwcolumn_opt ::= COLUMNKW", + /* 394 */ "vtabarglist ::= vtabarg", + /* 395 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 396 */ "vtabarg ::= vtabarg vtabargtoken", + /* 397 */ "anylist ::=", + /* 398 */ "anylist ::= anylist LP anylist RP", + /* 399 */ "anylist ::= anylist ANY", + /* 400 */ "with ::=", + /* 401 */ "windowdefn_list ::= windowdefn", + /* 402 */ "window ::= frame_opt", }; #endif /* NDEBUG */ @@ -170754,100 +172259,100 @@ static const YYCODETYPE yyRuleInfoLhs[] = { 304, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */ 241, /* (307) wqlist ::= wqitem */ 241, /* (308) wqlist ::= wqlist COMMA wqitem */ - 306, /* (309) windowdefn_list ::= windowdefn */ - 306, /* (310) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - 307, /* (311) windowdefn ::= nm AS LP window RP */ - 308, /* (312) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - 308, /* (313) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - 308, /* (314) window ::= ORDER BY sortlist frame_opt */ - 308, /* (315) window ::= nm ORDER BY sortlist frame_opt */ - 308, /* (316) window ::= frame_opt */ - 308, /* (317) window ::= nm frame_opt */ - 309, /* (318) frame_opt ::= */ - 309, /* (319) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - 309, /* (320) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - 313, /* (321) range_or_rows ::= RANGE|ROWS|GROUPS */ - 315, /* (322) frame_bound_s ::= frame_bound */ - 315, /* (323) frame_bound_s ::= UNBOUNDED PRECEDING */ - 316, /* (324) frame_bound_e ::= frame_bound */ - 316, /* (325) frame_bound_e ::= UNBOUNDED FOLLOWING */ - 314, /* (326) frame_bound ::= expr PRECEDING|FOLLOWING */ - 314, /* (327) frame_bound ::= CURRENT ROW */ - 317, /* (328) frame_exclude_opt ::= */ - 317, /* (329) frame_exclude_opt ::= EXCLUDE frame_exclude */ - 318, /* (330) frame_exclude ::= NO OTHERS */ - 318, /* (331) frame_exclude ::= CURRENT ROW */ - 318, /* (332) frame_exclude ::= GROUP|TIES */ - 251, /* (333) window_clause ::= WINDOW windowdefn_list */ - 273, /* (334) filter_over ::= filter_clause over_clause */ - 273, /* (335) filter_over ::= over_clause */ - 273, /* (336) filter_over ::= filter_clause */ - 312, /* (337) over_clause ::= OVER LP window RP */ - 312, /* (338) over_clause ::= OVER nm */ - 311, /* (339) filter_clause ::= FILTER LP WHERE expr RP */ - 185, /* (340) input ::= cmdlist */ - 186, /* (341) cmdlist ::= cmdlist ecmd */ - 186, /* (342) cmdlist ::= ecmd */ - 187, /* (343) ecmd ::= SEMI */ - 187, /* (344) ecmd ::= cmdx SEMI */ - 187, /* (345) ecmd ::= explain cmdx SEMI */ - 192, /* (346) trans_opt ::= */ - 192, /* (347) trans_opt ::= TRANSACTION */ - 192, /* (348) trans_opt ::= TRANSACTION nm */ - 194, /* (349) savepoint_opt ::= SAVEPOINT */ - 194, /* (350) savepoint_opt ::= */ - 190, /* (351) cmd ::= create_table create_table_args */ - 203, /* (352) table_option_set ::= table_option */ - 201, /* (353) columnlist ::= columnlist COMMA columnname carglist */ - 201, /* (354) columnlist ::= columnname carglist */ - 193, /* (355) nm ::= ID|INDEXED|JOIN_KW */ - 193, /* (356) nm ::= STRING */ - 208, /* (357) typetoken ::= typename */ - 209, /* (358) typename ::= ID|STRING */ - 210, /* (359) signed ::= plus_num */ - 210, /* (360) signed ::= minus_num */ - 207, /* (361) carglist ::= carglist ccons */ - 207, /* (362) carglist ::= */ - 215, /* (363) ccons ::= NULL onconf */ - 215, /* (364) ccons ::= GENERATED ALWAYS AS generated */ - 215, /* (365) ccons ::= AS generated */ - 202, /* (366) conslist_opt ::= COMMA conslist */ - 228, /* (367) conslist ::= conslist tconscomma tcons */ - 228, /* (368) conslist ::= tcons */ - 229, /* (369) tconscomma ::= */ - 233, /* (370) defer_subclause_opt ::= defer_subclause */ - 235, /* (371) resolvetype ::= raisetype */ - 239, /* (372) selectnowith ::= oneselect */ - 240, /* (373) oneselect ::= values */ - 254, /* (374) sclp ::= selcollist COMMA */ - 255, /* (375) as ::= ID|STRING */ - 264, /* (376) indexed_opt ::= indexed_by */ - 272, /* (377) returning ::= */ - 217, /* (378) expr ::= term */ - 274, /* (379) likeop ::= LIKE_KW|MATCH */ - 278, /* (380) case_operand ::= expr */ - 261, /* (381) exprlist ::= nexprlist */ - 284, /* (382) nmnum ::= plus_num */ - 284, /* (383) nmnum ::= nm */ - 284, /* (384) nmnum ::= ON */ - 284, /* (385) nmnum ::= DELETE */ - 284, /* (386) nmnum ::= DEFAULT */ - 211, /* (387) plus_num ::= INTEGER|FLOAT */ - 289, /* (388) foreach_clause ::= */ - 289, /* (389) foreach_clause ::= FOR EACH ROW */ - 292, /* (390) trnm ::= nm */ - 293, /* (391) tridxby ::= */ - 294, /* (392) database_kw_opt ::= DATABASE */ - 294, /* (393) database_kw_opt ::= */ - 297, /* (394) kwcolumn_opt ::= */ - 297, /* (395) kwcolumn_opt ::= COLUMNKW */ - 299, /* (396) vtabarglist ::= vtabarg */ - 299, /* (397) vtabarglist ::= vtabarglist COMMA vtabarg */ - 300, /* (398) vtabarg ::= vtabarg vtabargtoken */ - 303, /* (399) anylist ::= */ - 303, /* (400) anylist ::= anylist LP anylist RP */ - 303, /* (401) anylist ::= anylist ANY */ - 266, /* (402) with ::= */ + 306, /* (309) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + 307, /* (310) windowdefn ::= nm AS LP window RP */ + 308, /* (311) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (312) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + 308, /* (313) window ::= ORDER BY sortlist frame_opt */ + 308, /* (314) window ::= nm ORDER BY sortlist frame_opt */ + 308, /* (315) window ::= nm frame_opt */ + 309, /* (316) frame_opt ::= */ + 309, /* (317) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + 309, /* (318) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + 313, /* (319) range_or_rows ::= RANGE|ROWS|GROUPS */ + 315, /* (320) frame_bound_s ::= frame_bound */ + 315, /* (321) frame_bound_s ::= UNBOUNDED PRECEDING */ + 316, /* (322) frame_bound_e ::= frame_bound */ + 316, /* (323) frame_bound_e ::= UNBOUNDED FOLLOWING */ + 314, /* (324) frame_bound ::= expr PRECEDING|FOLLOWING */ + 314, /* (325) frame_bound ::= CURRENT ROW */ + 317, /* (326) frame_exclude_opt ::= */ + 317, /* (327) frame_exclude_opt ::= EXCLUDE frame_exclude */ + 318, /* (328) frame_exclude ::= NO OTHERS */ + 318, /* (329) frame_exclude ::= CURRENT ROW */ + 318, /* (330) frame_exclude ::= GROUP|TIES */ + 251, /* (331) window_clause ::= WINDOW windowdefn_list */ + 273, /* (332) filter_over ::= filter_clause over_clause */ + 273, /* (333) filter_over ::= over_clause */ + 273, /* (334) filter_over ::= filter_clause */ + 312, /* (335) over_clause ::= OVER LP window RP */ + 312, /* (336) over_clause ::= OVER nm */ + 311, /* (337) filter_clause ::= FILTER LP WHERE expr RP */ + 185, /* (338) input ::= cmdlist */ + 186, /* (339) cmdlist ::= cmdlist ecmd */ + 186, /* (340) cmdlist ::= ecmd */ + 187, /* (341) ecmd ::= SEMI */ + 187, /* (342) ecmd ::= cmdx SEMI */ + 187, /* (343) ecmd ::= explain cmdx SEMI */ + 192, /* (344) trans_opt ::= */ + 192, /* (345) trans_opt ::= TRANSACTION */ + 192, /* (346) trans_opt ::= TRANSACTION nm */ + 194, /* (347) savepoint_opt ::= SAVEPOINT */ + 194, /* (348) savepoint_opt ::= */ + 190, /* (349) cmd ::= create_table create_table_args */ + 203, /* (350) table_option_set ::= table_option */ + 201, /* (351) columnlist ::= columnlist COMMA columnname carglist */ + 201, /* (352) columnlist ::= columnname carglist */ + 193, /* (353) nm ::= ID|INDEXED|JOIN_KW */ + 193, /* (354) nm ::= STRING */ + 208, /* (355) typetoken ::= typename */ + 209, /* (356) typename ::= ID|STRING */ + 210, /* (357) signed ::= plus_num */ + 210, /* (358) signed ::= minus_num */ + 207, /* (359) carglist ::= carglist ccons */ + 207, /* (360) carglist ::= */ + 215, /* (361) ccons ::= NULL onconf */ + 215, /* (362) ccons ::= GENERATED ALWAYS AS generated */ + 215, /* (363) ccons ::= AS generated */ + 202, /* (364) conslist_opt ::= COMMA conslist */ + 228, /* (365) conslist ::= conslist tconscomma tcons */ + 228, /* (366) conslist ::= tcons */ + 229, /* (367) tconscomma ::= */ + 233, /* (368) defer_subclause_opt ::= defer_subclause */ + 235, /* (369) resolvetype ::= raisetype */ + 239, /* (370) selectnowith ::= oneselect */ + 240, /* (371) oneselect ::= values */ + 254, /* (372) sclp ::= selcollist COMMA */ + 255, /* (373) as ::= ID|STRING */ + 264, /* (374) indexed_opt ::= indexed_by */ + 272, /* (375) returning ::= */ + 217, /* (376) expr ::= term */ + 274, /* (377) likeop ::= LIKE_KW|MATCH */ + 278, /* (378) case_operand ::= expr */ + 261, /* (379) exprlist ::= nexprlist */ + 284, /* (380) nmnum ::= plus_num */ + 284, /* (381) nmnum ::= nm */ + 284, /* (382) nmnum ::= ON */ + 284, /* (383) nmnum ::= DELETE */ + 284, /* (384) nmnum ::= DEFAULT */ + 211, /* (385) plus_num ::= INTEGER|FLOAT */ + 289, /* (386) foreach_clause ::= */ + 289, /* (387) foreach_clause ::= FOR EACH ROW */ + 292, /* (388) trnm ::= nm */ + 293, /* (389) tridxby ::= */ + 294, /* (390) database_kw_opt ::= DATABASE */ + 294, /* (391) database_kw_opt ::= */ + 297, /* (392) kwcolumn_opt ::= */ + 297, /* (393) kwcolumn_opt ::= COLUMNKW */ + 299, /* (394) vtabarglist ::= vtabarg */ + 299, /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ + 300, /* (396) vtabarg ::= vtabarg vtabargtoken */ + 303, /* (397) anylist ::= */ + 303, /* (398) anylist ::= anylist LP anylist RP */ + 303, /* (399) anylist ::= anylist ANY */ + 266, /* (400) with ::= */ + 306, /* (401) windowdefn_list ::= windowdefn */ + 308, /* (402) window ::= frame_opt */ }; /* For rule J, yyRuleInfoNRhs[J] contains the negative of the number @@ -171162,100 +172667,100 @@ static const signed char yyRuleInfoNRhs[] = { -6, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */ -1, /* (307) wqlist ::= wqitem */ -3, /* (308) wqlist ::= wqlist COMMA wqitem */ - -1, /* (309) windowdefn_list ::= windowdefn */ - -3, /* (310) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - -5, /* (311) windowdefn ::= nm AS LP window RP */ - -5, /* (312) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - -6, /* (313) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - -4, /* (314) window ::= ORDER BY sortlist frame_opt */ - -5, /* (315) window ::= nm ORDER BY sortlist frame_opt */ - -1, /* (316) window ::= frame_opt */ - -2, /* (317) window ::= nm frame_opt */ - 0, /* (318) frame_opt ::= */ - -3, /* (319) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - -6, /* (320) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - -1, /* (321) range_or_rows ::= RANGE|ROWS|GROUPS */ - -1, /* (322) frame_bound_s ::= frame_bound */ - -2, /* (323) frame_bound_s ::= UNBOUNDED PRECEDING */ - -1, /* (324) frame_bound_e ::= frame_bound */ - -2, /* (325) frame_bound_e ::= UNBOUNDED FOLLOWING */ - -2, /* (326) frame_bound ::= expr PRECEDING|FOLLOWING */ - -2, /* (327) frame_bound ::= CURRENT ROW */ - 0, /* (328) frame_exclude_opt ::= */ - -2, /* (329) frame_exclude_opt ::= EXCLUDE frame_exclude */ - -2, /* (330) frame_exclude ::= NO OTHERS */ - -2, /* (331) frame_exclude ::= CURRENT ROW */ - -1, /* (332) frame_exclude ::= GROUP|TIES */ - -2, /* (333) window_clause ::= WINDOW windowdefn_list */ - -2, /* (334) filter_over ::= filter_clause over_clause */ - -1, /* (335) filter_over ::= over_clause */ - -1, /* (336) filter_over ::= filter_clause */ - -4, /* (337) over_clause ::= OVER LP window RP */ - -2, /* (338) over_clause ::= OVER nm */ - -5, /* (339) filter_clause ::= FILTER LP WHERE expr RP */ - -1, /* (340) input ::= cmdlist */ - -2, /* (341) cmdlist ::= cmdlist ecmd */ - -1, /* (342) cmdlist ::= ecmd */ - -1, /* (343) ecmd ::= SEMI */ - -2, /* (344) ecmd ::= cmdx SEMI */ - -3, /* (345) ecmd ::= explain cmdx SEMI */ - 0, /* (346) trans_opt ::= */ - -1, /* (347) trans_opt ::= TRANSACTION */ - -2, /* (348) trans_opt ::= TRANSACTION nm */ - -1, /* (349) savepoint_opt ::= SAVEPOINT */ - 0, /* (350) savepoint_opt ::= */ - -2, /* (351) cmd ::= create_table create_table_args */ - -1, /* (352) table_option_set ::= table_option */ - -4, /* (353) columnlist ::= columnlist COMMA columnname carglist */ - -2, /* (354) columnlist ::= columnname carglist */ - -1, /* (355) nm ::= ID|INDEXED|JOIN_KW */ - -1, /* (356) nm ::= STRING */ - -1, /* (357) typetoken ::= typename */ - -1, /* (358) typename ::= ID|STRING */ - -1, /* (359) signed ::= plus_num */ - -1, /* (360) signed ::= minus_num */ - -2, /* (361) carglist ::= carglist ccons */ - 0, /* (362) carglist ::= */ - -2, /* (363) ccons ::= NULL onconf */ - -4, /* (364) ccons ::= GENERATED ALWAYS AS generated */ - -2, /* (365) ccons ::= AS generated */ - -2, /* (366) conslist_opt ::= COMMA conslist */ - -3, /* (367) conslist ::= conslist tconscomma tcons */ - -1, /* (368) conslist ::= tcons */ - 0, /* (369) tconscomma ::= */ - -1, /* (370) defer_subclause_opt ::= defer_subclause */ - -1, /* (371) resolvetype ::= raisetype */ - -1, /* (372) selectnowith ::= oneselect */ - -1, /* (373) oneselect ::= values */ - -2, /* (374) sclp ::= selcollist COMMA */ - -1, /* (375) as ::= ID|STRING */ - -1, /* (376) indexed_opt ::= indexed_by */ - 0, /* (377) returning ::= */ - -1, /* (378) expr ::= term */ - -1, /* (379) likeop ::= LIKE_KW|MATCH */ - -1, /* (380) case_operand ::= expr */ - -1, /* (381) exprlist ::= nexprlist */ - -1, /* (382) nmnum ::= plus_num */ - -1, /* (383) nmnum ::= nm */ - -1, /* (384) nmnum ::= ON */ - -1, /* (385) nmnum ::= DELETE */ - -1, /* (386) nmnum ::= DEFAULT */ - -1, /* (387) plus_num ::= INTEGER|FLOAT */ - 0, /* (388) foreach_clause ::= */ - -3, /* (389) foreach_clause ::= FOR EACH ROW */ - -1, /* (390) trnm ::= nm */ - 0, /* (391) tridxby ::= */ - -1, /* (392) database_kw_opt ::= DATABASE */ - 0, /* (393) database_kw_opt ::= */ - 0, /* (394) kwcolumn_opt ::= */ - -1, /* (395) kwcolumn_opt ::= COLUMNKW */ - -1, /* (396) vtabarglist ::= vtabarg */ - -3, /* (397) vtabarglist ::= vtabarglist COMMA vtabarg */ - -2, /* (398) vtabarg ::= vtabarg vtabargtoken */ - 0, /* (399) anylist ::= */ - -4, /* (400) anylist ::= anylist LP anylist RP */ - -2, /* (401) anylist ::= anylist ANY */ - 0, /* (402) with ::= */ + -3, /* (309) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + -5, /* (310) windowdefn ::= nm AS LP window RP */ + -5, /* (311) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + -6, /* (312) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + -4, /* (313) window ::= ORDER BY sortlist frame_opt */ + -5, /* (314) window ::= nm ORDER BY sortlist frame_opt */ + -2, /* (315) window ::= nm frame_opt */ + 0, /* (316) frame_opt ::= */ + -3, /* (317) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + -6, /* (318) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + -1, /* (319) range_or_rows ::= RANGE|ROWS|GROUPS */ + -1, /* (320) frame_bound_s ::= frame_bound */ + -2, /* (321) frame_bound_s ::= UNBOUNDED PRECEDING */ + -1, /* (322) frame_bound_e ::= frame_bound */ + -2, /* (323) frame_bound_e ::= UNBOUNDED FOLLOWING */ + -2, /* (324) frame_bound ::= expr PRECEDING|FOLLOWING */ + -2, /* (325) frame_bound ::= CURRENT ROW */ + 0, /* (326) frame_exclude_opt ::= */ + -2, /* (327) frame_exclude_opt ::= EXCLUDE frame_exclude */ + -2, /* (328) frame_exclude ::= NO OTHERS */ + -2, /* (329) frame_exclude ::= CURRENT ROW */ + -1, /* (330) frame_exclude ::= GROUP|TIES */ + -2, /* (331) window_clause ::= WINDOW windowdefn_list */ + -2, /* (332) filter_over ::= filter_clause over_clause */ + -1, /* (333) filter_over ::= over_clause */ + -1, /* (334) filter_over ::= filter_clause */ + -4, /* (335) over_clause ::= OVER LP window RP */ + -2, /* (336) over_clause ::= OVER nm */ + -5, /* (337) filter_clause ::= FILTER LP WHERE expr RP */ + -1, /* (338) input ::= cmdlist */ + -2, /* (339) cmdlist ::= cmdlist ecmd */ + -1, /* (340) cmdlist ::= ecmd */ + -1, /* (341) ecmd ::= SEMI */ + -2, /* (342) ecmd ::= cmdx SEMI */ + -3, /* (343) ecmd ::= explain cmdx SEMI */ + 0, /* (344) trans_opt ::= */ + -1, /* (345) trans_opt ::= TRANSACTION */ + -2, /* (346) trans_opt ::= TRANSACTION nm */ + -1, /* (347) savepoint_opt ::= SAVEPOINT */ + 0, /* (348) savepoint_opt ::= */ + -2, /* (349) cmd ::= create_table create_table_args */ + -1, /* (350) table_option_set ::= table_option */ + -4, /* (351) columnlist ::= columnlist COMMA columnname carglist */ + -2, /* (352) columnlist ::= columnname carglist */ + -1, /* (353) nm ::= ID|INDEXED|JOIN_KW */ + -1, /* (354) nm ::= STRING */ + -1, /* (355) typetoken ::= typename */ + -1, /* (356) typename ::= ID|STRING */ + -1, /* (357) signed ::= plus_num */ + -1, /* (358) signed ::= minus_num */ + -2, /* (359) carglist ::= carglist ccons */ + 0, /* (360) carglist ::= */ + -2, /* (361) ccons ::= NULL onconf */ + -4, /* (362) ccons ::= GENERATED ALWAYS AS generated */ + -2, /* (363) ccons ::= AS generated */ + -2, /* (364) conslist_opt ::= COMMA conslist */ + -3, /* (365) conslist ::= conslist tconscomma tcons */ + -1, /* (366) conslist ::= tcons */ + 0, /* (367) tconscomma ::= */ + -1, /* (368) defer_subclause_opt ::= defer_subclause */ + -1, /* (369) resolvetype ::= raisetype */ + -1, /* (370) selectnowith ::= oneselect */ + -1, /* (371) oneselect ::= values */ + -2, /* (372) sclp ::= selcollist COMMA */ + -1, /* (373) as ::= ID|STRING */ + -1, /* (374) indexed_opt ::= indexed_by */ + 0, /* (375) returning ::= */ + -1, /* (376) expr ::= term */ + -1, /* (377) likeop ::= LIKE_KW|MATCH */ + -1, /* (378) case_operand ::= expr */ + -1, /* (379) exprlist ::= nexprlist */ + -1, /* (380) nmnum ::= plus_num */ + -1, /* (381) nmnum ::= nm */ + -1, /* (382) nmnum ::= ON */ + -1, /* (383) nmnum ::= DELETE */ + -1, /* (384) nmnum ::= DEFAULT */ + -1, /* (385) plus_num ::= INTEGER|FLOAT */ + 0, /* (386) foreach_clause ::= */ + -3, /* (387) foreach_clause ::= FOR EACH ROW */ + -1, /* (388) trnm ::= nm */ + 0, /* (389) tridxby ::= */ + -1, /* (390) database_kw_opt ::= DATABASE */ + 0, /* (391) database_kw_opt ::= */ + 0, /* (392) kwcolumn_opt ::= */ + -1, /* (393) kwcolumn_opt ::= COLUMNKW */ + -1, /* (394) vtabarglist ::= vtabarg */ + -3, /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ + -2, /* (396) vtabarg ::= vtabarg vtabargtoken */ + 0, /* (397) anylist ::= */ + -4, /* (398) anylist ::= anylist LP anylist RP */ + -2, /* (399) anylist ::= anylist ANY */ + 0, /* (400) with ::= */ + -1, /* (401) windowdefn_list ::= windowdefn */ + -1, /* (402) window ::= frame_opt */ }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -171298,10 +172803,10 @@ static YYACTIONTYPE yy_reduce( /********** Begin reduce actions **********************************************/ YYMINORTYPE yylhsminor; case 0: /* explain ::= EXPLAIN */ -{ pParse->explain = 1; } +{ if( pParse->pReprepare==0 ) pParse->explain = 1; } break; case 1: /* explain ::= EXPLAIN QUERY PLAN */ -{ pParse->explain = 2; } +{ if( pParse->pReprepare==0 ) pParse->explain = 2; } break; case 2: /* cmdx ::= cmd */ { sqlite3FinishCoding(pParse); } @@ -171315,7 +172820,7 @@ static YYACTIONTYPE yy_reduce( case 5: /* transtype ::= DEFERRED */ case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); - case 321: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==321); + case 319: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==319); {yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/} break; case 8: /* cmd ::= COMMIT|END trans_opt */ @@ -171611,7 +173116,6 @@ static YYACTIONTYPE yy_reduce( if( p ){ parserDoubleLinkSelect(pParse, p); } - yymsp[0].minor.yy47 = p; /*A-overwrites-X*/ } break; case 88: /* selectnowith ::= selectnowith multiselect_op oneselect */ @@ -171703,14 +173207,17 @@ static YYACTIONTYPE yy_reduce( case 101: /* selcollist ::= sclp scanpt STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); + sqlite3ExprSetErrorOffset(p, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy322, p); } break; case 102: /* selcollist ::= sclp scanpt nm DOT STAR */ { - Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); - Expr *pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); - Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + Expr *pRight, *pLeft, *pDot; + pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + sqlite3ExprSetErrorOffset(pRight, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); + pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); + pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, pDot); } break; @@ -172604,11 +174111,7 @@ static YYACTIONTYPE yy_reduce( yymsp[-2].minor.yy521 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy521, yymsp[0].minor.yy385); } break; - case 309: /* windowdefn_list ::= windowdefn */ -{ yylhsminor.yy41 = yymsp[0].minor.yy41; } - yymsp[0].minor.yy41 = yylhsminor.yy41; - break; - case 310: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ + case 309: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ { assert( yymsp[0].minor.yy41!=0 ); sqlite3WindowChain(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy41); @@ -172617,7 +174120,7 @@ static YYACTIONTYPE yy_reduce( } yymsp[-2].minor.yy41 = yylhsminor.yy41; break; - case 311: /* windowdefn ::= nm AS LP window RP */ + case 310: /* windowdefn ::= nm AS LP window RP */ { if( ALWAYS(yymsp[-1].minor.yy41) ){ yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); @@ -172626,90 +174129,83 @@ static YYACTIONTYPE yy_reduce( } yymsp[-4].minor.yy41 = yylhsminor.yy41; break; - case 312: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + case 311: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ { yymsp[-4].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, 0); } break; - case 313: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + case 312: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ { yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, &yymsp[-5].minor.yy0); } yymsp[-5].minor.yy41 = yylhsminor.yy41; break; - case 314: /* window ::= ORDER BY sortlist frame_opt */ + case 313: /* window ::= ORDER BY sortlist frame_opt */ { yymsp[-3].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, 0); } break; - case 315: /* window ::= nm ORDER BY sortlist frame_opt */ + case 314: /* window ::= nm ORDER BY sortlist frame_opt */ { yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0); } yymsp[-4].minor.yy41 = yylhsminor.yy41; break; - case 316: /* window ::= frame_opt */ - case 335: /* filter_over ::= over_clause */ yytestcase(yyruleno==335); -{ - yylhsminor.yy41 = yymsp[0].minor.yy41; -} - yymsp[0].minor.yy41 = yylhsminor.yy41; - break; - case 317: /* window ::= nm frame_opt */ + case 315: /* window ::= nm frame_opt */ { yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, 0, &yymsp[-1].minor.yy0); } yymsp[-1].minor.yy41 = yylhsminor.yy41; break; - case 318: /* frame_opt ::= */ + case 316: /* frame_opt ::= */ { yymsp[1].minor.yy41 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); } break; - case 319: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + case 317: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ { yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy394, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy516); } yymsp[-2].minor.yy41 = yylhsminor.yy41; break; - case 320: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + case 318: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ { yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy394, yymsp[-3].minor.yy595.eType, yymsp[-3].minor.yy595.pExpr, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, yymsp[0].minor.yy516); } yymsp[-5].minor.yy41 = yylhsminor.yy41; break; - case 322: /* frame_bound_s ::= frame_bound */ - case 324: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==324); + case 320: /* frame_bound_s ::= frame_bound */ + case 322: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==322); {yylhsminor.yy595 = yymsp[0].minor.yy595;} yymsp[0].minor.yy595 = yylhsminor.yy595; break; - case 323: /* frame_bound_s ::= UNBOUNDED PRECEDING */ - case 325: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==325); - case 327: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==327); + case 321: /* frame_bound_s ::= UNBOUNDED PRECEDING */ + case 323: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==323); + case 325: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==325); {yylhsminor.yy595.eType = yymsp[-1].major; yylhsminor.yy595.pExpr = 0;} yymsp[-1].minor.yy595 = yylhsminor.yy595; break; - case 326: /* frame_bound ::= expr PRECEDING|FOLLOWING */ + case 324: /* frame_bound ::= expr PRECEDING|FOLLOWING */ {yylhsminor.yy595.eType = yymsp[0].major; yylhsminor.yy595.pExpr = yymsp[-1].minor.yy528;} yymsp[-1].minor.yy595 = yylhsminor.yy595; break; - case 328: /* frame_exclude_opt ::= */ + case 326: /* frame_exclude_opt ::= */ {yymsp[1].minor.yy516 = 0;} break; - case 329: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ + case 327: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ {yymsp[-1].minor.yy516 = yymsp[0].minor.yy516;} break; - case 330: /* frame_exclude ::= NO OTHERS */ - case 331: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==331); + case 328: /* frame_exclude ::= NO OTHERS */ + case 329: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==329); {yymsp[-1].minor.yy516 = yymsp[-1].major; /*A-overwrites-X*/} break; - case 332: /* frame_exclude ::= GROUP|TIES */ + case 330: /* frame_exclude ::= GROUP|TIES */ {yymsp[0].minor.yy516 = yymsp[0].major; /*A-overwrites-X*/} break; - case 333: /* window_clause ::= WINDOW windowdefn_list */ + case 331: /* window_clause ::= WINDOW windowdefn_list */ { yymsp[-1].minor.yy41 = yymsp[0].minor.yy41; } break; - case 334: /* filter_over ::= filter_clause over_clause */ + case 332: /* filter_over ::= filter_clause over_clause */ { if( yymsp[0].minor.yy41 ){ yymsp[0].minor.yy41->pFilter = yymsp[-1].minor.yy528; @@ -172720,7 +174216,13 @@ static YYACTIONTYPE yy_reduce( } yymsp[-1].minor.yy41 = yylhsminor.yy41; break; - case 336: /* filter_over ::= filter_clause */ + case 333: /* filter_over ::= over_clause */ +{ + yylhsminor.yy41 = yymsp[0].minor.yy41; +} + yymsp[0].minor.yy41 = yylhsminor.yy41; + break; + case 334: /* filter_over ::= filter_clause */ { yylhsminor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); if( yylhsminor.yy41 ){ @@ -172732,13 +174234,13 @@ static YYACTIONTYPE yy_reduce( } yymsp[0].minor.yy41 = yylhsminor.yy41; break; - case 337: /* over_clause ::= OVER LP window RP */ + case 335: /* over_clause ::= OVER LP window RP */ { yymsp[-3].minor.yy41 = yymsp[-1].minor.yy41; assert( yymsp[-3].minor.yy41!=0 ); } break; - case 338: /* over_clause ::= OVER nm */ + case 336: /* over_clause ::= OVER nm */ { yymsp[-1].minor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); if( yymsp[-1].minor.yy41 ){ @@ -172746,73 +174248,75 @@ static YYACTIONTYPE yy_reduce( } } break; - case 339: /* filter_clause ::= FILTER LP WHERE expr RP */ + case 337: /* filter_clause ::= FILTER LP WHERE expr RP */ { yymsp[-4].minor.yy528 = yymsp[-1].minor.yy528; } break; default: - /* (340) input ::= cmdlist */ yytestcase(yyruleno==340); - /* (341) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==341); - /* (342) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=342); - /* (343) ecmd ::= SEMI */ yytestcase(yyruleno==343); - /* (344) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==344); - /* (345) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=345); - /* (346) trans_opt ::= */ yytestcase(yyruleno==346); - /* (347) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==347); - /* (348) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==348); - /* (349) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==349); - /* (350) savepoint_opt ::= */ yytestcase(yyruleno==350); - /* (351) cmd ::= create_table create_table_args */ yytestcase(yyruleno==351); - /* (352) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=352); - /* (353) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==353); - /* (354) columnlist ::= columnname carglist */ yytestcase(yyruleno==354); - /* (355) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==355); - /* (356) nm ::= STRING */ yytestcase(yyruleno==356); - /* (357) typetoken ::= typename */ yytestcase(yyruleno==357); - /* (358) typename ::= ID|STRING */ yytestcase(yyruleno==358); - /* (359) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=359); - /* (360) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=360); - /* (361) carglist ::= carglist ccons */ yytestcase(yyruleno==361); - /* (362) carglist ::= */ yytestcase(yyruleno==362); - /* (363) ccons ::= NULL onconf */ yytestcase(yyruleno==363); - /* (364) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==364); - /* (365) ccons ::= AS generated */ yytestcase(yyruleno==365); - /* (366) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==366); - /* (367) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==367); - /* (368) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=368); - /* (369) tconscomma ::= */ yytestcase(yyruleno==369); - /* (370) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=370); - /* (371) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=371); - /* (372) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=372); - /* (373) oneselect ::= values */ yytestcase(yyruleno==373); - /* (374) sclp ::= selcollist COMMA */ yytestcase(yyruleno==374); - /* (375) as ::= ID|STRING */ yytestcase(yyruleno==375); - /* (376) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=376); - /* (377) returning ::= */ yytestcase(yyruleno==377); - /* (378) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=378); - /* (379) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==379); - /* (380) case_operand ::= expr */ yytestcase(yyruleno==380); - /* (381) exprlist ::= nexprlist */ yytestcase(yyruleno==381); - /* (382) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=382); - /* (383) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=383); - /* (384) nmnum ::= ON */ yytestcase(yyruleno==384); - /* (385) nmnum ::= DELETE */ yytestcase(yyruleno==385); - /* (386) nmnum ::= DEFAULT */ yytestcase(yyruleno==386); - /* (387) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==387); - /* (388) foreach_clause ::= */ yytestcase(yyruleno==388); - /* (389) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==389); - /* (390) trnm ::= nm */ yytestcase(yyruleno==390); - /* (391) tridxby ::= */ yytestcase(yyruleno==391); - /* (392) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==392); - /* (393) database_kw_opt ::= */ yytestcase(yyruleno==393); - /* (394) kwcolumn_opt ::= */ yytestcase(yyruleno==394); - /* (395) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==395); - /* (396) vtabarglist ::= vtabarg */ yytestcase(yyruleno==396); - /* (397) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==397); - /* (398) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==398); - /* (399) anylist ::= */ yytestcase(yyruleno==399); - /* (400) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==400); - /* (401) anylist ::= anylist ANY */ yytestcase(yyruleno==401); - /* (402) with ::= */ yytestcase(yyruleno==402); + /* (338) input ::= cmdlist */ yytestcase(yyruleno==338); + /* (339) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==339); + /* (340) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=340); + /* (341) ecmd ::= SEMI */ yytestcase(yyruleno==341); + /* (342) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==342); + /* (343) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=343); + /* (344) trans_opt ::= */ yytestcase(yyruleno==344); + /* (345) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==345); + /* (346) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==346); + /* (347) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==347); + /* (348) savepoint_opt ::= */ yytestcase(yyruleno==348); + /* (349) cmd ::= create_table create_table_args */ yytestcase(yyruleno==349); + /* (350) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=350); + /* (351) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==351); + /* (352) columnlist ::= columnname carglist */ yytestcase(yyruleno==352); + /* (353) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==353); + /* (354) nm ::= STRING */ yytestcase(yyruleno==354); + /* (355) typetoken ::= typename */ yytestcase(yyruleno==355); + /* (356) typename ::= ID|STRING */ yytestcase(yyruleno==356); + /* (357) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=357); + /* (358) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=358); + /* (359) carglist ::= carglist ccons */ yytestcase(yyruleno==359); + /* (360) carglist ::= */ yytestcase(yyruleno==360); + /* (361) ccons ::= NULL onconf */ yytestcase(yyruleno==361); + /* (362) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==362); + /* (363) ccons ::= AS generated */ yytestcase(yyruleno==363); + /* (364) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==364); + /* (365) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==365); + /* (366) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=366); + /* (367) tconscomma ::= */ yytestcase(yyruleno==367); + /* (368) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=368); + /* (369) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=369); + /* (370) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=370); + /* (371) oneselect ::= values */ yytestcase(yyruleno==371); + /* (372) sclp ::= selcollist COMMA */ yytestcase(yyruleno==372); + /* (373) as ::= ID|STRING */ yytestcase(yyruleno==373); + /* (374) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=374); + /* (375) returning ::= */ yytestcase(yyruleno==375); + /* (376) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=376); + /* (377) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==377); + /* (378) case_operand ::= expr */ yytestcase(yyruleno==378); + /* (379) exprlist ::= nexprlist */ yytestcase(yyruleno==379); + /* (380) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=380); + /* (381) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=381); + /* (382) nmnum ::= ON */ yytestcase(yyruleno==382); + /* (383) nmnum ::= DELETE */ yytestcase(yyruleno==383); + /* (384) nmnum ::= DEFAULT */ yytestcase(yyruleno==384); + /* (385) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==385); + /* (386) foreach_clause ::= */ yytestcase(yyruleno==386); + /* (387) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==387); + /* (388) trnm ::= nm */ yytestcase(yyruleno==388); + /* (389) tridxby ::= */ yytestcase(yyruleno==389); + /* (390) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==390); + /* (391) database_kw_opt ::= */ yytestcase(yyruleno==391); + /* (392) kwcolumn_opt ::= */ yytestcase(yyruleno==392); + /* (393) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==393); + /* (394) vtabarglist ::= vtabarg */ yytestcase(yyruleno==394); + /* (395) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==395); + /* (396) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==396); + /* (397) anylist ::= */ yytestcase(yyruleno==397); + /* (398) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==398); + /* (399) anylist ::= anylist ANY */ yytestcase(yyruleno==399); + /* (400) with ::= */ yytestcase(yyruleno==400); + /* (401) windowdefn_list ::= windowdefn (OPTIMIZED OUT) */ assert(yyruleno!=401); + /* (402) window ::= frame_opt (OPTIMIZED OUT) */ assert(yyruleno!=402); break; /********** End reduce actions ************************************************/ }; @@ -173601,180 +175105,179 @@ static const unsigned char aKWCode[148] = {0, static int keywordCode(const char *z, int n, int *pType){ int i, j; const char *zKW; - if( n>=2 ){ - i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127; - for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){ - if( aKWLen[i]!=n ) continue; - zKW = &zKWText[aKWOffset[i]]; + assert( n>=2 ); + i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127; + for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){ + if( aKWLen[i]!=n ) continue; + zKW = &zKWText[aKWOffset[i]]; #ifdef SQLITE_ASCII - if( (z[0]&~0x20)!=zKW[0] ) continue; - if( (z[1]&~0x20)!=zKW[1] ) continue; - j = 2; - while( j=2 ) keywordCode((char*)z, n, &id); return id; } #define SQLITE_N_KEYWORD 147 @@ -174079,7 +175582,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); - testcase( z[0]=='9' ); + testcase( z[0]=='9' ); testcase( z[0]=='.' ); *tokenType = TK_INTEGER; #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ @@ -174151,7 +175654,8 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ return i; } case CC_KYWD0: { - for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} + if( aiClass[z[1]]>CC_KYWD ){ i = 1; break; } + for(i=2; aiClass[z[i]]<=CC_KYWD; i++){} if( IdChar(z[i]) ){ /* This token started out using characters that can appear in keywords, ** but z[i] is a character not allowed within keywords, so this must @@ -174930,12 +176434,6 @@ static int sqlite3TestExtInit(sqlite3 *db){ ** Forward declarations of external module initializer functions ** for modules that need them. */ -#ifdef SQLITE_ENABLE_FTS1 -SQLITE_PRIVATE int sqlite3Fts1Init(sqlite3*); -#endif -#ifdef SQLITE_ENABLE_FTS2 -SQLITE_PRIVATE int sqlite3Fts2Init(sqlite3*); -#endif #ifdef SQLITE_ENABLE_FTS5 SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); #endif @@ -174948,12 +176446,6 @@ SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3*); ** built-in extensions. */ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { -#ifdef SQLITE_ENABLE_FTS1 - sqlite3Fts1Init, -#endif -#ifdef SQLITE_ENABLE_FTS2 - sqlite3Fts2Init, -#endif #ifdef SQLITE_ENABLE_FTS3 sqlite3Fts3Init, #endif @@ -176566,9 +178058,9 @@ static int sqliteDefaultBusyCallback( void *ptr, /* Database connection */ int count /* Number of times table has been busy */ ){ -#if SQLITE_OS_WIN || HAVE_USLEEP +#if SQLITE_OS_WIN || !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP /* This case is for systems that have support for sleeping for fractions of - ** a second. Examples: All windows systems, unix systems with usleep() */ + ** a second. Examples: All windows systems, unix systems with nanosleep() */ static const u8 delays[] = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; static const u8 totals[] = @@ -178206,7 +179698,7 @@ static int openDatabase( ** 0 off off ** ** Legacy behavior is 3 (double-quoted string literals are allowed anywhere) -** and so that is the default. But developers are encouranged to use +** and so that is the default. But developers are encouraged to use ** -DSQLITE_DQS=0 (best) or -DSQLITE_DQS=1 (second choice) if possible. */ #if !defined(SQLITE_DQS) @@ -178741,7 +180233,7 @@ SQLITE_API int sqlite3_table_column_metadata( /* Find the column for which info is requested */ if( zColumnName==0 ){ - /* Query for existance of table only */ + /* Query for existence of table only */ }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; @@ -179061,10 +180553,12 @@ SQLITE_API int sqlite3_test_control(int op, ...){ sqlite3ShowSrcList(0); sqlite3ShowWith(0); sqlite3ShowUpsert(0); +#ifndef SQLITE_OMIT_TRIGGER sqlite3ShowTriggerStep(0); sqlite3ShowTriggerStepList(0); sqlite3ShowTrigger(0); sqlite3ShowTriggerList(0); +#endif #ifndef SQLITE_OMIT_WINDOWFUNC sqlite3ShowWindow(0); sqlite3ShowWinFunc(0); @@ -179181,7 +180675,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** formed and never corrupt. This flag is clear by default, indicating that ** database files might have arbitrary corruption. Setting the flag during ** testing causes certain assert() statements in the code to be activated - ** that demonstrat invariants on well-formed database files. + ** that demonstrate invariants on well-formed database files. */ case SQLITE_TESTCTRL_NEVER_CORRUPT: { sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int); @@ -179335,7 +180829,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** ** op==0 Store the current sqlite3TreeTrace in *ptr ** op==1 Set sqlite3TreeTrace to the value *ptr - ** op==3 Store the current sqlite3WhereTrace in *ptr + ** op==2 Store the current sqlite3WhereTrace in *ptr ** op==3 Set sqlite3WhereTrace to the value *ptr */ case SQLITE_TESTCTRL_TRACEFLAGS: { @@ -179371,6 +180865,23 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } +#if !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_USELONGDOUBLE, int X); + ** + ** X<0 Make no changes to the bUseLongDouble. Just report value. + ** X==0 Disable bUseLongDouble + ** X==1 Enable bUseLongDouble + ** X==2 Set bUseLongDouble to its default value for this platform + */ + case SQLITE_TESTCTRL_USELONGDOUBLE: { + int b = va_arg(ap, int); + if( b==2 ) b = sizeof(LONGDOUBLE_TYPE)>8; + if( b>=0 ) sqlite3Config.bUseLongDouble = b>0; + rc = sqlite3Config.bUseLongDouble!=0; + break; + } +#endif + #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) @@ -179671,7 +181182,7 @@ SQLITE_API int sqlite3_snapshot_get( } /* -** Open a read-transaction on the snapshot idendified by pSnapshot. +** Open a read-transaction on the snapshot identified by pSnapshot. */ SQLITE_API int sqlite3_snapshot_open( sqlite3 *db, @@ -195706,6 +197217,7 @@ static int fts3IncrmergeLoad( for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){ NodeReader reader; + memset(&reader, 0, sizeof(reader)); pNode = &pWriter->aNodeWriter[i]; if( pNode->block.a){ @@ -196576,7 +198088,7 @@ static u64 fts3ChecksumIndex( int rc; u64 cksum = 0; - assert( *pRc==SQLITE_OK ); + if( *pRc ) return 0; memset(&filter, 0, sizeof(filter)); memset(&csr, 0, sizeof(csr)); @@ -199752,25 +201264,51 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ ** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). */ static const char jsonIsSpace[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #define fast_isspace(x) (jsonIsSpace[(unsigned char)x]) +/* +** Characters that are special to JSON. Control charaters, +** '"' and '\\'. +*/ +static const char jsonIsOk[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; + + #if !defined(SQLITE_DEBUG) && !defined(SQLITE_COVERAGE_TEST) # define VVA(X) #else @@ -199781,6 +201319,7 @@ static const char jsonIsSpace[] = { typedef struct JsonString JsonString; typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; +typedef struct JsonCleanup JsonCleanup; /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator @@ -199796,16 +201335,26 @@ struct JsonString { char zSpace[100]; /* Initial static space */ }; +/* A deferred cleanup task. A list of JsonCleanup objects might be +** run when the JsonParse object is destroyed. +*/ +struct JsonCleanup { + JsonCleanup *pJCNext; /* Next in a list */ + void (*xOp)(void*); /* Routine to run */ + void *pArg; /* Argument to xOp() */ +}; + /* JSON type values */ -#define JSON_NULL 0 -#define JSON_TRUE 1 -#define JSON_FALSE 2 -#define JSON_INT 3 -#define JSON_REAL 4 -#define JSON_STRING 5 -#define JSON_ARRAY 6 -#define JSON_OBJECT 7 +#define JSON_SUBST 0 /* Special edit node. Uses u.iPrev */ +#define JSON_NULL 1 +#define JSON_TRUE 2 +#define JSON_FALSE 3 +#define JSON_INT 4 +#define JSON_REAL 5 +#define JSON_STRING 6 +#define JSON_ARRAY 7 +#define JSON_OBJECT 8 /* The "subtype" set for JSON values */ #define JSON_SUBTYPE 74 /* Ascii for "J" */ @@ -199814,52 +201363,87 @@ struct JsonString { ** Names of the various JSON types: */ static const char * const jsonType[] = { + "subst", "null", "true", "false", "integer", "real", "text", "array", "object" }; /* Bit values for the JsonNode.jnFlag field */ -#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ -#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ -#define JNODE_REMOVE 0x04 /* Do not output */ -#define JNODE_REPLACE 0x08 /* Replace with JsonNode.u.iReplace */ -#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */ -#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */ -#define JNODE_LABEL 0x40 /* Is a label of an object */ -#define JNODE_JSON5 0x80 /* Node contains JSON5 enhancements */ +#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ +#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ +#define JNODE_REMOVE 0x04 /* Do not output */ +#define JNODE_REPLACE 0x08 /* Target of a JSON_SUBST node */ +#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ +#define JNODE_LABEL 0x20 /* Is a label of an object */ +#define JNODE_JSON5 0x40 /* Node contains JSON5 enhancements */ -/* A single node of parsed JSON +/* A single node of parsed JSON. An array of these nodes describes +** a parse of JSON + edits. +** +** Use the json_parse() SQL function (available when compiled with +** -DSQLITE_DEBUG) to see a dump of complete JsonParse objects, including +** a complete listing and decoding of the array of JsonNodes. */ struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ u8 eU; /* Which union element to use */ - u32 n; /* Bytes of content, or number of sub-nodes */ + u32 n; /* Bytes of content for INT, REAL or STRING + ** Number of sub-nodes for ARRAY and OBJECT + ** Node that SUBST applies to */ union { const char *zJContent; /* 1: Content for INT, REAL, and STRING */ u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ - u32 iReplace; /* 4: Replacement content for JNODE_REPLACE */ - JsonNode *pPatch; /* 5: Node chain of patch for JNODE_PATCH */ + u32 iPrev; /* 4: Previous SUBST node, or 0 */ } u; }; -/* A completely parsed JSON string + +/* A parsed and possibly edited JSON string. Lifecycle: +** +** 1. JSON comes in and is parsed into an array aNode[]. The original +** JSON text is stored in zJson. +** +** 2. Zero or more changes are made (via json_remove() or json_replace() +** or similar) to the aNode[] array. +** +** 3. A new, edited and mimified JSON string is generated from aNode +** and stored in zAlt. The JsonParse object always owns zAlt. +** +** Step 1 always happens. Step 2 and 3 may or may not happen, depending +** on the operation. +** +** aNode[].u.zJContent entries typically point into zJson. Hence zJson +** must remain valid for the lifespan of the parse. For edits, +** aNode[].u.zJContent might point to malloced space other than zJson. +** Entries in pClup are responsible for freeing that extra malloced space. +** +** When walking the parse tree in aNode[], edits are ignored if useMod is +** false. */ struct JsonParse { u32 nNode; /* Number of slots of aNode[] used */ u32 nAlloc; /* Number of slots of aNode[] allocated */ JsonNode *aNode; /* Array of nodes containing the parse */ - const char *zJson; /* Original JSON string */ + char *zJson; /* Original JSON string (before edits) */ + char *zAlt; /* Revised and/or mimified JSON */ u32 *aUp; /* Index of parent of each node */ + JsonCleanup *pClup;/* Cleanup operations prior to freeing this object */ u16 iDepth; /* Nesting depth */ u8 nErr; /* Number of errors seen */ u8 oom; /* Set to true if out of memory */ + u8 bJsonIsRCStr; /* True if zJson is an RCStr */ u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ + u8 useMod; /* Actually use the edits contain inside aNode */ + u8 hasMod; /* aNode contains edits from the original zJson */ + u32 nJPRef; /* Number of references to this object */ int nJson; /* Length of the zJson string in bytes */ + int nAlt; /* Length of alternative JSON string zAlt, in bytes */ u32 iErr; /* Error location in zJson[] */ - u32 iHold; /* Replace cache line with the lowest iHold value */ + u32 iSubst; /* Last JSON_SUBST entry in aNode[] */ + u32 iHold; /* Age of this entry in the cache for LRU replacement */ }; /* @@ -199892,16 +201476,14 @@ static void jsonInit(JsonString *p, sqlite3_context *pCtx){ jsonZero(p); } - /* Free all allocated memory and reset the JsonString object back to its ** initial state. */ static void jsonReset(JsonString *p){ - if( !p->bStatic ) sqlite3_free(p->zBuf); + if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); jsonZero(p); } - /* Report an out-of-memory (OOM) condition */ static void jsonOom(JsonString *p){ @@ -199918,7 +201500,7 @@ static int jsonGrow(JsonString *p, u32 N){ char *zNew; if( p->bStatic ){ if( p->bErr ) return 1; - zNew = sqlite3_malloc64(nTotal); + zNew = sqlite3RCStrNew(nTotal); if( zNew==0 ){ jsonOom(p); return SQLITE_NOMEM; @@ -199927,12 +201509,12 @@ static int jsonGrow(JsonString *p, u32 N){ p->zBuf = zNew; p->bStatic = 0; }else{ - zNew = sqlite3_realloc64(p->zBuf, nTotal); - if( zNew==0 ){ - jsonOom(p); + p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); + if( p->zBuf==0 ){ + p->bErr = 1; + jsonZero(p); return SQLITE_NOMEM; } - p->zBuf = zNew; } p->nAlloc = nTotal; return SQLITE_OK; @@ -199940,12 +201522,35 @@ static int jsonGrow(JsonString *p, u32 N){ /* Append N bytes from zIn onto the end of the JsonString string. */ -static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ - if( N==0 ) return; - if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return; +static SQLITE_NOINLINE void jsonAppendExpand( + JsonString *p, + const char *zIn, + u32 N +){ + assert( N>0 ); + if( jsonGrow(p,N) ) return; memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } +static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ + if( N==0 ) return; + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} +static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ + assert( N>0 ); + if( N+p->nUsed >= p->nAlloc ){ + jsonAppendExpand(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} + /* Append formatted text (not to exceed N bytes) to the JsonString. */ @@ -199960,10 +201565,35 @@ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ /* Append a single character */ -static void jsonAppendChar(JsonString *p, char c){ - if( p->nUsed>=p->nAlloc && jsonGrow(p,1)!=0 ) return; +static SQLITE_NOINLINE void jsonAppendCharExpand(JsonString *p, char c){ + if( jsonGrow(p,1) ) return; p->zBuf[p->nUsed++] = c; } +static void jsonAppendChar(JsonString *p, char c){ + if( p->nUsed>=p->nAlloc ){ + jsonAppendCharExpand(p,c); + }else{ + p->zBuf[p->nUsed++] = c; + } +} + +/* Try to force the string to be a zero-terminated RCStr string. +** +** Return true on success. Return false if an OOM prevents this +** from happening. +*/ +static int jsonForceRCStr(JsonString *p){ + jsonAppendChar(p, 0); + if( p->bErr ) return 0; + p->nUsed--; + if( p->bStatic==0 ) return 1; + p->nAlloc = 0; + p->nUsed++; + jsonGrow(p, p->nUsed); + p->nUsed--; + return p->bStatic==0; +} + /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. @@ -199972,7 +201602,8 @@ static void jsonAppendSeparator(JsonString *p){ char c; if( p->nUsed==0 ) return; c = p->zBuf[p->nUsed-1]; - if( c!='[' && c!='{' ) jsonAppendChar(p, ','); + if( c=='[' || c=='{' ) return; + jsonAppendChar(p, ','); } /* Append the N-byte string in zIn to the end of the JsonString string @@ -199986,11 +201617,16 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ p->zBuf[p->nUsed++] = '"'; for(i=0; izBuf[p->nUsed++] = c; + }else if( c=='"' || c=='\\' ){ json_simple_escape: if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; - }else if( c<=0x1f ){ + p->zBuf[p->nUsed++] = c; + }else if( c=='\'' ){ + p->zBuf[p->nUsed++] = c; + }else{ static const char aSpecial[] = { 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -200001,6 +201637,7 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ assert( aSpecial['\n']=='n' ); assert( aSpecial['\r']=='r' ); assert( aSpecial['\t']=='t' ); + assert( c>=0 && czBuf[p->nUsed++] = 'u'; p->zBuf[p->nUsed++] = '0'; p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = '0' + (c>>4); - c = "0123456789abcdef"[c&0xf]; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c>>4]; + p->zBuf[p->nUsed++] = "0123456789abcdef"[c&0xf]; } - p->zBuf[p->nUsed++] = c; } p->zBuf[p->nUsed++] = '"'; assert( p->nUsednAlloc ); @@ -200032,7 +201668,7 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ while( N>0 ){ for(i=0; i0 ){ - jsonAppendRaw(p, zIn, i); + jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; if( N==0 ) break; @@ -200043,16 +201679,16 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ jsonAppendChar(p, '\''); break; case 'v': - jsonAppendRaw(p, "\\u0009", 6); + jsonAppendRawNZ(p, "\\u0009", 6); break; case 'x': - jsonAppendRaw(p, "\\u00", 4); - jsonAppendRaw(p, &zIn[2], 2); + jsonAppendRawNZ(p, "\\u00", 4); + jsonAppendRawNZ(p, &zIn[2], 2); zIn += 2; N -= 2; break; case '0': - jsonAppendRaw(p, "\\u0000", 6); + jsonAppendRawNZ(p, "\\u0000", 6); break; case '\r': if( zIn[2]=='\n' ){ @@ -200070,7 +201706,7 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ N -= 2; break; default: - jsonAppendRaw(p, zIn, 2); + jsonAppendRawNZ(p, zIn, 2); break; } zIn += 2; @@ -200100,11 +201736,12 @@ static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ jsonPrintf(100,p,"%lld",i); }else{ assert( rc==2 ); - jsonAppendRaw(p, "9.0e999", 7); + jsonAppendRawNZ(p, "9.0e999", 7); } return; } - jsonAppendRaw(p, zIn, N); + assert( N>0 ); + jsonAppendRawNZ(p, zIn, N); } /* @@ -200136,7 +201773,7 @@ static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ } } if( N>0 ){ - jsonAppendRaw(p, zIn, N); + jsonAppendRawNZ(p, zIn, N); } } @@ -200152,7 +201789,7 @@ static void jsonAppendValue( ){ switch( sqlite3_value_type(pValue) ){ case SQLITE_NULL: { - jsonAppendRaw(p, "null", 4); + jsonAppendRawNZ(p, "null", 4); break; } case SQLITE_FLOAT: { @@ -200188,15 +201825,25 @@ static void jsonAppendValue( /* Make the JSON in p the result of the SQL function. +** +** The JSON string is reset. */ static void jsonResult(JsonString *p){ if( p->bErr==0 ){ - sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, - p->bStatic ? SQLITE_TRANSIENT : sqlite3_free, - SQLITE_UTF8); - jsonZero(p); + if( p->bStatic ){ + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + }else if( jsonForceRCStr(p) ){ + sqlite3RCStrRef(p->zBuf); + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + (void(*)(void*))sqlite3RCStrUnref, + SQLITE_UTF8); + } } - assert( p->bStatic ); + if( p->bErr==1 ){ + sqlite3_result_error_nomem(p->pCtx); + } + jsonReset(p); } /************************************************************************** @@ -200221,20 +201868,73 @@ static u32 jsonNodeSize(JsonNode *pNode){ ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ - sqlite3_free(pParse->aNode); - pParse->aNode = 0; + while( pParse->pClup ){ + JsonCleanup *pTask = pParse->pClup; + pParse->pClup = pTask->pJCNext; + pTask->xOp(pTask->pArg); + sqlite3_free(pTask); + } + assert( pParse->nJPRef<=1 ); + if( pParse->aNode ){ + sqlite3_free(pParse->aNode); + pParse->aNode = 0; + } pParse->nNode = 0; pParse->nAlloc = 0; - sqlite3_free(pParse->aUp); - pParse->aUp = 0; + if( pParse->aUp ){ + sqlite3_free(pParse->aUp); + pParse->aUp = 0; + } + if( pParse->bJsonIsRCStr ){ + sqlite3RCStrUnref(pParse->zJson); + pParse->zJson = 0; + pParse->bJsonIsRCStr = 0; + } + if( pParse->zAlt ){ + sqlite3RCStrUnref(pParse->zAlt); + pParse->zAlt = 0; + } } /* ** Free a JsonParse object that was obtained from sqlite3_malloc(). +** +** Note that destroying JsonParse might call sqlite3RCStrUnref() to +** destroy the zJson value. The RCStr object might recursively invoke +** JsonParse to destroy this pParse object again. Take care to ensure +** that this recursive destructor sequence terminates harmlessly. */ static void jsonParseFree(JsonParse *pParse){ - jsonParseReset(pParse); - sqlite3_free(pParse); + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3_free(pParse); + } +} + +/* +** Add a cleanup task to the JsonParse object. +** +** If an OOM occurs, the cleanup operation happens immediately +** and this function returns SQLITE_NOMEM. +*/ +static int jsonParseAddCleanup( + JsonParse *pParse, /* Add the cleanup task to this parser */ + void(*xOp)(void*), /* The cleanup task */ + void *pArg /* Argument to the cleanup */ +){ + JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); + if( pTask==0 ){ + pParse->oom = 1; + xOp(pArg); + return SQLITE_ERROR; + } + pTask->pJCNext = pParse->pClup; + pParse->pClup = pTask; + pTask->xOp = xOp; + pTask->pArg = pArg; + return SQLITE_OK; } /* @@ -200243,32 +201943,38 @@ static void jsonParseFree(JsonParse *pParse){ ** the number of JsonNode objects that are encoded. */ static void jsonRenderNode( + JsonParse *pParse, /* the complete parse of the JSON */ JsonNode *pNode, /* The node to render */ - JsonString *pOut, /* Write JSON here */ - sqlite3_value **aReplace /* Replacement values */ + JsonString *pOut /* Write JSON here */ ){ assert( pNode!=0 ); - if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ - if( (pNode->jnFlags & JNODE_REPLACE)!=0 && ALWAYS(aReplace!=0) ){ - assert( pNode->eU==4 ); - jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); - return; + while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ + u32 idx = (u32)(pNode - pParse->aNode); + u32 i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( inNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ + pNode = &pParse->aNode[i+1]; + break; + } + i = pParse->aNode[i].u.iPrev; } - assert( pNode->eU==5 ); - pNode = pNode->u.pPatch; } switch( pNode->eType ){ default: { assert( pNode->eType==JSON_NULL ); - jsonAppendRaw(pOut, "null", 4); + jsonAppendRawNZ(pOut, "null", 4); break; } case JSON_TRUE: { - jsonAppendRaw(pOut, "true", 4); + jsonAppendRawNZ(pOut, "true", 4); break; } case JSON_FALSE: { - jsonAppendRaw(pOut, "false", 5); + jsonAppendRawNZ(pOut, "false", 5); break; } case JSON_STRING: { @@ -200284,7 +201990,8 @@ static void jsonRenderNode( }else if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -200293,7 +202000,8 @@ static void jsonRenderNode( if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -200302,7 +202010,8 @@ static void jsonRenderNode( if( pNode->jnFlags & JNODE_JSON5 ){ jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); }else{ - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); + assert( pNode->n>0 ); + jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); } break; } @@ -200311,15 +202020,16 @@ static void jsonRenderNode( jsonAppendChar(pOut, '['); for(;;){ while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){ + if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j], pOut); } j += jsonNodeSize(&pNode[j]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); - pNode = &pNode[pNode->u.iAppend]; + pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, ']'); @@ -200330,17 +202040,18 @@ static void jsonRenderNode( jsonAppendChar(pOut, '{'); for(;;){ while( j<=pNode->n ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){ + if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j], pOut); jsonAppendChar(pOut, ':'); - jsonRenderNode(&pNode[j+1], pOut, aReplace); + jsonRenderNode(pParse, &pNode[j+1], pOut); } j += 1 + jsonNodeSize(&pNode[j+1]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); - pNode = &pNode[pNode->u.iAppend]; + pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, '}'); @@ -200350,18 +202061,29 @@ static void jsonRenderNode( } /* -** Return a JsonNode and all its descendents as a JSON string. +** Return a JsonNode and all its descendants as a JSON string. */ static void jsonReturnJson( + JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ + int bGenerateAlt /* Also store the rendered text in zAlt */ ){ JsonString s; - jsonInit(&s, pCtx); - jsonRenderNode(pNode, &s, aReplace); - jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + if( pParse->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( pParse->nErr==0 ){ + jsonInit(&s, pCtx); + jsonRenderNode(pParse, pNode, &s); + if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ + pParse->zAlt = sqlite3RCStrRef(s.zBuf); + pParse->nAlt = s.nUsed; + } + jsonResult(&s); + sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + } } /* @@ -200399,9 +202121,9 @@ static u32 jsonHexToInt4(const char *z){ ** Make the JsonNode the return value of the function. */ static void jsonReturn( + JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ + sqlite3_context *pCtx /* Return value for this function */ ){ switch( pNode->eType ){ default: { @@ -200423,7 +202145,6 @@ static void jsonReturn( int bNeg = 0; const char *z; - assert( pNode->eU==1 ); z = pNode->u.zJContent; if( z[0]=='-' ){ z++; bNeg = 1; } @@ -200548,7 +202269,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - jsonReturnJson(pNode, pCtx, aReplace); + jsonReturnJson(pParse, pNode, pCtx, 0); break; } } @@ -200570,6 +202291,12 @@ static int jsonParseAddNode(JsonParse*,u32,u32,const char*); #endif +/* +** Add a single node to pParse->aNode after first expanding the +** size of the aNode array. Return the index of the new node. +** +** If an OOM error occurs, set pParse->oom and return -1. +*/ static JSON_NOINLINE int jsonParseAddNodeExpand( JsonParse *pParse, /* Append the node to this object */ u32 eType, /* Node type */ @@ -200586,7 +202313,7 @@ static JSON_NOINLINE int jsonParseAddNodeExpand( pParse->oom = 1; return -1; } - pParse->nAlloc = nNew; + pParse->nAlloc = sqlite3_msize(pNew)/sizeof(JsonNode); pParse->aNode = pNew; assert( pParse->nNodenAlloc ); return jsonParseAddNode(pParse, eType, n, zContent); @@ -200604,10 +202331,13 @@ static int jsonParseAddNode( const char *zContent /* Content */ ){ JsonNode *p; - if( pParse->aNode==0 || pParse->nNode>=pParse->nAlloc ){ + assert( pParse->aNode!=0 || pParse->nNode>=pParse->nAlloc ); + if( pParse->nNode>=pParse->nAlloc ){ return jsonParseAddNodeExpand(pParse, eType, n, zContent); } + assert( pParse->aNode!=0 ); p = &pParse->aNode[pParse->nNode]; + assert( p!=0 ); p->eType = (u8)(eType & 0xff); p->jnFlags = (u8)(eType >> 8); VVA( p->eU = zContent ? 1 : 0 ); @@ -200616,6 +202346,52 @@ static int jsonParseAddNode( return pParse->nNode++; } +/* +** Add an array of new nodes to the current pParse->aNode array. +** Return the index of the first node added. +** +** If an OOM error occurs, set pParse->oom. +*/ +static void jsonParseAddNodeArray( + JsonParse *pParse, /* Append the node to this object */ + JsonNode *aNode, /* Array of nodes to add */ + u32 nNode /* Number of elements in aNew */ +){ + assert( aNode!=0 ); + assert( nNode>=1 ); + if( pParse->nNode + nNode > pParse->nAlloc ){ + u32 nNew = pParse->nNode + nNode; + JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); + if( aNew==0 ){ + pParse->oom = 1; + return; + } + pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); + pParse->aNode = aNew; + } + memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); + pParse->nNode += nNode; +} + +/* +** Add a new JSON_SUBST node. The node immediately following +** this new node will be the substitute content for iNode. +*/ +static int jsonParseAddSubstNode( + JsonParse *pParse, /* Add the JSON_SUBST here */ + u32 iNode /* References this node */ +){ + int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); + if( pParse->oom ) return -1; + pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; + pParse->aNode[idx].eU = 4; + pParse->aNode[idx].u.iPrev = pParse->iSubst; + pParse->iSubst = idx; + pParse->hasMod = 1; + pParse->useMod = 1; + return idx; +} + /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ @@ -200782,7 +202558,7 @@ static const struct NanInfName { ** ** Special return values: ** -** 0 End if input +** 0 End of input ** -1 Syntax error ** -2 '}' seen ** -3 ']' seen @@ -200957,15 +202733,12 @@ json_parse_restart: jnFlags = 0; parse_string: cDelim = z[i]; - j = i+1; - for(;;){ + for(j=i+1; 1; j++){ + if( jsonIsOk[(unsigned char)z[j]] ) continue; c = z[j]; - if( (c & ~0x1f)==0 ){ - /* Control characters are not allowed in strings */ - pParse->iErr = j; - return -1; - } - if( c=='\\' ){ + if( c==cDelim ){ + break; + }else if( c=='\\' ){ c = z[++j]; if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' @@ -200985,10 +202758,11 @@ json_parse_restart: pParse->iErr = j; return -1; } - }else if( c==cDelim ){ - break; + }else if( c<=0x1f ){ + /* Control characters are not allowed in strings */ + pParse->iErr = j; + return -1; } - j++; } jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]); return j+1; @@ -201224,20 +202998,18 @@ json_parse_restart: /* ** Parse a complete JSON string. Return 0 on success or non-zero if there -** are any errors. If an error occurs, free all memory associated with -** pParse. +** are any errors. If an error occurs, free all memory held by pParse, +** but not pParse itself. ** -** pParse is uninitialized when this routine is called. +** pParse must be initialized to an empty parse object prior to calling +** this routine. */ static int jsonParse( JsonParse *pParse, /* Initialize and fill this JsonParse object */ - sqlite3_context *pCtx, /* Report errors here */ - const char *zJson /* Input JSON text to be parsed */ + sqlite3_context *pCtx /* Report errors here */ ){ int i; - memset(pParse, 0, sizeof(*pParse)); - if( zJson==0 ) return 1; - pParse->zJson = zJson; + const char *zJson = pParse->zJson; i = jsonParseValue(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ @@ -201266,6 +203038,7 @@ static int jsonParse( return 0; } + /* Mark node i of pParse as being a child of iParent. Call recursively ** to fill in all the descendants of node i. */ @@ -201315,35 +203088,49 @@ static int jsonParseFindParents(JsonParse *pParse){ #define JSON_CACHE_SZ 4 /* Max number of cache entries */ /* -** Obtain a complete parse of the JSON found in the first argument -** of the argv array. Use the sqlite3_get_auxdata() cache for this -** parse if it is available. If the cache is not available or if it -** is no longer valid, parse the JSON again and return the new parse, -** and also register the new parse so that it will be available for +** Obtain a complete parse of the JSON found in the pJson argument +** +** Use the sqlite3_get_auxdata() cache to find a preexisting parse +** if it is available. If the cache is not available or if it +** is no longer valid, parse the JSON again and return the new parse. +** Also register the new parse so that it will be available for ** future sqlite3_get_auxdata() calls. ** ** If an error occurs and pErrCtx!=0 then report the error on pErrCtx ** and return NULL. ** -** If an error occurs and pErrCtx==0 then return the Parse object with -** JsonParse.nErr non-zero. If the caller invokes this routine with -** pErrCtx==0 and it gets back a JsonParse with nErr!=0, then the caller -** is responsible for invoking jsonParseFree() on the returned value. -** But the caller may invoke jsonParseFree() *only* if pParse->nErr!=0. +** The returned pointer (if it is not NULL) is owned by the cache in +** most cases, not the caller. The caller does NOT need to invoke +** jsonParseFree(), in most cases. +** +** Except, if an error occurs and pErrCtx==0 then return the JsonParse +** object with JsonParse.nErr non-zero and the caller will own the JsonParse +** object. In that case, it will be the responsibility of the caller to +** invoke jsonParseFree(). To summarize: +** +** pErrCtx!=0 || p->nErr==0 ==> Return value p is owned by the +** cache. Call does not need to +** free it. +** +** pErrCtx==0 && p->nErr!=0 ==> Return value is owned by the caller +** and so the caller must free it. */ static JsonParse *jsonParseCached( - sqlite3_context *pCtx, - sqlite3_value **argv, - sqlite3_context *pErrCtx + sqlite3_context *pCtx, /* Context to use for cache search */ + sqlite3_value *pJson, /* Function param containing JSON text */ + sqlite3_context *pErrCtx, /* Write parse errors here if not NULL */ + int bUnedited /* No prior edits allowed */ ){ - const char *zJson = (const char*)sqlite3_value_text(argv[0]); - int nJson = sqlite3_value_bytes(argv[0]); + char *zJson = (char*)sqlite3_value_text(pJson); + int nJson = sqlite3_value_bytes(pJson); JsonParse *p; JsonParse *pMatch = 0; int iKey; int iMinKey = 0; u32 iMinHold = 0xffffffff; u32 iMaxHold = 0; + int bJsonRCStr; + if( zJson==0 ) return 0; for(iKey=0; iKeynJson==nJson - && memcmp(p->zJson,zJson,nJson)==0 + && (p->hasMod==0 || bUnedited==0) + && (p->zJson==zJson || memcmp(p->zJson,zJson,nJson)==0) ){ p->nErr = 0; + p->useMod = 0; + pMatch = p; + }else + if( pMatch==0 + && p->zAlt!=0 + && bUnedited==0 + && p->nAlt==nJson + && memcmp(p->zAlt, zJson, nJson)==0 + ){ + p->nErr = 0; + p->useMod = 1; pMatch = p; }else if( p->iHoldiHold; @@ -201366,28 +203165,44 @@ static JsonParse *jsonParseCached( } } if( pMatch ){ + /* The input JSON text was found in the cache. Use the preexisting + ** parse of this JSON */ pMatch->nErr = 0; pMatch->iHold = iMaxHold+1; + assert( pMatch->nJPRef>0 ); /* pMatch is owned by the cache */ return pMatch; } - p = sqlite3_malloc64( sizeof(*p) + nJson + 1 ); + + /* The input JSON was not found anywhere in the cache. We will need + ** to parse it ourselves and generate a new JsonParse object. + */ + bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref); + p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); if( p==0 ){ sqlite3_result_error_nomem(pCtx); return 0; } memset(p, 0, sizeof(*p)); - p->zJson = (char*)&p[1]; - memcpy((char*)p->zJson, zJson, nJson+1); - if( jsonParse(p, pErrCtx, p->zJson) ){ + if( bJsonRCStr ){ + p->zJson = sqlite3RCStrRef(zJson); + p->bJsonIsRCStr = 1; + }else{ + p->zJson = (char*)&p[1]; + memcpy(p->zJson, zJson, nJson+1); + } + p->nJPRef = 1; + if( jsonParse(p, pErrCtx) ){ if( pErrCtx==0 ){ p->nErr = 1; + assert( p->nJPRef==1 ); /* Caller will own the new JsonParse object p */ return p; } - sqlite3_free(p); + jsonParseFree(p); return 0; } p->nJson = nJson; p->iHold = iMaxHold+1; + /* Transfer ownership of the new JsonParse to the cache */ sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, (void(*)(void*))jsonParseFree); return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); @@ -201438,9 +203253,31 @@ static JsonNode *jsonLookupStep( ){ u32 i, j, nKey; const char *zKey; - JsonNode *pRoot = &pParse->aNode[iRoot]; + JsonNode *pRoot; + if( pParse->oom ) return 0; + pRoot = &pParse->aNode[iRoot]; + if( pRoot->jnFlags & (JNODE_REPLACE|JNODE_REMOVE) && pParse->useMod ){ + while( (pRoot->jnFlags & JNODE_REPLACE)!=0 ){ + u32 idx = (u32)(pRoot - pParse->aNode); + i = pParse->iSubst; + while( 1 /*exit-by-break*/ ){ + assert( inNode ); + assert( pParse->aNode[i].eType==JSON_SUBST ); + assert( pParse->aNode[i].eU==4 ); + assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ + pRoot = &pParse->aNode[i+1]; + iRoot = i+1; + break; + } + i = pParse->aNode[i].u.iPrev; + } + } + if( pRoot->jnFlags & JNODE_REMOVE ){ + return 0; + } + } if( zPath[0]==0 ) return pRoot; - if( pRoot->jnFlags & JNODE_REPLACE ) return 0; if( zPath[0]=='.' ){ if( pRoot->eType!=JSON_OBJECT ) return 0; zPath++; @@ -201474,14 +203311,16 @@ static JsonNode *jsonLookupStep( j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); - iRoot += pRoot->u.iAppend; + iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } if( pApnd ){ u32 iStart, iLabel; JsonNode *pNode; + assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); zPath += i; @@ -201490,7 +203329,7 @@ static JsonNode *jsonLookupStep( if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart - iRoot; + pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); pParse->aNode[iLabel].jnFlags |= JNODE_RAW; @@ -201511,12 +203350,13 @@ static JsonNode *jsonLookupStep( if( pRoot->eType!=JSON_ARRAY ) return 0; for(;;){ while( j<=pBase->n ){ - if( (pBase[j].jnFlags & JNODE_REMOVE)==0 ) i++; + if( (pBase[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i++; j += jsonNodeSize(&pBase[j]); } if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pBase->eU==2 ); - iBase += pBase->u.iAppend; + iBase = pBase->u.iAppend; pBase = &pParse->aNode[iBase]; j = 1; } @@ -201544,13 +203384,16 @@ static JsonNode *jsonLookupStep( zPath += j + 1; j = 1; for(;;){ - while( j<=pRoot->n && (i>0 || (pRoot[j].jnFlags & JNODE_REMOVE)!=0) ){ - if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 ) i--; + while( j<=pRoot->n + && (i>0 || ((pRoot[j].jnFlags & JNODE_REMOVE)!=0 && pParse->useMod)) + ){ + if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; + if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); - iRoot += pRoot->u.iAppend; + iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } @@ -201560,13 +203403,14 @@ static JsonNode *jsonLookupStep( if( i==0 && pApnd ){ u32 iStart; JsonNode *pNode; + assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); - pRoot->u.iAppend = iStart - iRoot; + pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); } @@ -201693,47 +203537,90 @@ static void jsonRemoveAllNulls(JsonNode *pNode){ ** SQL functions used for testing and debugging ****************************************************************************/ +#if SQLITE_DEBUG +/* +** Print N node entries. +*/ +static void jsonDebugPrintNodeEntries( + JsonNode *aNode, /* First node entry to print */ + int N /* Number of node entries to print */ +){ + int i; + for(i=0; iaNode, p->nNode); +} +static void jsonDebugPrintNode(JsonNode *pNode){ + jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); +} +#else + /* The usual case */ +# define jsonDebugPrintNode(X) +# define jsonDebugPrintParse(X) +#endif + #ifdef SQLITE_DEBUG /* -** The json_parse(JSON) function returns a string which describes -** a parse of the JSON provided. Or it returns NULL if JSON is not -** well-formed. +** SQL function: json_parse(JSON) +** +** Parse JSON using jsonParseCached(). Then print a dump of that +** parse on standard output. Return the mimified JSON result, just +** like the json() function. */ static void jsonParseFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ - JsonString s; /* Output string - not real JSON */ - JsonParse x; /* The parse */ - u32 i; + JsonParse *p; /* The parse */ assert( argc==1 ); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - jsonParseFindParents(&x); - jsonInit(&s, ctx); - for(i=0; inNode); + printf("nAlloc = %u\n", p->nAlloc); + printf("nJson = %d\n", p->nJson); + printf("nAlt = %d\n", p->nAlt); + printf("nErr = %u\n", p->nErr); + printf("oom = %u\n", p->oom); + printf("hasNonstd = %u\n", p->hasNonstd); + printf("useMod = %u\n", p->useMod); + printf("hasMod = %u\n", p->hasMod); + printf("nJPRef = %u\n", p->nJPRef); + printf("iSubst = %u\n", p->iSubst); + printf("iHold = %u\n", p->iHold); + jsonDebugPrintNodeEntries(p->aNode, p->nNode); + jsonReturnJson(p, p->aNode, ctx, 1); } /* @@ -201817,7 +203704,7 @@ static void jsonArrayLengthFunc( u32 i; JsonNode *pNode; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; assert( p->nNode ); if( argc==2 ){ @@ -201830,9 +203717,16 @@ static void jsonArrayLengthFunc( return; } if( pNode->eType==JSON_ARRAY ){ - assert( (pNode->jnFlags & JNODE_APPEND)==0 ); - for(i=1; i<=pNode->n; n++){ - i += jsonNodeSize(&pNode[i]); + while( 1 /*exit-by-break*/ ){ + i = 1; + while( i<=pNode->n ){ + if( (pNode[i].jnFlags & JNODE_REMOVE)==0 ) n++; + i += jsonNodeSize(&pNode[i]); + } + if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; + if( p->useMod==0 ) break; + assert( pNode->eU==2 ); + pNode = &p->aNode[pNode->u.iAppend]; } } sqlite3_result_int64(ctx, n); @@ -201879,7 +203773,7 @@ static void jsonExtractFunc( JsonString jx; if( argc<2 ) return; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ /* With a single PATH argument */ @@ -201897,11 +203791,11 @@ static void jsonExtractFunc( */ jsonInit(&jx, ctx); if( sqlite3Isdigit(zPath[0]) ){ - jsonAppendRaw(&jx, "$[", 2); + jsonAppendRawNZ(&jx, "$[", 2); jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); - jsonAppendRaw(&jx, "]", 2); + jsonAppendRawNZ(&jx, "]", 2); }else{ - jsonAppendRaw(&jx, "$.", 1 + (zPath[0]!='[')); + jsonAppendRawNZ(&jx, "$.", 1 + (zPath[0]!='[')); jsonAppendRaw(&jx, zPath, (int)strlen(zPath)); jsonAppendChar(&jx, 0); } @@ -201912,15 +203806,15 @@ static void jsonExtractFunc( } if( pNode ){ if( flags & JSON_JSON ){ - jsonReturnJson(pNode, ctx, 0); + jsonReturnJson(p, pNode, ctx, 0); }else{ - jsonReturn(pNode, ctx, 0); + jsonReturn(p, pNode, ctx); sqlite3_result_subtype(ctx, 0); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(pNode, ctx, 0); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); } }else{ /* Two or more PATH arguments results in a JSON array with each @@ -201934,9 +203828,9 @@ static void jsonExtractFunc( if( p->nErr ) break; jsonAppendSeparator(&jx); if( pNode ){ - jsonRenderNode(pNode, &jx, 0); + jsonRenderNode(p, pNode, &jx); }else{ - jsonAppendRaw(&jx, "null", 4); + jsonAppendRawNZ(&jx, "null", 4); } } if( i==argc ){ @@ -201981,45 +203875,38 @@ static JsonNode *jsonMergePatch( assert( pTarget[j].eType==JSON_STRING ); assert( pTarget[j].jnFlags & JNODE_LABEL ); if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ - if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break; + if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ) break; if( pPatch[i+1].eType==JSON_NULL ){ pTarget[j+1].jnFlags |= JNODE_REMOVE; }else{ JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); if( pNew==0 ) return 0; - pTarget = &pParse->aNode[iTarget]; - if( pNew!=&pTarget[j+1] ){ - assert( pTarget[j+1].eU==0 - || pTarget[j+1].eU==1 - || pTarget[j+1].eU==2 ); - testcase( pTarget[j+1].eU==1 ); - testcase( pTarget[j+1].eU==2 ); - VVA( pTarget[j+1].eU = 5 ); - pTarget[j+1].u.pPatch = pNew; - pTarget[j+1].jnFlags |= JNODE_PATCH; + if( pNew!=&pParse->aNode[iTarget+j+1] ){ + jsonParseAddSubstNode(pParse, iTarget+j+1); + jsonParseAddNodeArray(pParse, pNew, jsonNodeSize(pNew)); } + pTarget = &pParse->aNode[iTarget]; } break; } } if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ - int iStart, iPatch; - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); + int iStart; + JsonNode *pApnd; + u32 nApnd; + iStart = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); - iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + pApnd = &pPatch[i+1]; + if( pApnd->eType==JSON_OBJECT ) jsonRemoveAllNulls(pApnd); + nApnd = jsonNodeSize(pApnd); + jsonParseAddNodeArray(pParse, pApnd, jsonNodeSize(pApnd)); if( pParse->oom ) return 0; - jsonRemoveAllNulls(pPatch); - pTarget = &pParse->aNode[iTarget]; - assert( pParse->aNode[iRoot].eU==0 || pParse->aNode[iRoot].eU==2 ); - testcase( pParse->aNode[iRoot].eU==2 ); + pParse->aNode[iStart].n = 1+nApnd; pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + pParse->aNode[iRoot].u.iAppend = iStart; VVA( pParse->aNode[iRoot].eU = 2 ); - pParse->aNode[iRoot].u.iAppend = iStart - iRoot; iRoot = iStart; - assert( pParse->aNode[iPatch].eU==0 ); - VVA( pParse->aNode[iPatch].eU = 5 ); - pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; - pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; + pTarget = &pParse->aNode[iTarget]; } } return pTarget; @@ -202035,25 +203922,28 @@ static void jsonPatchFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The JSON that is being patched */ - JsonParse y; /* The patch */ + JsonParse *pX; /* The JSON that is being patched */ + JsonParse *pY; /* The patch */ JsonNode *pResult; /* The result of the merge */ UNUSED_PARAMETER(argc); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){ - jsonParseReset(&x); - return; - } - pResult = jsonMergePatch(&x, 0, y.aNode); - assert( pResult!=0 || x.oom ); - if( pResult ){ - jsonReturnJson(pResult, ctx, 0); + pX = jsonParseCached(ctx, argv[0], ctx, 1); + if( pX==0 ) return; + assert( pX->hasMod==0 ); + pX->hasMod = 1; + pY = jsonParseCached(ctx, argv[1], ctx, 1); + if( pY==0 ) return; + pX->useMod = 1; + pY->useMod = 1; + pResult = jsonMergePatch(pX, 0, pY->aNode); + assert( pResult!=0 || pX->oom ); + if( pResult && pX->oom==0 ){ + jsonDebugPrintParse(pX); + jsonDebugPrintNode(pResult); + jsonReturnJson(pX, pResult, ctx, 0); }else{ sqlite3_result_error_nomem(ctx); } - jsonParseReset(&x); - jsonParseReset(&y); } @@ -202109,26 +203999,118 @@ static void jsonRemoveFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; if( argc<1 ) return; - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i++){ zPath = (const char*)sqlite3_value_text(argv[i]); if( zPath==0 ) goto remove_done; - pNode = jsonLookup(&x, zPath, 0, ctx); - if( x.nErr ) goto remove_done; - if( pNode ) pNode->jnFlags |= JNODE_REMOVE; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto remove_done; + if( pNode ){ + pNode->jnFlags |= JNODE_REMOVE; + pParse->hasMod = 1; + pParse->useMod = 1; + } } - if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(x.aNode, ctx, 0); + if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ + jsonReturnJson(pParse, pParse->aNode, ctx, 1); } remove_done: - jsonParseReset(&x); + jsonDebugPrintParse(p); +} + +/* +** Substitute the value at iNode with the pValue parameter. +*/ +static void jsonReplaceNode( + sqlite3_context *pCtx, + JsonParse *p, + int iNode, + sqlite3_value *pValue +){ + int idx = jsonParseAddSubstNode(p, iNode); + if( idx<=0 ){ + assert( p->oom ); + return; + } + switch( sqlite3_value_type(pValue) ){ + case SQLITE_NULL: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + break; + } + case SQLITE_FLOAT: { + char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_REAL, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + break; + } + case SQLITE_INTEGER: { + char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); + int n; + if( z==0 ){ + p->oom = 1; + break; + } + n = sqlite3Strlen30(z); + jsonParseAddNode(p, JSON_INT, n, z); + jsonParseAddCleanup(p, sqlite3_free, z); + + break; + } + case SQLITE_TEXT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + if( z==0 ){ + p->oom = 1; + break; + } + if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ + char *zCopy = sqlite3DbStrDup(0, z); + int k; + if( zCopy ){ + jsonParseAddCleanup(p, sqlite3_free, zCopy); + }else{ + p->oom = 1; + sqlite3_result_error_nomem(pCtx); + } + k = jsonParseAddNode(p, JSON_STRING, n, zCopy); + assert( k>0 || p->oom ); + if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; + }else{ + JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); + if( pPatch==0 ){ + p->oom = 1; + break; + } + jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); + /* The nodes copied out of pPatch and into p likely contain + ** u.zJContent pointers into pPatch->zJson. So preserve the + ** content of pPatch until p is destroyed. */ + assert( pPatch->nJPRef>=1 ); + pPatch->nJPRef++; + jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); + } + break; + } + default: { + jsonParseAddNode(p, JSON_NULL, 0, 0); + sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); + p->nErr++; + break; + } + } } /* @@ -202142,7 +204124,7 @@ static void jsonReplaceFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; @@ -202152,28 +204134,20 @@ static void jsonReplaceFunc( jsonWrongNumArgs(ctx, "replace"); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); - pNode = jsonLookup(&x, zPath, 0, ctx); - if( x.nErr ) goto replace_err; + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, 0, ctx); + if( pParse->nErr ) goto replace_err; if( pNode ){ - assert( pNode->eU==0 || pNode->eU==1 || pNode->eU==4 ); - testcase( pNode->eU!=0 && pNode->eU!=1 ); - pNode->jnFlags |= (u8)JNODE_REPLACE; - VVA( pNode->eU = 4 ); - pNode->u.iReplace = i + 1; + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - assert( x.aNode[0].eU==4 ); - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } + jsonReturnJson(pParse, pParse->aNode, ctx, 1); replace_err: - jsonParseReset(&x); + jsonDebugPrintParse(pParse); } @@ -202194,7 +204168,7 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ + JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; @@ -202206,33 +204180,27 @@ static void jsonSetFunc( jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); + pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); + if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); bApnd = 0; - pNode = jsonLookup(&x, zPath, &bApnd, ctx); - if( x.oom ){ + pParse->useMod = 1; + pNode = jsonLookup(pParse, zPath, &bApnd, ctx); + if( pParse->oom ){ sqlite3_result_error_nomem(ctx); goto jsonSetDone; - }else if( x.nErr ){ + }else if( pParse->nErr ){ goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ - testcase( pNode->eU!=0 && pNode->eU!=1 ); - assert( pNode->eU!=3 && pNode->eU!=5 ); - VVA( pNode->eU = 4 ); - pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->u.iReplace = i + 1; + jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - assert( x.aNode[0].eU==4 ); - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } + jsonDebugPrintParse(pParse); + jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonSetDone: - jsonParseReset(&x); + /* no cleanup required */; } /* @@ -202251,7 +204219,7 @@ static void jsonTypeFunc( const char *zPath; JsonNode *pNode; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); @@ -202277,13 +204245,19 @@ static void jsonValidFunc( ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - p = jsonParseCached(ctx, argv, 0); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ +#ifdef SQLITE_LEGACY_JSON_VALID + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); +#endif + return; + } + p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); }else{ - sqlite3_result_int(ctx, p->nErr==0 && p->hasNonstd==0); + sqlite3_result_int(ctx, p->nErr==0 && (p->hasNonstd==0 || p->useMod)); if( p->nErr ) jsonParseFree(p); } } @@ -202324,7 +204298,7 @@ static void jsonErrorFunc( JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - p = jsonParseCached(ctx, argv, 0); + p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); @@ -202333,7 +204307,7 @@ static void jsonErrorFunc( }else{ int n = 1; u32 i; - const char *z = p->zJson; + const char *z = (const char*)sqlite3_value_text(argv[0]); for(i=0; iiErr && ALWAYS(z[i]); i++){ if( (z[i]&0xc0)!=0x80 ) n++; } @@ -202381,7 +204355,8 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -202422,7 +204397,7 @@ static void jsonGroupInverse( pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); #ifdef NEVER /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will - ** always have been called to initalize it */ + ** always have been called to initialize it */ if( NEVER(!pStr) ) return; #endif z = pStr->zBuf; @@ -202489,7 +204464,8 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); @@ -202600,7 +204576,6 @@ static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ - sqlite3_free(p->zJson); sqlite3_free(p->zRoot); jsonParseReset(&p->sParse); p->iRowid = 0; @@ -202738,7 +204713,7 @@ static int jsonEachColumn( case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ @@ -202754,7 +204729,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_TYPE: { @@ -202765,7 +204740,7 @@ static int jsonEachColumn( case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(pThis, ctx, 0); + jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_ID: { @@ -202920,11 +204895,19 @@ static int jsonEachFilter( if( idxNum==0 ) return SQLITE_OK; z = (const char*)sqlite3_value_text(argv[0]); if( z==0 ) return SQLITE_OK; - n = sqlite3_value_bytes(argv[0]); - p->zJson = sqlite3_malloc64( n+1 ); - if( p->zJson==0 ) return SQLITE_NOMEM; - memcpy(p->zJson, z, (size_t)n+1); - if( jsonParse(&p->sParse, 0, p->zJson) ){ + memset(&p->sParse, 0, sizeof(p->sParse)); + p->sParse.nJPRef = 1; + if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){ + p->sParse.zJson = sqlite3RCStrRef((char*)z); + }else{ + n = sqlite3_value_bytes(argv[0]); + p->sParse.zJson = sqlite3RCStrNew( n+1 ); + if( p->sParse.zJson==0 ) return SQLITE_NOMEM; + memcpy(p->sParse.zJson, z, (size_t)n+1); + } + p->sParse.bJsonIsRCStr = 1; + p->zJson = p->sParse.zJson; + if( jsonParse(&p->sParse, 0) ){ int rc = SQLITE_NOMEM; if( p->sParse.oom==0 ){ sqlite3_free(cur->pVtab->zErrMsg); @@ -203202,6 +205185,11 @@ typedef unsigned int u32; #endif #endif /* !defined(SQLITE_AMALGAMATION) */ +/* Macro to check for 4-byte alignment. Only used inside of assert() */ +#ifdef SQLITE_DEBUG +# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0) +#endif + /* #include */ /* #include */ /* #include */ @@ -203608,7 +205596,7 @@ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ - assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); #if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 pCoord->u = _byteswap_ulong(*(u32*)p); #elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -203662,7 +205650,7 @@ static void writeInt16(u8 *p, int i){ } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; - assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); #if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -204390,7 +206378,7 @@ static void rtreeNonleafConstraint( assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); - assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(pCellData) ); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ case RTREE_FALSE: break; /* Never satisfied */ @@ -204443,7 +206431,7 @@ static void rtreeLeafConstraint( || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; - assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(pCellData) ); RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ case RTREE_TRUE: return; /* Always satisfied */ @@ -205013,7 +207001,20 @@ static int rtreeFilter( p->pInfo->nCoord = pRtree->nDim2; p->pInfo->anQueue = pCsr->anQueue; p->pInfo->mxLevel = pRtree->iDepth + 1; - }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + }else if( eType==SQLITE_INTEGER ){ + sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]); +#ifdef SQLITE_RTREE_INT_ONLY + p->u.rValue = iVal; +#else + p->u.rValue = (double)iVal; + if( iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) + ){ + if( p->op==RTREE_LT ) p->op = RTREE_LE; + if( p->op==RTREE_GT ) p->op = RTREE_GE; + } +#endif + }else if( eType==SQLITE_FLOAT ){ #ifdef SQLITE_RTREE_INT_ONLY p->u.rValue = sqlite3_value_int64(argv[ii]); #else @@ -205144,11 +207145,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){ u8 op; + u8 doOmit = 1; switch( p->op ){ - case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break; - case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break; + case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break; - case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break; + case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break; case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break; default: op = 0; break; @@ -205157,7 +207159,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ zIdxStr[iIdx++] = op; zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0'); pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); - pIdxInfo->aConstraintUsage[ii].omit = 1; + pIdxInfo->aConstraintUsage[ii].omit = doOmit; } } } @@ -218645,6 +220647,7 @@ static int sessionPreupdateEqual( rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); } assert( rc==SQLITE_OK ); + (void)rc; /* Suppress warning about unused variable */ if( sqlite3_value_type(pVal)!=eType ) return 0; /* A SessionChange object never has a NULL value in a PK column */ @@ -220989,15 +222992,19 @@ static int sessionReadRecord( } } if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - sqlite3_int64 v = sessionGetI64(aVal); - if( eType==SQLITE_INTEGER ){ - sqlite3VdbeMemSetInt64(apOut[i], v); + if( (pIn->nData-pIn->iNext)<8 ){ + rc = SQLITE_CORRUPT_BKPT; }else{ - double d; - memcpy(&d, &v, 8); - sqlite3VdbeMemSetDouble(apOut[i], d); + sqlite3_int64 v = sessionGetI64(aVal); + if( eType==SQLITE_INTEGER ){ + sqlite3VdbeMemSetInt64(apOut[i], v); + }else{ + double d; + memcpy(&d, &v, 8); + sqlite3VdbeMemSetDouble(apOut[i], d); + } + pIn->iNext += 8; } - pIn->iNext += 8; } } } @@ -224060,7 +226067,7 @@ struct Fts5PhraseIter { ** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); @@ -224289,8 +226296,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; @@ -224338,7 +226345,7 @@ struct fts5_api { int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -224347,7 +226354,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -224355,7 +226362,7 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); @@ -224527,6 +226534,10 @@ typedef struct Fts5Config Fts5Config; ** attempt to merge together. A value of 1 sets the object to use the ** compile time default. Zero disables auto-merge altogether. ** +** bContentlessDelete: +** True if the contentless_delete option was present in the CREATE +** VIRTUAL TABLE statement. +** ** zContent: ** ** zContentRowid: @@ -224561,6 +226572,7 @@ struct Fts5Config { int nPrefix; /* Number of prefix indexes */ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ + int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ @@ -224582,6 +226594,7 @@ struct Fts5Config { char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ int bSecureDelete; /* 'secure-delete' */ + int nDeleteMerge; /* 'deletemerge' */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; @@ -224904,6 +226917,9 @@ static int sqlite3Fts5IndexReset(Fts5Index *p); static int sqlite3Fts5IndexLoadConfig(Fts5Index *p); +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); + /* ** End of interface to code in fts5_index.c. **************************************************************************/ @@ -224988,6 +227004,11 @@ static int sqlite3Fts5HashWrite( */ static void sqlite3Fts5HashClear(Fts5Hash*); +/* +** Return true if the hash is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash*); + static int sqlite3Fts5HashQuery( Fts5Hash*, /* Hash table to query */ int nPre, @@ -225009,6 +227030,7 @@ static void sqlite3Fts5HashScanEntry(Fts5Hash *, ); + /* ** End of interface to code in fts5_hash.c. **************************************************************************/ @@ -225252,7 +227274,8 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); #define FTS5_STAR 15 /* This file is automatically generated by Lemon from input grammar -** source file "fts5parse.y". */ +** source file "fts5parse.y". +*/ /* ** 2000-05-29 ** @@ -227880,6 +229903,8 @@ static void sqlite3Fts5TermsetFree(Fts5Termset *p){ #define FTS5_DEFAULT_CRISISMERGE 16 #define FTS5_DEFAULT_HASHSIZE (1024*1024) +#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */ + /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (64*1024) @@ -228210,6 +230235,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessDelete = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); @@ -228454,6 +230489,28 @@ static int sqlite3Fts5ConfigParse( sqlite3_free(zTwo); } + /* We only allow contentless_delete=1 if the table is indeed contentless. */ + if( rc==SQLITE_OK + && pRet->bContentlessDelete + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 requires a contentless table" + ); + rc = SQLITE_ERROR; + } + + /* We only allow contentless_delete=1 if columnsize=0 is not present. + ** + ** This restriction may be removed at some point. + */ + if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 is incompatible with columnsize=0" + ); + rc = SQLITE_ERROR; + } + /* If a tokenizer= option was successfully parsed, the tokenizer has ** already been allocated. Otherwise, allocate an instance of the default ** tokenizer (unicode61) now. */ @@ -228748,6 +230805,18 @@ static int sqlite3Fts5ConfigSetValue( } } + else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){ + int nVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nVal = sqlite3_value_int(pVal); + }else{ + *pbBadkey = 1; + } + if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE; + if( nVal>100 ) nVal = 0; + pConfig->nDeleteMerge = nVal; + } + else if( 0==sqlite3_stricmp(zKey, "rank") ){ const char *zIn = (const char*)sqlite3_value_text(pVal); char *zRank; @@ -228796,6 +230865,7 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; + pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ @@ -231319,7 +233389,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( return pRet; } -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; @@ -231425,6 +233495,8 @@ static char *fts5ExprPrintTcl( if( zRet==0 ) return 0; } + }else if( pExpr->eType==0 ){ + zRet = sqlite3_mprintf("{}"); }else{ char const *zOp = 0; int i; @@ -231686,14 +233758,14 @@ static void fts5ExprFold( sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } -#endif /* ifdef SQLITE_TEST */ +#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); @@ -232453,7 +234525,6 @@ static int fts5HashEntrySort( pList = fts5HashEntryMerge(pList, ap[i]); } - pHash->nEntry = 0; sqlite3_free(ap); *ppSorted = pList; return SQLITE_OK; @@ -232507,6 +234578,28 @@ static int sqlite3Fts5HashScanInit( return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); } +#ifdef SQLITE_DEBUG +static int fts5HashCount(Fts5Hash *pHash){ + int nEntry = 0; + int ii; + for(ii=0; iinSlot; ii++){ + Fts5HashEntry *p = 0; + for(p=pHash->aSlot[ii]; p; p=p->pHashNext){ + nEntry++; + } + } + return nEntry; +} +#endif + +/* +** Return true if the hash table is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){ + assert( pHash->nEntry==fts5HashCount(pHash) ); + return pHash->nEntry==0; +} + static void sqlite3Fts5HashScanNext(Fts5Hash *p){ assert( !sqlite3Fts5HashScanEof(p) ); p->pScan = p->pScan->pScanNext; @@ -232595,6 +234688,24 @@ static void sqlite3Fts5HashScanEntry( #define FTS5_MAX_LEVEL 64 +/* +** There are two versions of the format used for the structure record: +** +** 1. the legacy format, that may be read by all fts5 versions, and +** +** 2. the V2 format, which is used by contentless_delete=1 databases. +** +** Both begin with a 4-byte "configuration cookie" value. Then, a legacy +** format structure record contains a varint - the number of levels in +** the structure. Whereas a V2 structure record contains the constant +** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a +** varint has to be at least 16256 to begin with "0xFF". And the default +** maximum number of levels is 64. +** +** See below for more on structure record formats. +*/ +#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01" + /* ** Details: ** @@ -232602,7 +234713,7 @@ static void sqlite3Fts5HashScanEntry( ** ** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); ** -** , contains the following 5 types of records. See the comments surrounding +** , contains the following 6 types of records. See the comments surrounding ** the FTS5_*_ROWID macros below for a description of how %_data rowids are ** assigned to each fo them. ** @@ -232611,12 +234722,12 @@ static void sqlite3Fts5HashScanEntry( ** The set of segments that make up an index - the index structure - are ** recorded in a single record within the %_data table. The record consists ** of a single 32-bit configuration cookie value followed by a list of -** SQLite varints. If the FTS table features more than one index (because -** there are one or more prefix indexes), it is guaranteed that all share -** the same cookie value. +** SQLite varints. ** -** Immediately following the configuration cookie, the record begins with -** three varints: +** If the structure record is a V2 record, the configuration cookie is +** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01]. +** +** Next, the record continues with three varints: ** ** + number of levels, ** + total number of segments on all levels, @@ -232631,6 +234742,12 @@ static void sqlite3Fts5HashScanEntry( ** + first leaf page number (often 1, always greater than 0) ** + final leaf page number ** +** Then, for V2 structures only: +** +** + lower origin counter value, +** + upper origin counter value, +** + the number of tombstone hash pages. +** ** 2. The Averages Record: ** ** A single record within the %_data table. The data is a list of varints. @@ -232746,6 +234863,38 @@ static void sqlite3Fts5HashScanEntry( ** * A list of delta-encoded varints - the first rowid on each subsequent ** child page. ** +** 6. Tombstone Hash Page +** +** These records are only ever present in contentless_delete=1 tables. +** There are zero or more of these associated with each segment. They +** are used to store the tombstone rowids for rows contained in the +** associated segments. +** +** The set of nHashPg tombstone hash pages associated with a single +** segment together form a single hash table containing tombstone rowids. +** To find the page of the hash on which a key might be stored: +** +** iPg = (rowid % nHashPg) +** +** Then, within page iPg, which has nSlot slots: +** +** iSlot = (rowid / nHashPg) % nSlot +** +** Each tombstone hash page begins with an 8 byte header: +** +** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8. +** 1-byte: rowid-0-tombstone flag. This flag is only valid on the +** first tombstone hash page for each segment (iPg=0). If set, +** the hash table contains rowid 0. If clear, it does not. +** Rowid 0 is handled specially. +** 2-bytes: unused. +** 4-bytes: Big-endian integer containing number of entries on page. +** +** Following this are nSlot 4 or 8 byte slots (depending on the key-size +** in the first byte of the page header). The number of slots may be +** determined based on the size of the page record and the key-size: +** +** nSlot = (nByte - 8) / key-size */ /* @@ -232779,6 +234928,7 @@ static void sqlite3Fts5HashScanEntry( #define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) #define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) +#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg) #ifdef SQLITE_DEBUG static int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } @@ -232814,6 +234964,12 @@ struct Fts5Data { /* ** One object per %_data table. +** +** nContentlessDelete: +** The number of contentless delete operations since the most recent +** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked +** so that extra auto-merge work can be done by fts5IndexFlush() to +** account for the delete operations. */ struct Fts5Index { Fts5Config *pConfig; /* Virtual table configuration */ @@ -232828,6 +234984,8 @@ struct Fts5Index { int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ + int nContentlessDelete; /* Number of contentless delete ops */ + int nPendingRow; /* Number of INSERT in hash table */ /* Error state. */ int rc; /* Current error code */ @@ -232862,11 +235020,23 @@ struct Fts5DoclistIter { ** The contents of the "structure" record for each index are represented ** using an Fts5Structure record in memory. Which uses instances of the ** other Fts5StructureXXX types as components. +** +** nOriginCntr: +** This value is set to non-zero for structure records created for +** contentlessdelete=1 tables only. In that case it represents the +** origin value to apply to the next top-level segment created. */ struct Fts5StructureSegment { int iSegid; /* Segment id */ int pgnoFirst; /* First leaf page number in segment */ int pgnoLast; /* Last leaf page number in segment */ + + /* contentlessdelete=1 tables only: */ + u64 iOrigin1; + u64 iOrigin2; + int nPgTombstone; /* Number of tombstone hash table pages */ + u64 nEntryTombstone; /* Number of tombstone entries that "count" */ + u64 nEntry; /* Number of rows in this segment */ }; struct Fts5StructureLevel { int nMerge; /* Number of segments in incr-merge */ @@ -232876,6 +235046,7 @@ struct Fts5StructureLevel { struct Fts5Structure { int nRef; /* Object reference count */ u64 nWriteCounter; /* Total leaves written to level 0 */ + u64 nOriginCntr; /* Origin value for next top-level segment */ int nSegment; /* Total segments in this structure */ int nLevel; /* Number of levels in this index */ Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ @@ -232964,6 +235135,13 @@ struct Fts5CResult { ** ** iTermIdx: ** Index of current term on iTermLeafPgno. +** +** apTombstone/nTombstone: +** These are used for contentless_delete=1 tables only. When the cursor +** is first allocated, the apTombstone[] array is allocated so that it +** is large enough for all tombstones hash pages associated with the +** segment. The pages themselves are loaded lazily from the database as +** they are required. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ @@ -232972,6 +235150,8 @@ struct Fts5SegIter { Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ + Fts5Data **apTombstone; /* Array of tombstone pages */ + int nTombstone; /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); @@ -233101,6 +235281,60 @@ static u16 fts5GetU16(const u8 *aIn){ return ((u16)aIn[0] << 8) + aIn[1]; } +/* +** The only argument points to a buffer at least 8 bytes in size. This +** function interprets the first 8 bytes of the buffer as a 64-bit big-endian +** unsigned integer and returns the result. +*/ +static u64 fts5GetU64(u8 *a){ + return ((u64)a[0] << 56) + + ((u64)a[1] << 48) + + ((u64)a[2] << 40) + + ((u64)a[3] << 32) + + ((u64)a[4] << 24) + + ((u64)a[5] << 16) + + ((u64)a[6] << 8) + + ((u64)a[7] << 0); +} + +/* +** The only argument points to a buffer at least 4 bytes in size. This +** function interprets the first 4 bytes of the buffer as a 32-bit big-endian +** unsigned integer and returns the result. +*/ +static u32 fts5GetU32(const u8 *a){ + return ((u32)a[0] << 24) + + ((u32)a[1] << 16) + + ((u32)a[2] << 8) + + ((u32)a[3] << 0); +} + +/* +** Write iVal, formated as a 64-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU64(u8 *a, u64 iVal){ + a[0] = ((iVal >> 56) & 0xFF); + a[1] = ((iVal >> 48) & 0xFF); + a[2] = ((iVal >> 40) & 0xFF); + a[3] = ((iVal >> 32) & 0xFF); + a[4] = ((iVal >> 24) & 0xFF); + a[5] = ((iVal >> 16) & 0xFF); + a[6] = ((iVal >> 8) & 0xFF); + a[7] = ((iVal >> 0) & 0xFF); +} + +/* +** Write iVal, formated as a 32-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU32(u8 *a, u32 iVal){ + a[0] = ((iVal >> 24) & 0xFF); + a[1] = ((iVal >> 16) & 0xFF); + a[2] = ((iVal >> 8) & 0xFF); + a[3] = ((iVal >> 0) & 0xFF); +} + /* ** Allocate and return a buffer at least nByte bytes in size. ** @@ -233328,10 +235562,17 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ /* ** Remove all records associated with segment iSegid. */ -static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){ +static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){ + int iSegid = pSeg->iSegid; i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0); i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1; fts5DataDelete(p, iFirst, iLast); + + if( pSeg->nPgTombstone ){ + i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0); + i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1); + fts5DataDelete(p, iTomb1, iTomb2); + } if( p->pIdxDeleter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf( @@ -233442,11 +235683,19 @@ static int fts5StructureDecode( int nSegment = 0; sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ Fts5Structure *pRet = 0; /* Structure object to return */ + int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */ + u64 nOriginCntr = 0; /* Largest origin value seen so far */ /* Grab the cookie value */ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); i = 4; + /* Check if this is a V2 structure record. Set bStructureV2 if it is. */ + if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){ + i += 4; + bStructureV2 = 1; + } + /* Read the total number of levels and segments from the start of the ** structure record. */ i += fts5GetVarint32(&pData[i], nLevel); @@ -233497,6 +235746,14 @@ static int fts5StructureDecode( i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); + if( bStructureV2 ){ + i += fts5GetVarint(&pData[i], &pSeg->iOrigin1); + i += fts5GetVarint(&pData[i], &pSeg->iOrigin2); + i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntry); + nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2); + } if( pSeg->pgnoLastpgnoFirst ){ rc = FTS5_CORRUPT; break; @@ -233507,6 +235764,9 @@ static int fts5StructureDecode( } } if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; + if( bStructureV2 ){ + pRet->nOriginCntr = nOriginCntr+1; + } if( rc!=SQLITE_OK ){ fts5StructureRelease(pRet); @@ -233719,6 +235979,7 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ Fts5Buffer buf; /* Buffer to serialize record into */ int iLvl; /* Used to iterate through levels */ int iCookie; /* Cookie value to store */ + int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9)); assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); memset(&buf, 0, sizeof(Fts5Buffer)); @@ -233727,9 +235988,12 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; - if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){ + if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){ sqlite3Fts5Put32(buf.p, iCookie); buf.n = 4; + if( pStruct->nOriginCntr>0 ){ + fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4); + } fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); @@ -233743,9 +236007,17 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ assert( pLvl->nMerge<=pLvl->nSeg ); for(iSeg=0; iSegnSeg; iSeg++){ - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast); + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast); + if( pStruct->nOriginCntr>0 ){ + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry); + } } } @@ -234268,6 +236540,23 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ } } +/* +** Allocate a tombstone hash page array (pIter->apTombstone) for the +** iterator passed as the second argument. If an OOM error occurs, leave +** an error in the Fts5Index object. +*/ +static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ + const int nTomb = pIter->pSeg->nPgTombstone; + if( nTomb>0 ){ + Fts5Data **apTomb = 0; + apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); + if( apTomb ){ + pIter->apTombstone = apTomb; + pIter->nTombstone = nTomb; + } + } +} + /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when @@ -234309,6 +236598,7 @@ static void fts5SegIterInit( pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; fts5SegIterLoadTerm(p, pIter, 0); fts5SegIterLoadNPos(p, pIter); + fts5SegIterAllocTombstone(p, pIter); } } @@ -235010,6 +237300,7 @@ static void fts5SegIterSeekInit( } fts5SegIterSetNext(p, pIter); + fts5SegIterAllocTombstone(p, pIter); /* Either: ** @@ -235090,6 +237381,20 @@ static void fts5SegIterHashInit( fts5SegIterSetNext(p, pIter); } +/* +** Array ap[] contains n elements. Release each of these elements using +** fts5DataRelease(). Then free the array itself using sqlite3_free(). +*/ +static void fts5IndexFreeArray(Fts5Data **ap, int n){ + if( ap ){ + int ii; + for(ii=0; iiterm); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); + fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); @@ -235434,6 +237740,84 @@ static void fts5MultiIterSetEof(Fts5Iter *pIter){ pIter->iSwitchRowid = pSeg->iRowid; } +/* +** The argument to this macro must be an Fts5Data structure containing a +** tombstone hash page. This macro returns the key-size of the hash-page. +*/ +#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8) + +#define TOMBSTONE_NSLOT(pPg) \ + ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1) + +/* +** Query a single tombstone hash table for rowid iRowid. Return true if +** it is found or false otherwise. The tombstone hash table is one of +** nHashTable tables. +*/ +static int fts5IndexTombstoneQuery( + Fts5Data *pHash, /* Hash table page to query */ + int nHashTable, /* Number of pages attached to segment */ + u64 iRowid /* Rowid to query hash for */ +){ + const int szKey = TOMBSTONE_KEYSIZE(pHash); + const int nSlot = TOMBSTONE_NSLOT(pHash); + int iSlot = (iRowid / nHashTable) % nSlot; + int nCollide = nSlot; + + if( iRowid==0 ){ + return pHash->p[1]; + }else if( szKey==4 ){ + u32 *aSlot = (u32*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + }else{ + u64 *aSlot = (u64*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + } + + return 0; +} + +/* +** Return true if the iterator passed as the only argument points +** to an segment entry for which there is a tombstone. Return false +** if there is no tombstone or if the iterator is already at EOF. +*/ +static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + + if( pSeg->pLeaf && pSeg->nTombstone ){ + /* Figure out which page the rowid might be present on. */ + int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone; + assert( iPg>=0 ); + + /* If tombstone hash page iPg has not yet been loaded from the + ** database, load it now. */ + if( pSeg->apTombstone[iPg]==0 ){ + pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) + ); + if( pSeg->apTombstone[iPg]==0 ) return 0; + } + + return fts5IndexTombstoneQuery( + pSeg->apTombstone[iPg], + pSeg->nTombstone, + pSeg->iRowid + ); + } + + return 0; +} + /* ** Move the iterator to the next entry. ** @@ -235471,7 +237855,9 @@ static void fts5MultiIterNext( fts5AssertMultiIterSetup(p, pIter); assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); - if( pIter->bSkipEmpty==0 || pSeg->nPos ){ + if( (pIter->bSkipEmpty==0 || pSeg->nPos) + && 0==fts5MultiIterIsDeleted(pIter) + ){ pIter->xSetOutputs(pIter, pSeg); return; } @@ -235503,7 +237889,9 @@ static void fts5MultiIterNext2( } fts5AssertMultiIterSetup(p, pIter); - }while( fts5MultiIterIsEmpty(p, pIter) ); + }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter)) + && (p->rc==SQLITE_OK) + ); } } @@ -236058,7 +238446,9 @@ static void fts5MultiIterNew( fts5MultiIterSetEof(pNew); fts5AssertMultiIterSetup(p, pNew); - if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ + if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew)) + || fts5MultiIterIsDeleted(pNew) + ){ fts5MultiIterNext(p, pNew, 0, 0); }else if( pNew->base.bEof==0 ){ Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; @@ -236236,7 +238626,9 @@ static void fts5IndexDiscardData(Fts5Index *p){ if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; + p->nPendingRow = 0; } + p->nContentlessDelete = 0; } /* @@ -236873,6 +239265,12 @@ static void fts5IndexMergeLevel( /* Read input from all segments in the input level */ nInput = pLvl->nSeg; + + /* Set the range of origins that will go into the output segment. */ + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1; + pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2; + } } bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); @@ -236932,8 +239330,11 @@ static void fts5IndexMergeLevel( int i; /* Remove the redundant segments from the %_data table */ + assert( pSeg->nEntry==0 ); for(i=0; iaSeg[i].iSegid); + Fts5StructureSegment *pOld = &pLvl->aSeg[i]; + pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone); + fts5DataRemoveSegment(p, pOld); } /* Remove the redundant segments from the input level */ @@ -236959,6 +239360,43 @@ static void fts5IndexMergeLevel( if( pnRem ) *pnRem -= writer.nLeafWritten; } +/* +** If this is not a contentless_delete=1 table, or if the 'deletemerge' +** configuration option is set to 0, then this function always returns -1. +** Otherwise, it searches the structure object passed as the second argument +** for a level suitable for merging due to having a large number of +** tombstones in the tombstone hash. If one is found, its index is returned. +** Otherwise, if there is no suitable level, -1. +*/ +static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ + Fts5Config *pConfig = p->pConfig; + int iRet = -1; + if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){ + int ii; + int nBest = 0; + + for(ii=0; iinLevel; ii++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[ii]; + i64 nEntry = 0; + i64 nTomb = 0; + int iSeg; + for(iSeg=0; iSegnSeg; iSeg++){ + nEntry += pLvl->aSeg[iSeg].nEntry; + nTomb += pLvl->aSeg[iSeg].nEntryTombstone; + } + assert_nc( nEntry>0 || pLvl->nSeg==0 ); + if( nEntry>0 ){ + int nPercent = (nTomb * 100) / nEntry; + if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){ + iRet = ii; + nBest = nPercent; + } + } + } + } + return iRet; +} + /* ** Do up to nPg pages of automerge work on the index. ** @@ -236978,14 +239416,15 @@ static int fts5IndexMerge( int iBestLvl = 0; /* Level offering the most input segments */ int nBest = 0; /* Number of input segments on best level */ - /* Set iBestLvl to the level to read input segments from. */ + /* Set iBestLvl to the level to read input segments from. Or to -1 if + ** there is no level suitable to merge segments from. */ assert( pStruct->nLevel>0 ); for(iLvl=0; iLvlnLevel; iLvl++){ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; if( pLvl->nMerge ){ if( pLvl->nMerge>nBest ){ iBestLvl = iLvl; - nBest = pLvl->nMerge; + nBest = nMin; } break; } @@ -236994,22 +239433,18 @@ static int fts5IndexMerge( iBestLvl = iLvl; } } - - /* If nBest is still 0, then the index must be empty. */ -#ifdef SQLITE_DEBUG - for(iLvl=0; nBest==0 && iLvlnLevel; iLvl++){ - assert( pStruct->aLevel[iLvl].nSeg==0 ); + if( nBestaLevel[iBestLvl].nMerge==0 ){ - break; - } + if( iBestLvl<0 ) break; bRet = 1; fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ fts5StructurePromote(p, iBestLvl+1, pStruct); } + + if( nMin==1 ) nMin = 2; } *ppStruct = pStruct; return bRet; @@ -237175,7 +239610,7 @@ static void fts5SecureDeleteOverflow( pLeaf = 0; }else if( bDetailNone ){ break; - }else if( iNext>=pLeaf->szLeaf || iNext<4 ){ + }else if( iNext>=pLeaf->szLeaf || pLeaf->nnszLeaf || iNext<4 ){ p->rc = FTS5_CORRUPT; break; }else{ @@ -237194,9 +239629,13 @@ static void fts5SecureDeleteOverflow( int i1 = pLeaf->szLeaf; int i2 = 0; + i1 += fts5GetVarint32(&aPg[i1], iFirst); + if( iFirstrc = FTS5_CORRUPT; + break; + } aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2); if( aIdx==0 ) break; - i1 += fts5GetVarint32(&aPg[i1], iFirst); i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift); if( i1nn ){ memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1); @@ -237379,7 +239818,9 @@ static void fts5DoSecureDelete( iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); - if( nPrefix2>nPrefix ){ + if( nPrefix2>pSeg->term.n ){ + p->rc = FTS5_CORRUPT; + }else if( nPrefix2>nPrefix ){ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); iOff += (nPrefix2-nPrefix); } @@ -237389,80 +239830,79 @@ static void fts5DoSecureDelete( } } }else if( iStart==4 ){ - int iPgno; + int iPgno; - assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); - /* The entry being removed may be the only position list in - ** its doclist. */ - for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ - Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); - int bEmpty = (pPg && pPg->nn==4); - fts5DataRelease(pPg); - if( bEmpty==0 ) break; - } + assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ + Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; + } - if( iPgno==pSeg->iTermLeafPgno ){ - i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); - Fts5Data *pTerm = fts5DataRead(p, iId); - if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ - u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; - int nTermIdx = pTerm->nn - pTerm->szLeaf; - int iTermIdx = 0; - int iTermOff = 0; + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); + Fts5Data *pTerm = fts5DataRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; + int iTermIdx = 0; + int iTermOff = 0; - while( 1 ){ - u32 iVal = 0; - int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); - iTermOff += iVal; - if( (iTermIdx+nByte)>=nTermIdx ) break; - iTermIdx += nByte; - } - nTermIdx = iTermIdx; - - memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); - fts5PutU16(&pTerm->p[2], iTermOff); - - fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); - if( nTermIdx==0 ){ - fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); - } + while( 1 ){ + u32 iVal = 0; + int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); + iTermOff += iVal; + if( (iTermIdx+nByte)>=nTermIdx ) break; + iTermIdx += nByte; } - fts5DataRelease(pTerm); + nTermIdx = iTermIdx; + + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); + + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); + } + } + fts5DataRelease(pTerm); + } + } + + if( p->rc==SQLITE_OK ){ + const int nMove = nPg - iNextOff; /* Number of bytes to move */ + int nShift = iNextOff - iOff; /* Distance to move them */ + + int iPrevKeyOut = 0; + int iKeyIn = 0; + + memmove(&aPg[iOff], &aPg[iNextOff], nMove); + iPgIdx -= nShift; + nPg = iPgIdx; + fts5PutU16(&aPg[2], iPgIdx); + + for(iIdx=0; iIdxiOff ? nShift : 0)); + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut); + iPrevKeyOut = iKeyOut; } } - if( p->rc==SQLITE_OK ){ - const int nMove = nPg - iNextOff; - int nShift = 0; - - memmove(&aPg[iOff], &aPg[iNextOff], nMove); - iPgIdx -= (iNextOff - iOff); - nPg = iPgIdx; - fts5PutU16(&aPg[2], iPgIdx); - - nShift = iNextOff - iOff; - for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdxiOff ){ - iKeyOff -= nShift; - nShift = 0; - } - nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff); - iPrevKeyOff = iKeyOff; - } - } - - if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ - fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); - } - - assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); - fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg); + if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); } - sqlite3_free(aIdx); + + assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg); + } + sqlite3_free(aIdx); } /* @@ -237516,187 +239956,197 @@ static void fts5FlushOneHash(Fts5Index *p){ /* Obtain a reference to the index structure and allocate a new segment-id ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); - iSegid = fts5AllocateSegid(p, pStruct); fts5StructureInvalidate(p); - if( iSegid ){ - const int pgsz = p->pConfig->pgsz; - int eDetail = p->pConfig->eDetail; - int bSecureDelete = p->pConfig->bSecureDelete; - Fts5StructureSegment *pSeg; /* New segment within pStruct */ - Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ - Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ + if( sqlite3Fts5HashIsEmpty(pHash)==0 ){ + iSegid = fts5AllocateSegid(p, pStruct); + if( iSegid ){ + const int pgsz = p->pConfig->pgsz; + int eDetail = p->pConfig->eDetail; + int bSecureDelete = p->pConfig->bSecureDelete; + Fts5StructureSegment *pSeg; /* New segment within pStruct */ + Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ + Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ - Fts5SegWriter writer; - fts5WriteInit(p, &writer, iSegid); + Fts5SegWriter writer; + fts5WriteInit(p, &writer, iSegid); - pBuf = &writer.writer.buf; - pPgidx = &writer.writer.pgidx; + pBuf = &writer.writer.buf; + pPgidx = &writer.writer.pgidx; - /* fts5WriteInit() should have initialized the buffers to (most likely) - ** the maximum space required. */ - assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); - assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + /* fts5WriteInit() should have initialized the buffers to (most likely) + ** the maximum space required. */ + assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); - /* Begin scanning through hash table entries. This loop runs once for each - ** term/doclist currently stored within the hash table. */ - if( p->rc==SQLITE_OK ){ - p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); - } - while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ - const char *zTerm; /* Buffer containing term */ - int nTerm; /* Size of zTerm in bytes */ - const u8 *pDoclist; /* Pointer to doclist for this term */ - int nDoclist; /* Size of doclist in bytes */ - - /* Get the term and doclist for this entry. */ - sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - nTerm = (int)strlen(zTerm); - if( bSecureDelete==0 ){ - fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); - if( p->rc!=SQLITE_OK ) break; - assert( writer.bFirstRowidInPage==0 ); + /* Begin scanning through hash table entries. This loop runs once for each + ** term/doclist currently stored within the hash table. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); } + while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ + const char *zTerm; /* Buffer containing term */ + int nTerm; /* Size of zTerm in bytes */ + const u8 *pDoclist; /* Pointer to doclist for this term */ + int nDoclist; /* Size of doclist in bytes */ - if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ - /* The entire doclist will fit on the current leaf. */ - fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); - }else{ - int bTermWritten = !bSecureDelete; - i64 iRowid = 0; - i64 iPrev = 0; - int iOff = 0; + /* Get the term and doclist for this entry. */ + sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); + nTerm = (int)strlen(zTerm); + if( bSecureDelete==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + if( p->rc!=SQLITE_OK ) break; + assert( writer.bFirstRowidInPage==0 ); + } - /* The entire doclist will not fit on this leaf. The following - ** loop iterates through the poslists that make up the current - ** doclist. */ - while( p->rc==SQLITE_OK && iOff=(pBuf->n + pPgidx->n + nDoclist + 1) ){ + /* The entire doclist will fit on the current leaf. */ + fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); + }else{ + int bTermWritten = !bSecureDelete; + i64 iRowid = 0; + i64 iPrev = 0; + int iOff = 0; - /* If in secure delete mode, and if this entry in the poslist is - ** in fact a delete, then edit the existing segments directly - ** using fts5FlushSecureDelete(). */ - if( bSecureDelete ){ - if( eDetail==FTS5_DETAIL_NONE ){ - if( iOffrc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ iOff++; - nDoclist = 0; - }else{ continue; } } - }else if( (pDoclist[iOff] & 0x01) ){ - fts5FlushSecureDelete(p, pStruct, zTerm, iRowid); - if( p->rc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ - iOff++; - continue; - } } - } - if( p->rc==SQLITE_OK && bTermWritten==0 ){ - fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); - bTermWritten = 1; - assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); - } + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + bTermWritten = 1; + assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); + } - if( writer.bFirstRowidInPage ){ - fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); - writer.bFirstRowidInPage = 0; - fts5WriteDlidxAppend(p, &writer, iRowid); - }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev); - } - if( p->rc!=SQLITE_OK ) break; - assert( pBuf->n<=pBuf->nSpace ); - iPrev = iRowid; + if( writer.bFirstRowidInPage ){ + fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); + writer.bFirstRowidInPage = 0; + fts5WriteDlidxAppend(p, &writer, iRowid); + }else{ + u64 iRowidDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta); + } + if( p->rc!=SQLITE_OK ) break; + assert( pBuf->n<=pBuf->nSpace ); + iPrev = iRowid; - if( eDetail==FTS5_DETAIL_NONE ){ - if( iOffp[pBuf->n++] = 0; - iOff++; + if( eDetail==FTS5_DETAIL_NONE ){ if( iOffp[pBuf->n++] = 0; iOff++; + if( iOffp[pBuf->n++] = 0; + iOff++; + } + } + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); } - } - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); - } - }else{ - int bDummy; - int nPos; - int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); - nCopy += nPos; - if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ - /* The entire poslist will fit on the current leaf. So copy - ** it in one go. */ - fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); }else{ - /* The entire poslist will not fit on this leaf. So it needs - ** to be broken into sections. The only qualification being - ** that each varint must be stored contiguously. */ - const u8 *pPoslist = &pDoclist[iOff]; - int iPos = 0; - while( p->rc==SQLITE_OK ){ - int nSpace = pgsz - pBuf->n - pPgidx->n; - int n = 0; - if( (nCopy - iPos)<=nSpace ){ - n = nCopy - iPos; - }else{ - n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + int bDummy; + int nPos; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); + nCopy += nPos; + if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ + /* The entire poslist will fit on the current leaf. So copy + ** it in one go. */ + fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); + }else{ + /* The entire poslist will not fit on this leaf. So it needs + ** to be broken into sections. The only qualification being + ** that each varint must be stored contiguously. */ + const u8 *pPoslist = &pDoclist[iOff]; + int iPos = 0; + while( p->rc==SQLITE_OK ){ + int nSpace = pgsz - pBuf->n - pPgidx->n; + int n = 0; + if( (nCopy - iPos)<=nSpace ){ + n = nCopy - iPos; + }else{ + n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + } + assert( n>0 ); + fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); + iPos += n; + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + if( iPos>=nCopy ) break; } - assert( n>0 ); - fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); - iPos += n; - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); - } - if( iPos>=nCopy ) break; } + iOff += nCopy; } - iOff += nCopy; } } - } - /* TODO2: Doclist terminator written here. */ - /* pBuf->p[pBuf->n++] = '\0'; */ - assert( pBuf->n<=pBuf->nSpace ); - if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); - } - sqlite3Fts5HashClear(pHash); - fts5WriteFinish(p, &writer, &pgnoLast); + /* TODO2: Doclist terminator written here. */ + /* pBuf->p[pBuf->n++] = '\0'; */ + assert( pBuf->n<=pBuf->nSpace ); + if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); + } + sqlite3Fts5HashClear(pHash); + fts5WriteFinish(p, &writer, &pgnoLast); - assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); - if( pgnoLast>0 ){ - /* Update the Fts5Structure. It is written back to the database by the - ** fts5StructureRelease() call below. */ - if( pStruct->nLevel==0 ){ - fts5StructureAddLevel(&p->rc, &pStruct); + assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); + if( pgnoLast>0 ){ + /* Update the Fts5Structure. It is written back to the database by the + ** fts5StructureRelease() call below. */ + if( pStruct->nLevel==0 ){ + fts5StructureAddLevel(&p->rc, &pStruct); + } + fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); + if( p->rc==SQLITE_OK ){ + pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; + pSeg->iSegid = iSegid; + pSeg->pgnoFirst = 1; + pSeg->pgnoLast = pgnoLast; + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pStruct->nOriginCntr; + pSeg->iOrigin2 = pStruct->nOriginCntr; + pSeg->nEntry = p->nPendingRow; + pStruct->nOriginCntr++; + } + pStruct->nSegment++; + } + fts5StructurePromote(p, 0, pStruct); } - fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); - if( p->rc==SQLITE_OK ){ - pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; - pSeg->iSegid = iSegid; - pSeg->pgnoFirst = 1; - pSeg->pgnoLast = pgnoLast; - pStruct->nSegment++; - } - fts5StructurePromote(p, 0, pStruct); } } - fts5IndexAutomerge(p, &pStruct, pgnoLast); + fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete); fts5IndexCrisismerge(p, &pStruct); fts5StructureWrite(p, pStruct); fts5StructureRelease(pStruct); + p->nContentlessDelete = 0; } /* @@ -237704,10 +240154,11 @@ static void fts5FlushOneHash(Fts5Index *p){ */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ - if( p->nPendingData ){ + if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); - p->nPendingData = 0; fts5FlushOneHash(p); + p->nPendingData = 0; + p->nPendingRow = 0; } } @@ -237723,17 +240174,22 @@ static Fts5Structure *fts5IndexOptimizeStruct( /* Figure out if this structure requires optimization. A structure does ** not require optimization if either: ** - ** + it consists of fewer than two segments, or - ** + all segments are on the same level, or - ** + all segments except one are currently inputs to a merge operation. + ** 1. it consists of fewer than two segments, or + ** 2. all segments are on the same level, or + ** 3. all segments except one are currently inputs to a merge operation. ** - ** In the first case, return NULL. In the second, increment the ref-count - ** on *pStruct and return a copy of the pointer to it. + ** In the first case, if there are no tombstone hash pages, return NULL. In + ** the second, increment the ref-count on *pStruct and return a copy of the + ** pointer to it. */ - if( nSeg<2 ) return 0; + if( nSeg==0 ) return 0; for(i=0; inLevel; i++){ int nThis = pStruct->aLevel[i].nSeg; - if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){ + int nMerge = pStruct->aLevel[i].nMerge; + if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){ + if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){ + return 0; + } fts5StructureRef(pStruct); return pStruct; } @@ -237749,6 +240205,7 @@ static Fts5Structure *fts5IndexOptimizeStruct( pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; + pNew->nOriginCntr = pStruct->nOriginCntr; pLvl = &pNew->aLevel[pNew->nLevel-1]; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pLvl->aSeg ){ @@ -237779,6 +240236,7 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); + assert( p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); fts5StructureInvalidate(p); @@ -237808,7 +240266,10 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ ** INSERT command. */ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ - Fts5Structure *pStruct = fts5StructureRead(p); + Fts5Structure *pStruct = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); if( pStruct ){ int nMin = p->pConfig->nUsermerge; fts5StructureInvalidate(p); @@ -237816,7 +240277,7 @@ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); fts5StructureRelease(pStruct); pStruct = pNew; - nMin = 2; + nMin = 1; nMerge = nMerge*-1; } if( pStruct && pStruct->nLevel ){ @@ -238330,6 +240791,9 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ p->iWriteRowid = iRowid; p->bDelete = bDelete; + if( bDelete==0 ){ + p->nPendingRow++; + } return fts5IndexReturn(p); } @@ -238367,6 +240831,9 @@ static int sqlite3Fts5IndexReinit(Fts5Index *p){ fts5StructureInvalidate(p); fts5IndexDiscardData(p); memset(&s, 0, sizeof(Fts5Structure)); + if( p->pConfig->bContentlessDelete ){ + s.nOriginCntr = 1; + } fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); fts5StructureWrite(p, &s); return fts5IndexReturn(p); @@ -238758,6 +241225,347 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ return fts5IndexReturn(p); } +/* +** Retrieve the origin value that will be used for the segment currently +** being accumulated in the in-memory hash table when it is flushed to +** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to +** the queried value. Or, if an error occurs, an error code is returned +** and the final value of (*piOrigin) is undefined. +*/ +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + *piOrigin = pStruct->nOriginCntr; + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +/* +** Buffer pPg contains a page of a tombstone hash table - one of nPg pages +** associated with the same segment. This function adds rowid iRowid to +** the hash table. The caller is required to guarantee that there is at +** least one free slot on the page. +** +** If parameter bForce is false and the hash table is deemed to be full +** (more than half of the slots are occupied), then non-zero is returned +** and iRowid not inserted. Or, if bForce is true or if the hash table page +** is not full, iRowid is inserted and zero returned. +*/ +static int fts5IndexTombstoneAddToPage( + Fts5Data *pPg, + int bForce, + int nPg, + u64 iRowid +){ + const int szKey = TOMBSTONE_KEYSIZE(pPg); + const int nSlot = TOMBSTONE_NSLOT(pPg); + const int nElem = fts5GetU32(&pPg->p[4]); + int iSlot = (iRowid / nPg) % nSlot; + int nCollide = nSlot; + + if( szKey==4 && iRowid>0xFFFFFFFF ) return 2; + if( iRowid==0 ){ + pPg->p[1] = 0x01; + return 0; + } + + if( bForce==0 && nElem>=(nSlot/2) ){ + return 1; + } + + fts5PutU32(&pPg->p[4], nElem+1); + if( szKey==4 ){ + u32 *aSlot = (u32*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid); + }else{ + u64 *aSlot = (u64*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU64((u8*)&aSlot[iSlot], iRowid); + } + + return 0; +} + +/* +** This function attempts to build a new hash containing all the keys +** currently in the tombstone hash table for segment pSeg. The new +** hash will be stored in the nOut buffers passed in array apOut[]. +** All pages of the new hash use key-size szKey (4 or 8). +** +** Return 0 if the hash is successfully rebuilt into the nOut pages. +** Or non-zero if it is not (because one page became overfull). In this +** case the caller should retry with a larger nOut parameter. +** +** Parameter pData1 is page iPg1 of the hash table being rebuilt. +*/ +static int fts5IndexTombstoneRehash( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int nOut, /* Number of output pages */ + Fts5Data **apOut /* Array of output hash pages */ +){ + int ii; + int res = 0; + + /* Initialize the headers of all the output pages */ + for(ii=0; iip[0] = szKey; + fts5PutU32(&apOut[ii]->p[4], 0); + } + + /* Loop through the current pages of the hash table. */ + for(ii=0; res==0 && iinPgTombstone; ii++){ + Fts5Data *pData = 0; /* Page ii of the current hash table */ + Fts5Data *pFree = 0; /* Free this at the end of the loop */ + + if( iPg1==ii ){ + pData = pData1; + }else{ + pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii)); + } + + if( pData ){ + int szKeyIn = TOMBSTONE_KEYSIZE(pData); + int nSlotIn = (pData->nn - 8) / szKeyIn; + int iIn; + for(iIn=0; iInp[8]; + if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]); + }else{ + u64 *aSlot = (u64*)&pData->p[8]; + if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]); + } + + /* If iVal is not 0 at this point, insert it into the new hash table */ + if( iVal ){ + Fts5Data *pPg = apOut[(iVal % nOut)]; + res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal); + if( res ) break; + } + } + + /* If this is page 0 of the old hash, copy the rowid-0-flag from the + ** old hash to the new. */ + if( ii==0 ){ + apOut[0]->p[1] = pData->p[1]; + } + } + fts5DataRelease(pFree); + } + + return res; +} + +/* +** This is called to rebuild the hash table belonging to segment pSeg. +** If parameter pData1 is not NULL, then one page of the existing hash table +** has already been loaded - pData1, which is page iPg1. The key-size for +** the new hash table is szKey (4 or 8). +** +** If successful, the new hash table is not written to disk. Instead, +** output parameter (*pnOut) is set to the number of pages in the new +** hash table, and (*papOut) to point to an array of buffers containing +** the new page data. +** +** If an error occurs, an error code is left in the Fts5Index object and +** both output parameters set to 0 before returning. +*/ +static void fts5IndexTombstoneRebuild( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int *pnOut, /* OUT: Number of output pages */ + Fts5Data ***papOut /* OUT: Output hash pages */ +){ + const int MINSLOT = 32; + int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); + int nSlot = 0; /* Number of slots in each output page */ + int nOut = 0; + + /* Figure out how many output pages (nOut) and how many slots per + ** page (nSlot). There are three possibilities: + ** + ** 1. The hash table does not yet exist. In this case the new hash + ** table will consist of a single page with MINSLOT slots. + ** + ** 2. The hash table exists but is currently a single page. In this + ** case an attempt is made to grow the page to accommodate the new + ** entry. The page is allowed to grow up to nSlotPerPage (see above) + ** slots. + ** + ** 3. The hash table already consists of more than one page, or of + ** a single page already so large that it cannot be grown. In this + ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage + ** slots each, where nPg is the current number of pages in the + ** hash table. + */ + if( pSeg->nPgTombstone==0 ){ + /* Case 1. */ + nOut = 1; + nSlot = MINSLOT; + }else if( pSeg->nPgTombstone==1 ){ + /* Case 2. */ + int nElem = (int)fts5GetU32(&pData1->p[4]); + assert( pData1 && iPg1==0 ); + nOut = 1; + nSlot = MAX(nElem*4, MINSLOT); + if( nSlot>nSlotPerPage ) nOut = 0; + } + if( nOut==0 ){ + /* Case 3. */ + nOut = (pSeg->nPgTombstone * 2 + 1); + nSlot = nSlotPerPage; + } + + /* Allocate the required array and output pages */ + while( 1 ){ + int res = 0; + int ii = 0; + int szPage = 0; + Fts5Data **apOut = 0; + + /* Allocate space for the new hash table */ + assert( nSlot>=MINSLOT ); + apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut); + szPage = 8 + nSlot*szKey; + for(ii=0; iirc, + sizeof(Fts5Data)+szPage + ); + if( pNew ){ + pNew->nn = szPage; + pNew->p = (u8*)&pNew[1]; + apOut[ii] = pNew; + } + } + + /* Rebuild the hash table. */ + if( p->rc==SQLITE_OK ){ + res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut); + } + if( res==0 ){ + if( p->rc ){ + fts5IndexFreeArray(apOut, nOut); + apOut = 0; + nOut = 0; + } + *pnOut = nOut; + *papOut = apOut; + break; + } + + /* If control flows to here, it was not possible to rebuild the hash + ** table. Free all buffers and then try again with more pages. */ + assert( p->rc==SQLITE_OK ); + fts5IndexFreeArray(apOut, nOut); + nSlot = nSlotPerPage; + nOut = nOut*2 + 1; + } +} + + +/* +** Add a tombstone for rowid iRowid to segment pSeg. +*/ +static void fts5IndexTombstoneAdd( + Fts5Index *p, + Fts5StructureSegment *pSeg, + u64 iRowid +){ + Fts5Data *pPg = 0; + int iPg = -1; + int szKey = 0; + int nHash = 0; + Fts5Data **apHash = 0; + + p->nContentlessDelete++; + + if( pSeg->nPgTombstone>0 ){ + iPg = iRowid % pSeg->nPgTombstone; + pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg)); + if( pPg==0 ){ + assert( p->rc!=SQLITE_OK ); + return; + } + + if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){ + fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn); + fts5DataRelease(pPg); + return; + } + } + + /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */ + szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4; + if( iRowid>0xFFFFFFFF ) szKey = 8; + + /* Rebuild the hash table */ + fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash); + assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) ); + + /* If all has succeeded, write the new rowid into one of the new hash + ** table pages, then write them all out to disk. */ + if( nHash ){ + int ii = 0; + fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid); + for(ii=0; iiiSegid, ii); + fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn); + } + pSeg->nPgTombstone = nHash; + fts5StructureWrite(p, p->pStruct); + } + + fts5DataRelease(pPg); + fts5IndexFreeArray(apHash, nHash); +} + +/* +** Add iRowid to the tombstone list of the segment or segments that contain +** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite +** error code otherwise. +*/ +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + int bFound = 0; /* True after pSeg->nEntryTombstone incr. */ + int iLvl; + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ + int iSeg; + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){ + if( bFound==0 ){ + pSeg->nEntryTombstone++; + bFound = 1; + } + fts5IndexTombstoneAdd(p, pSeg, iRowid); + } + } + } + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} /************************************************************************* ************************************************************************** @@ -239309,13 +242117,14 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum ** function only. */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). */ static void fts5DecodeRowid( i64 iRowid, /* Rowid from %_data table */ + int *pbTombstone, /* OUT: Tombstone hash flag */ int *piSegid, /* OUT: Segment id */ int *pbDlidx, /* OUT: Dlidx flag */ int *piHeight, /* OUT: Height */ @@ -239331,13 +242140,16 @@ static void fts5DecodeRowid( iRowid >>= FTS5_DATA_DLI_B; *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); -} -#endif /* SQLITE_TEST */ + iRowid >>= FTS5_DATA_ID_B; -#ifdef SQLITE_TEST + *pbTombstone = (int)(iRowid & 0x0001); +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ - int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */ - fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno); + int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */ + fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); if( iSegid==0 ){ if( iKey==FTS5_AVERAGES_ROWID ){ @@ -239347,14 +242159,16 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ } } else{ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}", - bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}", + bDlidx ? "dlidx " : "", + bTomb ? "tombstone " : "", + iSegid, iHeight, iPgno ); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, @@ -239369,16 +242183,22 @@ static void fts5DebugStructure( ); for(iSeg=0; iSegnSeg; iSeg++){ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}", + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d", pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast ); + if( pSeg->iOrigin1>0 ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld", + pSeg->iOrigin1, pSeg->iOrigin2 + ); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** @@ -239403,9 +242223,9 @@ static void fts5DecodeStructure( fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** @@ -239428,9 +242248,9 @@ static void fts5DecodeAverages( zSpace = " "; } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return @@ -239447,9 +242267,9 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } return iOff; } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text @@ -239482,9 +242302,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ return iOff; } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. @@ -239525,9 +242345,9 @@ static void fts5DecodeRowidList( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } -#endif /* SQLITE_TEST */ +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). */ @@ -239538,6 +242358,7 @@ static void fts5DecodeFunction( ){ i64 iRowid; /* Rowid for record being decoded */ int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */ + int bTomb; const u8 *aBlob; int n; /* Record to decode */ u8 *a = 0; Fts5Buffer s; /* Build up text to return here */ @@ -239560,7 +242381,7 @@ static void fts5DecodeFunction( if( a==0 ) goto decode_out; if( n>0 ) memcpy(a, aBlob, n); - fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno); + fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); fts5DebugRowid(&rc, &s, iRowid); if( bDlidx ){ @@ -239579,6 +242400,28 @@ static void fts5DecodeFunction( " %d(%lld)", lvl.iLeafPgno, lvl.iRowid ); } + }else if( bTomb ){ + u32 nElem = fts5GetU32(&a[4]); + int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8; + int nSlot = (n - 8) / szKey; + int ii; + sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem); + if( aBlob[1] ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0"); + } + for(ii=0; iiszLeaf ){ + rc = FTS5_CORRUPT; + }else{ + fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff); + } iOff = iTermOff; if( iOffestimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + break; + } + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){ + Fts5StructVtab *p = (Fts5StructVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + int rc = SQLITE_OK; + Fts5StructVcsr *pNew = 0; + + pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + *ppCsr = (sqlite3_vtab_cursor*)pNew; + + return SQLITE_OK; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + fts5StructureRelease(pCsr->pStruct); + sqlite3_free(pCsr); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int fts5structNextMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + + assert( pCsr->pStruct ); + pCsr->iSeg++; + pCsr->iRowid++; + while( pCsr->iLevelnLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){ + pCsr->iLevel++; + pCsr->iSeg = 0; + } + if( pCsr->iLevel>=p->nLevel ){ + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fts5structEofMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + return pCsr->pStruct==0; +} + +static int fts5structRowidMethod( + sqlite3_vtab_cursor *cur, + sqlite_int64 *piRowid +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + *piRowid = pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int fts5structColumnMethod( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg]; + + switch( i ){ + case 0: /* level */ + sqlite3_result_int(ctx, pCsr->iLevel); + break; + case 1: /* segment */ + sqlite3_result_int(ctx, pCsr->iSeg); + break; + case 2: /* merge */ + sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge); + break; + case 3: /* segid */ + sqlite3_result_int(ctx, pSeg->iSegid); + break; + case 4: /* leaf1 */ + sqlite3_result_int(ctx, pSeg->pgnoFirst); + break; + case 5: /* leaf2 */ + sqlite3_result_int(ctx, pSeg->pgnoLast); + break; + case 6: /* origin1 */ + sqlite3_result_int64(ctx, pSeg->iOrigin1); + break; + case 7: /* origin2 */ + sqlite3_result_int64(ctx, pSeg->iOrigin2); + break; + case 8: /* npgtombstone */ + sqlite3_result_int(ctx, pSeg->nPgTombstone); + break; + case 9: /* nentrytombstone */ + sqlite3_result_int64(ctx, pSeg->nEntryTombstone); + break; + case 10: /* nentry */ + sqlite3_result_int64(ctx, pSeg->nEntry); + break; + } + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int fts5structFilterMethod( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor; + int rc = SQLITE_OK; + + const u8 *aBlob = 0; + int nBlob = 0; + + assert( argc==1 ); + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + + nBlob = sqlite3_value_bytes(argv[0]); + aBlob = (const u8*)sqlite3_value_blob(argv[0]); + rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct); + if( rc==SQLITE_OK ){ + pCsr->iLevel = 0; + pCsr->iRowid = 0; + pCsr->iSeg = -1; + rc = fts5structNextMethod(pVtabCursor); + } + + return rc; +} + +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called as part of registering the FTS5 module with database @@ -239783,7 +242857,7 @@ static void fts5RowidFunction( ** SQLite error code is returned instead. */ static int sqlite3Fts5IndexInit(sqlite3 *db){ -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); @@ -239800,6 +242874,36 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){ db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 ); } + + if( rc==SQLITE_OK ){ + static const sqlite3_module fts5structure_module = { + 0, /* iVersion */ + 0, /* xCreate */ + fts5structConnectMethod, /* xConnect */ + fts5structBestIndexMethod, /* xBestIndex */ + fts5structDisconnectMethod, /* xDisconnect */ + 0, /* xDestroy */ + fts5structOpenMethod, /* xOpen */ + fts5structCloseMethod, /* xClose */ + fts5structFilterMethod, /* xFilter */ + fts5structNextMethod, /* xNext */ + fts5structEofMethod, /* xEof */ + fts5structColumnMethod, /* xColumn */ + fts5structRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ + }; + rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); + } return rc; #else return SQLITE_OK; @@ -241443,7 +244547,6 @@ static int fts5UpdateMethod( int rc = SQLITE_OK; /* Return code */ int bUpdateOrDelete = 0; - /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); @@ -241472,7 +244575,14 @@ static int fts5UpdateMethod( if( pConfig->eContent!=FTS5_CONTENT_NORMAL && 0==sqlite3_stricmp("delete", z) ){ - rc = fts5SpecialDelete(pTab, apVal); + if( pConfig->bContentlessDelete ){ + fts5SetVtabError(pTab, + "'delete' may not be used with a contentless_delete=1 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = fts5SpecialDelete(pTab, apVal); + } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } @@ -241489,7 +244599,7 @@ static int fts5UpdateMethod( ** Cases 3 and 4 may violate the rowid constraint. */ int eConflict = SQLITE_ABORT; - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){ eConflict = sqlite3_vtab_on_conflict(pConfig->db); } @@ -241497,8 +244607,12 @@ static int fts5UpdateMethod( assert( nArg!=1 || eType0==SQLITE_INTEGER ); /* Filter out attempts to run UPDATE or DELETE on contentless tables. - ** This is not suported. */ - if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){ + ** This is not suported. Except - DELETE is supported if the CREATE + ** VIRTUAL TABLE statement contained "contentless_delete=1". */ + if( eType0==SQLITE_INTEGER + && pConfig->eContent==FTS5_CONTENT_NONE + && pConfig->bContentlessDelete==0 + ){ pTab->p.base.zErrMsg = sqlite3_mprintf( "cannot %s contentless fts5 table: %s", (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName @@ -241585,8 +244699,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; - fts5TripCursors(pTab); - rc = sqlite3Fts5StorageSync(pTab->pStorage); + rc = sqlite3Fts5FlushToDisk(&pTab->p); pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -242353,6 +245466,12 @@ static int fts5ColumnMethod( sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); } pConfig->pzErrmsg = 0; + }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){ + char *zErr = sqlite3_mprintf("cannot UPDATE a subset of " + "columns on fts5 contentless-delete table: %s", pConfig->zName + ); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); } return rc; } @@ -242635,7 +245754,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2023-09-11 12:01:27 2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0", -1, SQLITE_TRANSIENT); } /* @@ -242848,10 +245967,10 @@ static int fts5StorageGetStmt( "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ - "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */ + "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ - "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ + "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ "SELECT %s FROM %s AS T", /* SCAN */ @@ -242899,6 +246018,19 @@ static int fts5StorageGetStmt( break; } + case FTS5_STMT_REPLACE_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, + (pC->bContentlessDelete ? ",?" : "") + ); + break; + + case FTS5_STMT_LOOKUP_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], + (pC->bContentlessDelete ? ",origin" : ""), + pC->zDb, pC->zName + ); + break; + default: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); break; @@ -243088,9 +246220,11 @@ static int sqlite3Fts5StorageOpen( } if( rc==SQLITE_OK && pConfig->bColumnsize ){ - rc = sqlite3Fts5CreateTable( - pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr - ); + const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; + if( pConfig->bContentlessDelete ){ + zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; + } + rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5CreateTable( @@ -243167,7 +246301,7 @@ static int fts5StorageDeleteFromIndex( ){ Fts5Config *pConfig = p->pConfig; sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int rc2; /* sqlite3_reset() return code */ int iCol; Fts5InsertCtx ctx; @@ -243183,7 +246317,6 @@ static int fts5StorageDeleteFromIndex( ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ const char *zText; @@ -243220,6 +246353,37 @@ static int fts5StorageDeleteFromIndex( return rc; } +/* +** This function is called to process a DELETE on a contentless_delete=1 +** table. It adds the tombstone required to delete the entry with rowid +** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs, +** an SQLite error code. +*/ +static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ + i64 iOrigin = 0; + sqlite3_stmt *pLookup = 0; + int rc = SQLITE_OK; + + assert( p->pConfig->bContentlessDelete ); + assert( p->pConfig->eContent==FTS5_CONTENT_NONE ); + + /* Look up the origin of the document in the %_docsize table. Store + ** this in stack variable iOrigin. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pLookup, 1, iDel); + if( SQLITE_ROW==sqlite3_step(pLookup) ){ + iOrigin = sqlite3_column_int64(pLookup, 1); + } + rc = sqlite3_reset(pLookup); + } + + if( rc==SQLITE_OK && iOrigin!=0 ){ + rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); + } + + return rc; +} /* ** Insert a record into the %_docsize table. Specifically, do: @@ -243240,10 +246404,17 @@ static int fts5StorageInsertDocsize( rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pReplace, 1, iRowid); - sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); - sqlite3_step(pReplace); - rc = sqlite3_reset(pReplace); - sqlite3_bind_null(pReplace, 2); + if( p->pConfig->bContentlessDelete ){ + i64 iOrigin = 0; + rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); + sqlite3_bind_int64(pReplace, 3, iOrigin); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); + sqlite3_step(pReplace); + rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); + } } } return rc; @@ -243307,7 +246478,15 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap /* Delete the index records */ if( rc==SQLITE_OK ){ - rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); + } + + if( rc==SQLITE_OK ){ + if( p->pConfig->bContentlessDelete ){ + rc = fts5StorageContentlessDelete(p, iDel); + }else{ + rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + } } /* Delete the %_docsize record */ diff --git a/src/libs/3rdparty/sqlite/sqlite3.h b/src/libs/3rdparty/sqlite/sqlite3.h index 48effe20216..b9d06929888 100644 --- a/src/libs/3rdparty/sqlite/sqlite3.h +++ b/src/libs/3rdparty/sqlite/sqlite3.h @@ -146,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.42.0" -#define SQLITE_VERSION_NUMBER 3042000 -#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0" +#define SQLITE_VERSION "3.43.1" +#define SQLITE_VERSION_NUMBER 3043001 +#define SQLITE_SOURCE_ID "2023-09-11 12:01:27 2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -528,6 +528,7 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) @@ -1190,7 +1191,7 @@ struct sqlite3_io_methods { ** by clients within the current process, only within other processes. ** **
    4. [[SQLITE_FCNTL_CKSM_FILE]] -** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the ** [checksum VFS shim] only. ** **
    5. [[SQLITE_FCNTL_RESET_CACHE]] @@ -2454,7 +2455,7 @@ struct sqlite3_mem_methods { ** the [VACUUM] command will fail with an obscure error when attempting to ** process a table with generated columns and a descending index. This is ** not considered a bug since SQLite versions 3.3.0 and earlier do not support -** either generated columns or decending indexes. +** either generated columns or descending indexes. ** ** ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] @@ -2735,6 +2736,7 @@ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether ** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. */ SQLITE_API void sqlite3_interrupt(sqlite3*); SQLITE_API int sqlite3_is_interrupted(sqlite3*); @@ -3388,8 +3390,10 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** M argument should be the bitwise OR-ed combination of ** zero or more [SQLITE_TRACE] constants. ** -** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides -** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. ** ** ^The X callback is invoked whenever any of the events identified by ** mask M occur. ^The integer return value from the callback is currently @@ -3758,7 +3762,7 @@ SQLITE_API int sqlite3_open_v2( ** as F) must be one of: **
        **
      • A database filename pointer created by the SQLite core and -** passed into the xOpen() method of a VFS implemention, or +** passed into the xOpen() method of a VFS implementation, or **
      • A filename obtained from [sqlite3_db_filename()], or **
      • A new filename constructed using [sqlite3_create_filename()]. **
      @@ -3871,7 +3875,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); /* ** CAPI3REF: Create and Destroy VFS Filenames ** -** These interfces are provided for use by [VFS shim] implementations and +** These interfaces are provided for use by [VFS shim] implementations and ** are not useful outside of that context. ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of @@ -4418,6 +4422,41 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); */ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt @@ -4581,7 +4620,7 @@ typedef struct sqlite3_context sqlite3_context; ** with it may be passed. ^It is called to dispose of the BLOB or string even ** if the call to the bind API fails, except the destructor is not called if ** the third parameter is a NULL pointer or the fourth parameter is negative. -** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that ** the application remains responsible for disposing of the object. ^In this ** case, the object and the provided pointer to it must remain valid until ** either the prepared statement is finalized or the same SQL parameter is @@ -5260,14 +5299,26 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** -** ^If the most recent call to [sqlite3_step(S)] for the -** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], -** or if [sqlite3_step(S)] has never before been called on S, -** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. @@ -5484,7 +5535,7 @@ SQLITE_API int sqlite3_create_window_function( ** [application-defined SQL function] ** that has side-effects or that could potentially leak sensitive information. ** This will prevent attacks in which an application is tricked -** into using a database file that has had its schema surreptiously +** into using a database file that has had its schema surreptitiously ** modified to invoke the application-defined function in ways that are ** harmful. **

      @@ -8161,7 +8212,8 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_TRACEFLAGS 31 #define SQLITE_TESTCTRL_TUNE 32 #define SQLITE_TESTCTRL_LOGEST 33 -#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking @@ -9193,8 +9245,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections -** unlock-notify callback may also be canceled by closing the blocked +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes @@ -9617,7 +9669,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** [[SQLITE_VTAB_DIRECTONLY]]

      SQLITE_VTAB_DIRECTONLY
      **
      Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** prohibits that virtual table from being used from within triggers and ** views. **
      @@ -9807,7 +9859,7 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); ** communicated to the xBestIndex method as a ** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use ** this constraint, it must set the corresponding -** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under ** the usual mode of handling IN operators, SQLite generates [bytecode] ** that invokes the [xFilter|xFilter() method] once for each value ** on the right-hand side of the IN operator.)^ Thus the virtual table @@ -10236,7 +10288,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** When the [sqlite3_blob_write()] API is used to update a blob column, ** the pre-update hook is invoked with SQLITE_DELETE. This is because the ** in this case the new values are not available. In this case, when a -** callback made with op==SQLITE_DELETE is actuall a write using the +** callback made with op==SQLITE_DELETE is actually a write using the ** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns ** the index of the column being written. In other cases, where the ** pre-update hook is being invoked for some other reason, including a @@ -12754,7 +12806,7 @@ struct Fts5PhraseIter { ** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); @@ -12983,8 +13035,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; @@ -13032,7 +13084,7 @@ struct fts5_api { int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -13041,7 +13093,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -13049,7 +13101,7 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); diff --git a/src/libs/3rdparty/sqlite/sqlite3ext.h b/src/libs/3rdparty/sqlite/sqlite3ext.h index 19e030028ad..71163809926 100644 --- a/src/libs/3rdparty/sqlite/sqlite3ext.h +++ b/src/libs/3rdparty/sqlite/sqlite3ext.h @@ -361,6 +361,8 @@ struct sqlite3_api_routines { int (*value_encoding)(sqlite3_value*); /* Version 3.41.0 and later */ int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); }; /* @@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_value_encoding sqlite3_api->value_encoding /* Version 3.41.0 and later */ #define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index e9faed95abb..7ceff0e78de 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -1377,7 +1377,11 @@ bool Check::visit(BinaryExpression *ast) SourceLocation expressionSourceLocation = locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()); - if (expressionAffectsVisualAspects(ast)) + + const bool isDirectInConnectionsScope = (!m_typeStack.isEmpty() + && m_typeStack.last() == "Connections"); + + if (expressionAffectsVisualAspects(ast) && !isDirectInConnectionsScope) addMessage(WarnImperativeCodeNotEditableInVisualDesigner, expressionSourceLocation); // check ==, != diff --git a/src/libs/qmljs/qmljssimplereader.cpp b/src/libs/qmljs/qmljssimplereader.cpp index 761131b3a28..d4b8a21bf7c 100644 --- a/src/libs/qmljs/qmljssimplereader.cpp +++ b/src/libs/qmljs/qmljssimplereader.cpp @@ -89,7 +89,10 @@ static Q_LOGGING_CATEGORY(simpleReaderLog, "qtc.qmljs.simpleReader", QtWarningMs return newNode; } - const SimpleReaderNode::List SimpleReaderNode::children() const { return m_children; } + const SimpleReaderNode::List &SimpleReaderNode::children() const + { + return m_children; + } void SimpleReaderNode::setProperty(const QString &name, const SourceLocation &nameLocation, diff --git a/src/libs/qmljs/qmljssimplereader.h b/src/libs/qmljs/qmljssimplereader.h index de57bb62dad..e14f49d65dc 100644 --- a/src/libs/qmljs/qmljssimplereader.h +++ b/src/libs/qmljs/qmljssimplereader.h @@ -34,6 +34,7 @@ public: SourceLocation valueLocation; bool isValid() const { return !value.isNull() && value.isValid(); } + explicit operator bool() const { return isValid(); } bool isDefaultValue() const { return !value.isNull() && !nameLocation.isValid() && !valueLocation.isValid(); @@ -53,7 +54,7 @@ public: WeakPtr parent() const; QString name() const; SourceLocation nameLocation() const; - const List children() const; + const List &children() const; protected: SimpleReaderNode(); diff --git a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h index 6e25b3b419c..58166516c80 100644 --- a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h +++ b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h @@ -44,7 +44,7 @@ enum class View3DActionType { ParticlesPlay, ParticlesRestart, ParticlesSeek, - SyncBackgroundColor, + SyncEnvBackground, GetNodeAtPos, SetBakeLightsView3D }; diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 881f78b115f..6a890815d07 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -10,6 +10,7 @@ add_qtc_library(Utils ansiescapecodehandler.cpp ansiescapecodehandler.h appinfo.cpp appinfo.h appmainwindow.cpp appmainwindow.h + array.h aspects.cpp aspects.h async.cpp async.h basetreeview.cpp basetreeview.h diff --git a/src/libs/utils/algorithm.h b/src/libs/utils/algorithm.h index 91eb2f54f1e..61c8f12817b 100644 --- a/src/libs/utils/algorithm.h +++ b/src/libs/utils/algorithm.h @@ -597,13 +597,91 @@ public: MapInsertIterator operator++(int) { return *this; } }; -// inserter helper function, returns a std::back_inserter for most containers -// and is overloaded for QSet<> and other containers without push_back, returning custom inserters -template -inline std::back_insert_iterator -inserter(C &container) +// because Qt container are not implementing the standard interface we need +// this helper functions for generic code +template +void append(QList *container, QList &&input) { - return std::back_inserter(container); + container->append(std::move(input)); +} + +template +void append(QList *container, const QList &input) +{ + container->append(input); +} + +template +void append(Container *container, Container &&input) +{ + container->insert(container->end(), + std::make_move_iterator(input.begin()), + std::make_move_iterator(input.end())); +} + +template +void append(Container *container, const Container &input) +{ + container->insert(container->end(), input.begin(), input.end()); +} + +// BackInsertIterator behaves like std::back_insert_iterator except is adds the back insertion for +// container of the same type +template +class BackInsertIterator +{ +public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = ptrdiff_t; + using pointer = void; + using reference = void; + using container_type = Container; + + explicit constexpr BackInsertIterator(Container &container) + : m_container(std::addressof(container)) + {} + + constexpr BackInsertIterator &operator=(const typename Container::value_type &value) + { + m_container->push_back(value); + return *this; + } + + constexpr BackInsertIterator &operator=(typename Container::value_type &&value) + { + m_container->push_back(std::move(value)); + return *this; + } + + constexpr BackInsertIterator &operator=(const Container &container) + { + append(m_container, container); + return *this; + } + + constexpr BackInsertIterator &operator=(Container &&container) + { + append(m_container, container); + return *this; + } + + [[nodiscard]] constexpr BackInsertIterator &operator*() { return *this; } + + constexpr BackInsertIterator &operator++() { return *this; } + + constexpr BackInsertIterator operator++(int) { return *this; } + +private: + Container *m_container; +}; + +// inserter helper function, returns a BackInsertIterator for most containers +// and is overloaded for QSet<> and other containers without push_back, returning custom inserters +template +inline BackInsertIterator inserter(Container &container) +{ + return BackInsertIterator(container); } template diff --git a/src/libs/utils/array.h b/src/libs/utils/array.h new file mode 100644 index 00000000000..0d9a6047281 --- /dev/null +++ b/src/libs/utils/array.h @@ -0,0 +1,23 @@ +// 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 + +namespace Utils { + +namespace Internal { +template +constexpr std::array, size> to_array_implementation( + Type (&&array)[size], std::index_sequence) +{ + return {{std::move(array[index])...}}; +} +} // namespace Internal + +template +constexpr std::array, size> to_array(Type (&&array)[size]) +{ + return Internal::to_array_implementation(std::move(array), std::make_index_sequence{}); +} + +} // namespace Utils diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index 16e872d5afa..54318795a71 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -33,11 +33,6 @@ #define unittest_public private #endif -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif - namespace Utils { template @@ -425,10 +420,9 @@ public: size_type newSize = oldSize + string.size(); reserve(optimalCapacity(newSize)); - QT_WARNING_PUSH - QT_WARNING_DISABLE_CLANG("-Wunsafe-buffer-usage") - std::char_traits::copy(data() + oldSize, string.data(), string.size()); - QT_WARNING_POP + std::char_traits::copy(std::next(data(), static_cast(oldSize)), + string.data(), + string.size()); setSize(newSize); } @@ -566,30 +560,20 @@ public: static BasicSmallString number(int number) { -#ifdef __cpp_lib_to_chars - // +1 for the sign, +1 for the extra digit + // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10); - Q_ASSERT(result.ec == std::errc{}); auto endOfConversionString = result.ptr; return BasicSmallString(buffer, endOfConversionString); -#else - return std::to_string(number); -#endif } static BasicSmallString number(long long int number) noexcept { -#ifdef __cpp_lib_to_chars - // +1 for the sign, +1 for the extra digit + // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10); - Q_ASSERT(result.ec == std::errc{}); auto endOfConversionString = result.ptr; return BasicSmallString(buffer, endOfConversionString); -#else - return std::to_string(number); -#endif } static BasicSmallString number(double number) noexcept { return std::to_string(number); } @@ -927,7 +911,3 @@ SmallString operator+(const char(&first)[Size], SmallStringView second) } } // namespace Utils - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 2b898cb1049..ffc03c1a29c 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -37,6 +37,7 @@ constexpr char C_PANEL_WIDGET[] = "panelwidget"; constexpr char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow"; constexpr char C_SHOW_BORDER[] = "showborder"; constexpr char C_TOP_BORDER[] = "topBorder"; +constexpr char C_TOOLBAR_ACTIONWIDGET[] = "toolbar_actionWidget"; enum ToolbarStyle { ToolbarStyleCompact, diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index c3aa8842818..0dbdb61fbe4 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -345,6 +345,17 @@ public: DSscrollBarTrack, DSscrollBarHandle, DSscrollBarHandle_idle, + DSconnectionCodeEditor, + DSpillText, + DSpillTextSelected, + DspillTextEdit, + DSpillDefaultBackgroundIdle, + DSpillDefaultBackgroundHover, + DSpillOperatorBackgroundIdle, + DSpillOperatorBackgroundHover, + DSpillLiteralBackgroundIdle, + DSpillLiteralBackgroundHover, + /*Legacy QtDS*/ DSiconColor, diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index d10f434eb10..5d7316dee61 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -112,4 +112,8 @@ add_subdirectory(mcusupport) add_subdirectory(saferenderer) add_subdirectory(copilot) add_subdirectory(terminal) + +if (WITH_QMLDESIGNER) + add_subdirectory(effectmakernew) +endif() add_subdirectory(compilerexplorer) diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp index 1c2a1a0dc8d..4a73eb6e9eb 100644 --- a/src/plugins/coreplugin/icore.cpp +++ b/src/plugins/coreplugin/icore.cpp @@ -543,6 +543,12 @@ bool ICore::showWarningWithOptions(const QString &title, const QString &text, return false; } + bool ICore::isQtDesignStudio() +{ + QtcSettings *settings = Core::ICore::settings(); + return settings->value("QML/Designer/StandAloneMode", false).toBool(); +} + /*! Returns the application's main settings object. diff --git a/src/plugins/coreplugin/icore.h b/src/plugins/coreplugin/icore.h index eb27d3a47a5..cff938f5393 100644 --- a/src/plugins/coreplugin/icore.h +++ b/src/plugins/coreplugin/icore.h @@ -63,6 +63,7 @@ public: Utils::Id settingsId = {}, QWidget *parent = nullptr); + static bool isQtDesignStudio(); static Utils::QtcSettings *settings(QSettings::Scope scope = QSettings::UserScope); static QPrinter *printer(); static QString userInterfaceLanguage(); diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp index aeac11dc575..8a2e0b43584 100644 --- a/src/plugins/coreplugin/manhattanstyle.cpp +++ b/src/plugins/coreplugin/manhattanstyle.cpp @@ -732,8 +732,17 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element, painter->drawLine(borderRect.topRight(), borderRect.bottomRight()); } } else if (option->state & State_Enabled && option->state & State_MouseOver) { - StyleHelper::drawPanelBgRect( - painter, rect, creatorTheme()->color(Theme::FancyToolButtonHoverColor)); + if (widget->property(StyleHelper::C_TOOLBAR_ACTIONWIDGET).toBool()) { + painter->save(); + painter->setBrush(creatorTheme()->color(Theme::FancyToolButtonHoverColor)); + painter->drawRoundedRect(rect, 5, 5); + painter->restore(); + } else { + StyleHelper::drawPanelBgRect(painter, + rect, + creatorTheme()->color( + Theme::FancyToolButtonHoverColor)); + } } if (option->state & State_HasFocus && (option->state & State_KeyboardFocusChange)) { QColor highlight = option->palette.highlight().color(); diff --git a/src/plugins/effectmakernew/CMakeLists.txt b/src/plugins/effectmakernew/CMakeLists.txt new file mode 100644 index 00000000000..b8842687e5e --- /dev/null +++ b/src/plugins/effectmakernew/CMakeLists.txt @@ -0,0 +1,26 @@ +find_package(Qt6 OPTIONAL_COMPONENTS Gui Quick ShaderTools) + +add_qtc_plugin(EffectMakerNew + CONDITION TARGET QmlDesigner AND TARGET Qt::ShaderTools + PLUGIN_DEPENDS + QtCreator::Core QtCreator::QmlDesigner + DEPENDS + Qt::Core + QtCreator::Utils Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick Qt::ShaderTools Qt::ShaderToolsPrivate + SOURCES + effectmakerplugin.cpp effectmakerplugin.h + effectmakerwidget.cpp effectmakerwidget.h + effectmakerview.cpp effectmakerview.h + effectmakermodel.cpp effectmakermodel.h + effectmakernodesmodel.cpp effectmakernodesmodel.h + effectmakeruniformsmodel.cpp effectmakeruniformsmodel.h + effectnode.cpp effectnode.h + effectnodescategory.cpp effectnodescategory.h + compositionnode.cpp compositionnode.h + uniform.cpp uniform.h + effectutils.cpp effectutils.h + effectmakercontextobject.cpp effectmakercontextobject.h + shaderfeatures.cpp shaderfeatures.h + syntaxhighlighterdata.cpp syntaxhighlighterdata.h + BUILD_DEFAULT OFF +) diff --git a/src/plugins/effectmakernew/EffectMakerNew.json.in b/src/plugins/effectmakernew/EffectMakerNew.json.in new file mode 100644 index 00000000000..46c5e12247f --- /dev/null +++ b/src/plugins/effectmakernew/EffectMakerNew.json.in @@ -0,0 +1,15 @@ +{ + \"Name\" : \"EffectMakerNew\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Revision\" : \"$$QTC_PLUGIN_REVISION\", + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\" + ], + \"Description\" : \"Plugin for Effect Maker.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/qmldesigner/components/effectmaker/compositionnode.cpp b/src/plugins/effectmakernew/compositionnode.cpp similarity index 98% rename from src/plugins/qmldesigner/components/effectmaker/compositionnode.cpp rename to src/plugins/effectmakernew/compositionnode.cpp index 2e707636098..74e43c76d56 100644 --- a/src/plugins/qmldesigner/components/effectmaker/compositionnode.cpp +++ b/src/plugins/effectmakernew/compositionnode.cpp @@ -12,7 +12,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { CompositionNode::CompositionNode(const QString &qenPath) { @@ -119,4 +119,5 @@ void CompositionNode::parse(const QString &qenPath) } } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/compositionnode.h b/src/plugins/effectmakernew/compositionnode.h similarity index 96% rename from src/plugins/qmldesigner/components/effectmaker/compositionnode.h rename to src/plugins/effectmakernew/compositionnode.h index 840abde96ea..2637bfb3879 100644 --- a/src/plugins/qmldesigner/components/effectmaker/compositionnode.h +++ b/src/plugins/effectmakernew/compositionnode.h @@ -7,7 +7,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { class CompositionNode : public QObject { @@ -57,4 +57,5 @@ private: EffectMakerUniformsModel m_unifomrsModel; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.cpp b/src/plugins/effectmakernew/effectmakercontextobject.cpp similarity index 93% rename from src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.cpp rename to src/plugins/effectmakernew/effectmakercontextobject.cpp index 7ee8399c2cf..6a37765d5c2 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.cpp +++ b/src/plugins/effectmakernew/effectmakercontextobject.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,7 +27,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { EffectMakerContextObject::EffectMakerContextObject(QQmlContext *context, QObject *parent) : QObject(parent) @@ -117,7 +117,7 @@ void EffectMakerContextObject::setBackendValues(QQmlPropertyMap *newBackendValue emit backendValuesChanged(); } -void EffectMakerContextObject::setModel(Model *model) +void EffectMakerContextObject::setModel(QmlDesigner::Model *model) { m_model = model; } @@ -169,7 +169,7 @@ int EffectMakerContextObject::devicePixelRatio() QStringList EffectMakerContextObject::allStatesForId(const QString &id) { if (m_model && m_model->rewriterView()) { - const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); + const QmlDesigner::QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); if (node.isValid()) return node.allStateNames(); } @@ -182,4 +182,5 @@ bool EffectMakerContextObject::isBlocked(const QString &) const return false; } -} // QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.h b/src/plugins/effectmakernew/effectmakercontextobject.h similarity index 97% rename from src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.h rename to src/plugins/effectmakernew/effectmakercontextobject.h index e27957f4ece..9b76dc36d8f 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakercontextobject.h +++ b/src/plugins/effectmakernew/effectmakercontextobject.h @@ -14,7 +14,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { class EffectMakerContextObject : public QObject { @@ -90,7 +90,7 @@ private: int m_majorVersion = 1; QQmlPropertyMap *m_backendValues = nullptr; - Model *m_model = nullptr; + QmlDesigner::Model *m_model = nullptr; QPoint m_lastPos; @@ -98,4 +98,5 @@ private: bool m_selectionChanged = false; }; -} // QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakermodel.cpp b/src/plugins/effectmakernew/effectmakermodel.cpp similarity index 78% rename from src/plugins/qmldesigner/components/effectmaker/effectmakermodel.cpp rename to src/plugins/effectmakernew/effectmakermodel.cpp index 42f0977f7dc..9da7d97ef60 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakermodel.cpp +++ b/src/plugins/effectmakernew/effectmakermodel.cpp @@ -12,11 +12,43 @@ #include -namespace QmlDesigner { +namespace EffectMaker { + +enum class FileType +{ + Binary, + Text +}; + +static bool writeToFile(const QByteArray &buf, const QString &filename, FileType fileType) +{ + QDir().mkpath(QFileInfo(filename).path()); + QFile f(filename); + QIODevice::OpenMode flags = QIODevice::WriteOnly | QIODevice::Truncate; + if (fileType == FileType::Text) + flags |= QIODevice::Text; + if (!f.open(flags)) { + qWarning() << "Failed to open file for writing:" << filename; + return false; + } + f.write(buf); + return true; +} EffectMakerModel::EffectMakerModel(QObject *parent) : QAbstractListModel{parent} { + m_vertexShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb"); + m_fragmentShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb"); + // TODO: Will be revisted later when saving output files + if (m_vertexShaderFile.open()) + qInfo() << "Using temporary vs file:" << m_vertexShaderFile.fileName(); + if (m_fragmentShaderFile.open()) + qInfo() << "Using temporary fs file:" << m_fragmentShaderFile.fileName(); + + // Prepare baker + m_baker.setGeneratedShaderVariants({ QShader::StandardShader }); + updateBakedShaderVersions(); } QHash EffectMakerModel::roleNames() const @@ -72,6 +104,8 @@ void EffectMakerModel::addNode(const QString &nodeQenPath) endInsertRows(); setIsEmpty(false); + + bakeShaders(); } void EffectMakerModel::moveNode(int fromIdx, int toIdx) @@ -83,6 +117,8 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx) beginMoveRows({}, fromIdx, fromIdx, {}, toIdxAdjusted); m_nodes.move(fromIdx, toIdx); endMoveRows(); + + bakeShaders(); } void EffectMakerModel::removeNode(int idx) @@ -95,6 +131,23 @@ void EffectMakerModel::removeNode(int idx) if (m_nodes.isEmpty()) setIsEmpty(true); + else + bakeShaders(); +} + +void EffectMakerModel::updateBakedShaderVersions() +{ + QList targets; + targets.append({ QShader::SpirvShader, QShaderVersion(100) }); // Vulkan 1.0 + targets.append({ QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0 + targets.append({ QShader::MslShader, QShaderVersion(12) }); // Metal 1.2 + targets.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) }); // GLES 3.0+ + targets.append({ QShader::GlslShader, QShaderVersion(410) }); // OpenGL 4.1+ + targets.append({ QShader::GlslShader, QShaderVersion(330) }); // OpenGL 3.3 + targets.append({ QShader::GlslShader, QShaderVersion(140) }); // OpenGL 3.1 + //TODO: Do we need support for legacy shaders 100, 120? + + m_baker.setGeneratedShaders(targets); } QString EffectMakerModel::fragmentShader() const @@ -123,6 +176,11 @@ void EffectMakerModel::setVertexShader(const QString &newVertexShader) m_vertexShader = newVertexShader; } +const QString &EffectMakerModel::qmlComponentString() const +{ + return m_qmlComponentString; +} + const QList EffectMakerModel::allUniforms() { QList uniforms = {}; @@ -554,7 +612,7 @@ QString EffectMakerModel::generateVertexShader(bool includeUniforms) const bool removeTags = includeUniforms; s += getDefineProperties(); - s += getConstVariables(); + // s += getConstVariables(); // Not sure yet, will check on this later // When the node is complete, add shader code in correct nodes order // split to root and main parts @@ -614,7 +672,7 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms) const bool removeTags = includeUniforms; s += getDefineProperties(); - s += getConstVariables(); + // s += getConstVariables(); // Not sure yet, will check on this later // When the node is complete, add shader code in correct nodes order // split to root and main parts @@ -740,11 +798,44 @@ void EffectMakerModel::bakeShaders() // First update the features based on shader content // This will make sure that next calls to "generate" will produce correct uniforms. - m_shaderFeatures.update(generateVertexShader(false), generateFragmentShader(false), m_previewEffectPropertiesString); + m_shaderFeatures.update(generateVertexShader(false), generateFragmentShader(false), + m_previewEffectPropertiesString); updateCustomUniforms(); - // TODO: Shaders baking + setVertexShader(generateVertexShader()); + QString vs = m_vertexShader; + m_baker.setSourceString(vs.toUtf8(), QShader::VertexStage); + QShader vertShader = m_baker.bake(); + if (!vertShader.isValid()) { + qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage()); + setEffectError(m_baker.errorMessage().split('\n').first(), ErrorVert); + } else { + QString filename = m_vertexShaderFile.fileName(); + writeToFile(vertShader.serialized(), filename, FileType::Binary); + resetEffectError(ErrorVert); + } + + setFragmentShader(generateFragmentShader()); + QString fs = m_fragmentShader; + m_baker.setSourceString(fs.toUtf8(), QShader::FragmentStage); + + QShader fragShader = m_baker.bake(); + if (!fragShader.isValid()) { + qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage()); + setEffectError(m_baker.errorMessage().split('\n').first(), ErrorFrag); + } else { + QString filename = m_fragmentShaderFile.fileName(); + writeToFile(fragShader.serialized(), filename, FileType::Binary); + resetEffectError(ErrorFrag); + } + + if (vertShader.isValid() && fragShader.isValid()) { + Q_EMIT shadersBaked(); + setShadersUpToDate(true); + } + + // TODO: Mark shaders as baked, required by export later } bool EffectMakerModel::shadersUpToDate() const @@ -760,4 +851,86 @@ void EffectMakerModel::setShadersUpToDate(bool UpToDate) emit shadersUpToDateChanged(); } -} // namespace QmlDesigner +QString EffectMakerModel::getQmlImagesString(bool localFiles) +{ + Q_UNUSED(localFiles) + + // TODO + return QString(); +} + +QString EffectMakerModel::getQmlComponentString(bool localFiles) +{ + auto addProperty = [localFiles](const QString &name, const QString &var, + const QString &type, bool blurHelper = false) + { + if (localFiles) { + const QString parent = blurHelper ? QString("blurHelper.") : QString("rootItem."); + return QString("readonly property alias %1: %2%3\n").arg(name, parent, var); + } else { + const QString parent = blurHelper ? "blurHelper." : QString(); + return QString("readonly property %1 %2: %3%4\n").arg(type, name, parent, var); + } + }; + + QString customImagesString = getQmlImagesString(localFiles); + QString vertexShaderFilename = "file:///" + m_fragmentShaderFile.fileName(); + QString fragmentShaderFilename = "file:///" + m_vertexShaderFile.fileName(); + QString s; + QString l1 = localFiles ? QStringLiteral(" ") : QStringLiteral(""); + QString l2 = localFiles ? QStringLiteral(" ") : QStringLiteral(" "); + QString l3 = localFiles ? QStringLiteral(" ") : QStringLiteral(" "); + + if (!localFiles) + s += "import QtQuick\n"; + s += l1 + "ShaderEffect {\n"; + if (m_shaderFeatures.enabled(ShaderFeatures::Source)) + s += l2 + addProperty("iSource", "source", "Item"); + if (m_shaderFeatures.enabled(ShaderFeatures::Time)) + s += l2 + addProperty("iTime", "animatedTime", "real"); + if (m_shaderFeatures.enabled(ShaderFeatures::Frame)) + s += l2 + addProperty("iFrame", "animatedFrame", "int"); + if (m_shaderFeatures.enabled(ShaderFeatures::Resolution)) { + // Note: Pixel ratio is currently always 1.0 + s += l2 + "readonly property vector3d iResolution: Qt.vector3d(width, height, 1.0)\n"; + } + if (m_shaderFeatures.enabled(ShaderFeatures::Mouse)) { // Do we need interactive effects? + s += l2 + "readonly property vector4d iMouse: Qt.vector4d(rootItem._effectMouseX, rootItem._effectMouseY,\n"; + s += l2 + " rootItem._effectMouseZ, rootItem._effectMouseW)\n"; + } + if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) { + s += l2 + addProperty("iSourceBlur1", "blurSrc1", "Item", true); + s += l2 + addProperty("iSourceBlur2", "blurSrc2", "Item", true); + s += l2 + addProperty("iSourceBlur3", "blurSrc3", "Item", true); + s += l2 + addProperty("iSourceBlur4", "blurSrc4", "Item", true); + s += l2 + addProperty("iSourceBlur5", "blurSrc5", "Item", true); + } + // When used in preview component, we need property with value + // and when in exported component, property with binding to root value. + s += localFiles ? m_exportedEffectPropertiesString : m_previewEffectPropertiesString; + if (!customImagesString.isEmpty()) + s += '\n' + customImagesString; + + s += '\n'; + s += l2 + "vertexShader: '" + vertexShaderFilename + "'\n"; + s += l2 + "fragmentShader: '" + fragmentShaderFilename + "'\n"; + s += l2 + "anchors.fill: parent\n"; + if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) { + QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()).arg(m_shaderFeatures.gridMeshHeight()); + s += l2 + "mesh: GridMesh {\n"; + s += l3 + QString("resolution: Qt.size(%1)\n").arg(gridSize); + s += l2 + "}\n"; + } + s += l1 + "}\n"; + return s; +} + +void EffectMakerModel::updateQmlComponent() +{ + // Clear possible QML runtime errors + resetEffectError(ErrorQMLRuntime); + m_qmlComponentString = getQmlComponentString(false); +} + +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakermodel.h b/src/plugins/effectmakernew/effectmakermodel.h similarity index 87% rename from src/plugins/qmldesigner/components/effectmaker/effectmakermodel.h rename to src/plugins/effectmakernew/effectmakermodel.h index 58c6a93fd8f..395a9063c53 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakermodel.h +++ b/src/plugins/effectmakernew/effectmakermodel.h @@ -8,8 +8,11 @@ #include #include #include +#include -namespace QmlDesigner { +#include + +namespace EffectMaker { class CompositionNode; class Uniform; @@ -33,6 +36,8 @@ class EffectMakerModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged) + Q_PROPERTY(QString qmlComponentString READ qmlComponentString) + public: EffectMakerModel(QObject *parent = nullptr); @@ -58,11 +63,17 @@ public: QString vertexShader() const; void setVertexShader(const QString &newVertexShader); + const QString &qmlComponentString() const; + void setQmlComponentString(const QString &string); + + Q_INVOKABLE void updateQmlComponent(); + signals: void isEmptyChanged(); void selectedIndexChanged(int idx); void effectErrorChanged(); void shadersUpToDateChanged(); + void shadersBaked(); private: enum Roles { @@ -88,6 +99,7 @@ private: const QString getVSUniforms(); const QString getFSUniforms(); + void updateBakedShaderVersions(); QString detectErrorMessage(const QString &errorMessage); EffectError effectError() const; void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); @@ -112,6 +124,9 @@ private: void updateCustomUniforms(); void bakeShaders(); + QString getQmlImagesString(bool localFiles); + QString getQmlComponentString(bool localFiles); + QList m_nodes; int m_selectedIndex = -1; @@ -125,14 +140,19 @@ private: QString m_vertexShader; QStringList m_defaultRootVertexShader; QStringList m_defaultRootFragmentShader; + QShaderBaker m_baker; + QTemporaryFile m_fragmentShaderFile; + QTemporaryFile m_vertexShaderFile; // Used in exported QML, at root of the file QString m_exportedRootPropertiesString; // Used in exported QML, at ShaderEffect component of the file QString m_exportedEffectPropertiesString; // Used in preview QML, at ShaderEffect component of the file QString m_previewEffectPropertiesString; + QString m_qmlComponentString; const QRegularExpression m_spaceReg = QRegularExpression("\\s+"); }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.cpp b/src/plugins/effectmakernew/effectmakernodesmodel.cpp similarity index 97% rename from src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.cpp rename to src/plugins/effectmakernew/effectmakernodesmodel.cpp index 521e3e7ce21..3d3bb9f95d6 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.cpp +++ b/src/plugins/effectmakernew/effectmakernodesmodel.cpp @@ -7,7 +7,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { EffectMakerNodesModel::EffectMakerNodesModel(QObject *parent) : QAbstractListModel{parent} @@ -73,6 +73,8 @@ void EffectMakerNodesModel::loadModel() return; } + m_categories = {}; + QDirIterator itCategories(m_nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot); while (itCategories.hasNext()) { itCategories.next(); @@ -104,4 +106,5 @@ void EffectMakerNodesModel::resetModel() endResetModel(); } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.h b/src/plugins/effectmakernew/effectmakernodesmodel.h similarity index 94% rename from src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.h rename to src/plugins/effectmakernew/effectmakernodesmodel.h index 5ed702f84be..28a4e8484f0 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakernodesmodel.h +++ b/src/plugins/effectmakernew/effectmakernodesmodel.h @@ -9,7 +9,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { class EffectMakerNodesModel : public QAbstractListModel { @@ -40,4 +40,5 @@ private: bool m_probeNodesDir = false; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/effectmakernew/effectmakerplugin.cpp b/src/plugins/effectmakernew/effectmakerplugin.cpp new file mode 100644 index 00000000000..3890d3c9d65 --- /dev/null +++ b/src/plugins/effectmakernew/effectmakerplugin.cpp @@ -0,0 +1,46 @@ +// 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 "effectmakerplugin.h" + +#include "effectmakerview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace EffectMaker { + +bool EffectMakerPlugin::delayedInitialize() +{ + if (m_delayedInitialized) + return true; + + auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); + auto &viewManager = designerPlugin->viewManager(); + viewManager.registerView(std::make_unique( + QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly())); + + m_delayedInitialized = true; + + return true; +} + +} // namespace EffectMaker + diff --git a/src/plugins/effectmakernew/effectmakerplugin.h b/src/plugins/effectmakernew/effectmakerplugin.h new file mode 100644 index 00000000000..116115629e5 --- /dev/null +++ b/src/plugins/effectmakernew/effectmakerplugin.h @@ -0,0 +1,32 @@ +// 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 + +namespace Core { +class ActionContainer; +class ExternalTool; +} + +namespace EffectMaker { + +class EffectMakerPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "EffectMakerNew.json") + +public: + EffectMakerPlugin() {} + ~EffectMakerPlugin() override {} + + bool delayedInitialize() override; + +private: + bool m_delayedInitialized = false; +}; + +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.cpp b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp similarity index 97% rename from src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.cpp rename to src/plugins/effectmakernew/effectmakeruniformsmodel.cpp index dac01905b67..9313b986404 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.cpp +++ b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp @@ -7,7 +7,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { EffectMakerUniformsModel::EffectMakerUniformsModel(QObject *parent) : QAbstractListModel{parent} @@ -72,4 +72,5 @@ QList EffectMakerUniformsModel::uniforms() const return m_uniforms; } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.h b/src/plugins/effectmakernew/effectmakeruniformsmodel.h similarity index 95% rename from src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.h rename to src/plugins/effectmakernew/effectmakeruniformsmodel.h index 1d69d6d1b27..9b9651a8720 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakeruniformsmodel.h +++ b/src/plugins/effectmakernew/effectmakeruniformsmodel.h @@ -5,7 +5,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { class Uniform; @@ -42,4 +42,5 @@ private: QList m_uniforms; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/effectmakernew/effectmakerview.cpp b/src/plugins/effectmakernew/effectmakerview.cpp new file mode 100644 index 00000000000..4bb68f358a4 --- /dev/null +++ b/src/plugins/effectmakernew/effectmakerview.cpp @@ -0,0 +1,83 @@ +// 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 "effectmakerview.h" + +#include "effectmakerwidget.h" +#include "effectmakernodesmodel.h" + +#include "nodeinstanceview.h" +#include "qmldesignerconstants.h" + +#include + +#include +#include +#include +#include +#include + +namespace EffectMaker { + +EffectMakerContext::EffectMakerContext(QWidget *widget) + : IContext(widget) +{ + setWidget(widget); + setContext(Core::Context(QmlDesigner::Constants::C_QMLEFFECTMAKER, + QmlDesigner::Constants::C_QT_QUICK_TOOLS_MENU)); +} + +void EffectMakerContext::contextHelp(const HelpCallback &callback) const +{ + qobject_cast(m_widget)->contextHelp(callback); +} + +EffectMakerView::EffectMakerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies) + : AbstractView{externalDependencies} +{ +} + +EffectMakerView::~EffectMakerView() +{} + +bool EffectMakerView::hasWidget() const +{ + return true; +} + +QmlDesigner::WidgetInfo EffectMakerView::widgetInfo() +{ + if (m_widget.isNull()) { + m_widget = new EffectMakerWidget{this}; + + auto context = new EffectMakerContext(m_widget.data()); + Core::ICore::addContextObject(context); + } + + return createWidgetInfo(m_widget.data(), "Effect Maker", + QmlDesigner::WidgetInfo::LeftPane, 0, tr("Effect Maker")); +} + +void EffectMakerView::customNotification(const AbstractView * /*view*/, + const QString & /*identifier*/, + const QList & /*nodeList*/, + const QList & /*data*/) +{ + // TODO +} + +void EffectMakerView::modelAttached(QmlDesigner::Model *model) +{ + AbstractView::modelAttached(model); + + m_widget->effectMakerNodesModel()->loadModel(); + m_widget->initView(); +} + +void EffectMakerView::modelAboutToBeDetached(QmlDesigner::Model *model) +{ + AbstractView::modelAboutToBeDetached(model); +} + +} // namespace EffectMaker + diff --git a/src/plugins/effectmakernew/effectmakerview.h b/src/plugins/effectmakernew/effectmakerview.h new file mode 100644 index 00000000000..2bed1cfc103 --- /dev/null +++ b/src/plugins/effectmakernew/effectmakerview.h @@ -0,0 +1,46 @@ +// 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 "abstractview.h" + +#include + +#include + +namespace EffectMaker { + +class EffectMakerWidget; + +class EffectMakerContext : public Core::IContext +{ + Q_OBJECT + +public: + EffectMakerContext(QWidget *widget); + void contextHelp(const Core::IContext::HelpCallback &callback) const override; +}; + +class EffectMakerView : public QmlDesigner::AbstractView +{ +public: + EffectMakerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies); + ~EffectMakerView() override; + + bool hasWidget() const override; + QmlDesigner::WidgetInfo widgetInfo() override; + + // AbstractView + void modelAttached(QmlDesigner::Model *model) override; + void modelAboutToBeDetached(QmlDesigner::Model *model) override; + +private: + void customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) override; + + QPointer m_widget; +}; + +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.cpp b/src/plugins/effectmakernew/effectmakerwidget.cpp similarity index 88% rename from src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.cpp rename to src/plugins/effectmakernew/effectmakerwidget.cpp index f6f96bc886c..72d4c1b2cd6 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.cpp +++ b/src/plugins/effectmakernew/effectmakerwidget.cpp @@ -23,7 +23,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { static QString propertyEditorResourcesPath() { @@ -46,21 +46,22 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) m_quickWidget->quickWidget()->installEventFilter(this); // create the inner widget - m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_EFFECT_MAKER); + m_quickWidget->quickWidget()->setObjectName(QmlDesigner::Constants::OBJECT_NAME_EFFECT_MAKER); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); - Theme::setupTheme(m_quickWidget->engine()); + QmlDesigner::Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_quickWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); + m_quickWidget->setClearColor(QmlDesigner::Theme::getColor( + QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); auto layout = new QHBoxLayout(this); layout->setContentsMargins({}); layout->setSpacing(0); layout->addWidget(m_quickWidget.data()); - setStyleSheet(Theme::replaceCssColors( + setStyleSheet(QmlDesigner::Theme::replaceCssColors( QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); - QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_EFFECTMAKER_TIME); + QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(this, QmlDesigner::Constants::EVENT_EFFECTMAKER_TIME); auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend"); map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())}, @@ -142,4 +143,5 @@ void EffectMakerWidget::reloadQmlSource() m_quickWidget->setSource(QUrl::fromLocalFile(effectMakerQmlPath)); } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.h b/src/plugins/effectmakernew/effectmakerwidget.h similarity index 88% rename from src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.h rename to src/plugins/effectmakernew/effectmakerwidget.h index d59318eb459..6f55cbc786e 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakerwidget.h +++ b/src/plugins/effectmakernew/effectmakerwidget.h @@ -3,7 +3,7 @@ #pragma once -#include "qmlmodelnodeproxy.h" +#include "qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h" #include @@ -11,7 +11,7 @@ class StudioQuickWidget; -namespace QmlDesigner { +namespace EffectMaker { class EffectMakerView; class EffectMakerModel; @@ -53,7 +53,8 @@ private: QPointer m_effectMakerNodesModel; QPointer m_effectMakerView; QPointer m_quickWidget; - QmlModelNodeProxy m_backendModelNode; + QmlDesigner::QmlModelNodeProxy m_backendModelNode; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectnode.cpp b/src/plugins/effectmakernew/effectnode.cpp similarity index 94% rename from src/plugins/qmldesigner/components/effectmaker/effectnode.cpp rename to src/plugins/effectmakernew/effectnode.cpp index 08d11925f56..292c04d13e5 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectnode.cpp +++ b/src/plugins/effectmakernew/effectnode.cpp @@ -6,7 +6,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { EffectNode::EffectNode(const QString &qenPath) : m_qenPath(qenPath) @@ -39,4 +39,5 @@ QString EffectNode::qenPath() const return m_qenPath; } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectnode.h b/src/plugins/effectmakernew/effectnode.h similarity index 93% rename from src/plugins/qmldesigner/components/effectmaker/effectnode.h rename to src/plugins/effectmakernew/effectnode.h index 823fe092db0..5c457e2a6de 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectnode.h +++ b/src/plugins/effectmakernew/effectnode.h @@ -6,7 +6,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { class EffectNode : public QObject { @@ -31,4 +31,5 @@ private: QUrl m_iconPath; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectnodescategory.cpp b/src/plugins/effectmakernew/effectnodescategory.cpp similarity index 90% rename from src/plugins/qmldesigner/components/effectmaker/effectnodescategory.cpp rename to src/plugins/effectmakernew/effectnodescategory.cpp index 36a8f0a0d06..7f89766cca1 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectnodescategory.cpp +++ b/src/plugins/effectmakernew/effectnodescategory.cpp @@ -3,7 +3,7 @@ #include "effectnodescategory.h" -namespace QmlDesigner { +namespace EffectMaker { EffectNodesCategory::EffectNodesCategory(const QString &name, const QList &nodes) : m_name(name), @@ -19,4 +19,5 @@ QList EffectNodesCategory::nodes() const return m_categoryNodes; } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectnodescategory.h b/src/plugins/effectmakernew/effectnodescategory.h similarity index 92% rename from src/plugins/qmldesigner/components/effectmaker/effectnodescategory.h rename to src/plugins/effectmakernew/effectnodescategory.h index ba7d6868bc6..25f5d8d4bd6 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectnodescategory.h +++ b/src/plugins/effectmakernew/effectnodescategory.h @@ -7,7 +7,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { class EffectNodesCategory : public QObject { @@ -27,4 +27,5 @@ private: QList m_categoryNodes; }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectutils.cpp b/src/plugins/effectmakernew/effectutils.cpp similarity index 90% rename from src/plugins/qmldesigner/components/effectmaker/effectutils.cpp rename to src/plugins/effectmakernew/effectutils.cpp index 8f45b9a1370..8e2bb625431 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectutils.cpp +++ b/src/plugins/effectmakernew/effectutils.cpp @@ -5,7 +5,7 @@ #include -namespace QmlDesigner { +namespace EffectMaker { QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray) { @@ -20,4 +20,5 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray) return codeString; } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/effectutils.h b/src/plugins/effectmakernew/effectutils.h similarity index 86% rename from src/plugins/qmldesigner/components/effectmaker/effectutils.h rename to src/plugins/effectmakernew/effectutils.h index 0abe4d64e6b..e3de9312dce 100644 --- a/src/plugins/qmldesigner/components/effectmaker/effectutils.h +++ b/src/plugins/effectmakernew/effectutils.h @@ -7,7 +7,7 @@ QT_FORWARD_DECLARE_CLASS(QJsonArray) -namespace QmlDesigner { +namespace EffectMaker { class EffectUtils { @@ -17,4 +17,5 @@ public: static QString codeFromJsonArray(const QJsonArray &codeArray); }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/shaderfeatures.cpp b/src/plugins/effectmakernew/shaderfeatures.cpp similarity index 91% rename from src/plugins/qmldesigner/components/effectmaker/shaderfeatures.cpp rename to src/plugins/effectmakernew/shaderfeatures.cpp index 755b203d23c..03aea9c1f15 100644 --- a/src/plugins/qmldesigner/components/effectmaker/shaderfeatures.cpp +++ b/src/plugins/effectmakernew/shaderfeatures.cpp @@ -5,7 +5,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { ShaderFeatures::ShaderFeatures() { @@ -77,4 +77,15 @@ void ShaderFeatures::checkLine(const QString &line, Features &features) features.setFlag(BlurSources, true); } -} // namespace QmlDesigner +int ShaderFeatures::gridMeshHeight() const +{ + return m_gridMeshHeight; +} + +int ShaderFeatures::gridMeshWidth() const +{ + return m_gridMeshWidth; +} + +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/shaderfeatures.h b/src/plugins/effectmakernew/shaderfeatures.h similarity index 88% rename from src/plugins/qmldesigner/components/effectmaker/shaderfeatures.h rename to src/plugins/effectmakernew/shaderfeatures.h index 35fb507066d..4e9f09eeae2 100644 --- a/src/plugins/qmldesigner/components/effectmaker/shaderfeatures.h +++ b/src/plugins/effectmakernew/shaderfeatures.h @@ -6,7 +6,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { class ShaderFeatures { @@ -28,6 +28,10 @@ public: bool enabled(ShaderFeatures::Feature feature) const; + int gridMeshWidth() const; + + int gridMeshHeight() const; + private: void checkLine(const QString &line, ShaderFeatures::Features &features); ShaderFeatures::Features m_enabledFeatures; @@ -36,4 +40,5 @@ private: }; Q_DECLARE_OPERATORS_FOR_FLAGS(ShaderFeatures::Features) -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.cpp b/src/plugins/effectmakernew/syntaxhighlighterdata.cpp similarity index 98% rename from src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.cpp rename to src/plugins/effectmakernew/syntaxhighlighterdata.cpp index 47020ed0b0f..4a6face8196 100644 --- a/src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.cpp +++ b/src/plugins/effectmakernew/syntaxhighlighterdata.cpp @@ -3,7 +3,7 @@ #include "syntaxhighlighterdata.h" -namespace QmlDesigner { +namespace EffectMaker { static constexpr QByteArrayView shader_arg_names[] { { "gl_Position" }, @@ -186,5 +186,6 @@ QList SyntaxHighlighterData::reservedFunctionNames() return { std::begin(shader_function_names), std::end(shader_function_names) }; } -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.h b/src/plugins/effectmakernew/syntaxhighlighterdata.h similarity index 88% rename from src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.h rename to src/plugins/effectmakernew/syntaxhighlighterdata.h index 6342ea094aa..bce1100e05c 100644 --- a/src/plugins/qmldesigner/components/effectmaker/syntaxhighlighterdata.h +++ b/src/plugins/effectmakernew/syntaxhighlighterdata.h @@ -6,7 +6,7 @@ #include #include -namespace QmlDesigner { +namespace EffectMaker { class SyntaxHighlighterData { @@ -18,5 +18,6 @@ public: static QList reservedFunctionNames(); }; -} // namespace QmlDesigner +} // namespace EffectMaker + diff --git a/src/plugins/qmldesigner/components/effectmaker/uniform.cpp b/src/plugins/effectmakernew/uniform.cpp similarity index 98% rename from src/plugins/qmldesigner/components/effectmaker/uniform.cpp rename to src/plugins/effectmakernew/uniform.cpp index 8074c3cc95a..5cad43e0c95 100644 --- a/src/plugins/qmldesigner/components/effectmaker/uniform.cpp +++ b/src/plugins/effectmakernew/uniform.cpp @@ -2,14 +2,14 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "uniform.h" +#include -#include "propertyeditorvalue.h" #include #include #include -namespace QmlDesigner { +namespace EffectMaker { Uniform::Uniform(const QJsonObject &propObj) { @@ -45,7 +45,7 @@ Uniform::Uniform(const QJsonObject &propObj) setValueData(value, defaultValue, minValue, maxValue); - m_backendValue = new PropertyEditorValue(this); + m_backendValue = new QmlDesigner::PropertyEditorValue(this); m_backendValue->setValue(value); } @@ -321,4 +321,4 @@ QString Uniform::typeToProperty(Uniform::Type type) return QString(); } -} // namespace QmlDesigner +} // namespace EffectMaker diff --git a/src/plugins/qmldesigner/components/effectmaker/uniform.h b/src/plugins/effectmakernew/uniform.h similarity index 93% rename from src/plugins/qmldesigner/components/effectmaker/uniform.h rename to src/plugins/effectmakernew/uniform.h index 67699c5e53a..7bad706cb3e 100644 --- a/src/plugins/qmldesigner/components/effectmaker/uniform.h +++ b/src/plugins/effectmakernew/uniform.h @@ -6,13 +6,15 @@ #include #include +#include + QT_FORWARD_DECLARE_CLASS(QColor) QT_FORWARD_DECLARE_CLASS(QJsonObject) QT_FORWARD_DECLARE_CLASS(QVector2D) -namespace QmlDesigner { +namespace EffectMaker { + -class PropertyEditorValue; class Uniform : public QObject { @@ -97,7 +99,7 @@ private: bool m_useCustomValue = false; bool m_enabled = true; bool m_enableMipmap = false; - PropertyEditorValue *m_backendValue = nullptr; + QmlDesigner::PropertyEditorValue *m_backendValue = nullptr; bool operator==(const Uniform &rhs) const noexcept { @@ -105,4 +107,4 @@ private: } }; -} // namespace QmlDesigner +} // namespace EffectMaker diff --git a/src/plugins/mcusupport/mcusupportplugin.cpp b/src/plugins/mcusupport/mcusupportplugin.cpp index a9f8ff0d499..5805f8caeba 100644 --- a/src/plugins/mcusupport/mcusupportplugin.cpp +++ b/src/plugins/mcusupport/mcusupportplugin.cpp @@ -125,7 +125,10 @@ void McuSupportPlugin::initialize() // Temporary fix for CodeModel/Checker race condition // Remove after https://bugreports.qt.io/browse/QTCREATORBUG-29269 is closed - connect(QmlJS::ModelManagerInterface::instance(), + + if (!Core::ICore::isQtDesignStudio()) { + connect( + QmlJS::ModelManagerInterface::instance(), &QmlJS::ModelManagerInterface::documentUpdated, [lasttime = QTime::currentTime()](QmlJS::Document::Ptr doc) mutable { // Prevent inifinite recall loop @@ -157,6 +160,7 @@ void McuSupportPlugin::initialize() ->action() ->trigger(); }); + } dd->m_options.registerQchFiles(); dd->m_options.registerExamples(); diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index cc47700ee03..97dbc9eac3a 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -11,6 +11,10 @@ endif() add_compile_options("$<$:-Wno-error=maybe-uninitialized>") add_compile_options("$<$:-Wno-error=maybe-uninitialized>") +env_with_default("QDS_USE_PROJECTSTORAGE" ENV_QDS_USE_PROJECTSTORAGE OFF) +option(USE_PROJECTSTORAGE "Use ProjectStorage" ${ENV_QDS_USE_PROJECTSTORAGE}) +add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "") + add_qtc_library(QmlDesignerUtils STATIC DEPENDS Qt::Gui Utils Qt::QmlPrivate Core @@ -64,7 +68,10 @@ add_qtc_library(QmlDesignerCore STATIC QmlDesignerUtils TextEditor Sqlite - DEFINES QMLDESIGNERCORE_LIBRARY QMLDESIGNERUTILS_LIBRARY + DEFINES + QMLDESIGNERCORE_LIBRARY + QMLDESIGNERUTILS_LIBRARY + $<$:QDS_USE_PROJECTSTORAGE> INCLUDES ${CMAKE_CURRENT_LIST_DIR} PUBLIC_INCLUDES @@ -76,13 +83,6 @@ add_qtc_library(QmlDesignerCore STATIC rewritertransaction.h ) - -if(TARGET QmlDesignerCore) - env_with_default("QDS_USE_PROJECTSTORAGE" ENV_QDS_USE_PROJECTSTORAGE OFF) - option(USE_PROJECTSTORAGE "Use ProjectStorage" ${ENV_QDS_USE_PROJECTSTORAGE}) - add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "") -endif() - extend_qtc_library(QmlDesignerCore CONDITION ENABLE_COMPILE_WARNING_AS_ERROR PROPERTIES COMPILE_WARNING_AS_ERROR ON @@ -238,6 +238,7 @@ extend_qtc_library(QmlDesignerCore modelmerger.h modelnode.h modelnodepositionstorage.h + module.h nodeabstractproperty.h nodeinstance.h nodelistproperty.h @@ -574,6 +575,9 @@ extend_qtc_plugin(QmlDesigner modelnodeoperations.cpp modelnodeoperations.h formatoperation.cpp formatoperation.h navigation2d.cpp navigation2d.h + propertyeditorcomponentgenerator.cpp propertyeditorcomponentgenerator.h + propertycomponentgenerator.cpp propertycomponentgenerator.h + propertycomponentgeneratorinterface.h qmldesignericonprovider.cpp qmldesignericonprovider.h qmleditormenu.cpp qmleditormenu.h selectioncontext.cpp selectioncontext.h @@ -683,6 +687,7 @@ extend_qtc_plugin(QmlDesigner assetimportupdatetreemodel.cpp assetimportupdatetreemodel.h assetimportupdatetreeview.cpp assetimportupdatetreeview.h itemlibrary.qrc + itemlibraryconstants.h itemlibraryimageprovider.cpp itemlibraryimageprovider.h itemlibraryitem.cpp itemlibraryitem.h itemlibrarymodel.cpp itemlibrarymodel.h @@ -709,24 +714,6 @@ extend_qtc_plugin(QmlDesigner assetslibraryiconprovider.cpp assetslibraryiconprovider.h ) -extend_qtc_plugin(QmlDesigner - SOURCES_PREFIX components/effectmaker - SOURCES - effectmakerwidget.cpp effectmakerwidget.h - effectmakerview.cpp effectmakerview.h - effectmakermodel.cpp effectmakermodel.h - effectmakernodesmodel.cpp effectmakernodesmodel.h - effectmakeruniformsmodel.cpp effectmakeruniformsmodel.h - effectnode.cpp effectnode.h - effectnodescategory.cpp effectnodescategory.h - compositionnode.cpp compositionnode.h - uniform.cpp uniform.h - effectutils.cpp effectutils.h - effectmakercontextobject.cpp effectmakercontextobject.h - shaderfeatures.cpp shaderfeatures.h - syntaxhighlighterdata.cpp syntaxhighlighterdata.h -) - extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/navigator SOURCES @@ -805,7 +792,9 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/collectioneditor SOURCES - collectionmodel.cpp collectionmodel.h + collectioneditorconstants.h + collectionlistmodel.cpp collectionlistmodel.h + collectionsourcemodel.cpp collectionsourcemodel.h collectionview.cpp collectionview.h collectionwidget.cpp collectionwidget.h singlecollectionmodel.cpp singlecollectionmodel.h @@ -1214,4 +1203,3 @@ extend_qtc_plugin(qtquickplugin qtquickplugin.cpp qtquickplugin.h qtquickplugin.qrc ) -add_subdirectory(studioplugin) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 838af4d63d1..dc5a1c97411 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -56,8 +56,15 @@ Thumbnail AssetsLibraryIconProvider::createThumbnail(const QString &id, const QS originalSize = KtxImage(id).dimensions(); } - if (requestedSize.isValid()) - pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + if (requestedSize.isValid()) { + double ratio = requestedSize.width() / 48.; + if (ratio * pixmap.size().width() > requestedSize.width() + || ratio * pixmap.size().height() > requestedSize.height()) { + pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + } else if (!qFuzzyCompare(ratio, 1.)) { + pixmap = pixmap.scaled(pixmap.size() * ratio, Qt::KeepAspectRatio); + } + } return Thumbnail{pixmap, originalSize, assetType, fileSize}; } diff --git a/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp index 58238ad1664..9718691eb3f 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp @@ -3,18 +3,17 @@ #include "abstracteditordialog.h" +#include #include - #include #include #include -#include #include -#include -#include #include #include +#include +#include namespace QmlDesigner { @@ -71,7 +70,7 @@ QString AbstractEditorDialog::editorValue() const void AbstractEditorDialog::setEditorValue(const QString &text) { if (m_editorWidget) - m_editorWidget->document()->setPlainText(text); + m_editorWidget->setEditorTextWithIndentation(text); } void AbstractEditorDialog::unregisterAutoCompletion() @@ -102,8 +101,6 @@ void AbstractEditorDialog::setupJSEditor() m_editorWidget->setLineNumbersVisible(false); m_editorWidget->setMarksVisible(false); m_editorWidget->setCodeFoldingSupported(false); - m_editorWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_editorWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_editorWidget->setTabChangesFocus(true); } @@ -123,7 +120,8 @@ void AbstractEditorDialog::setupUIComponents() m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); m_verticalLayout->addLayout(m_comboBoxLayout); - m_verticalLayout->addWidget(m_editorWidget); + //editor widget has to stretch the most among the other siblings: + m_verticalLayout->addWidget(m_editorWidget, 10); m_verticalLayout->addWidget(m_buttonBox); this->resize(660, 240); diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index 30c99b0f27a..8480115a3b7 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -83,12 +83,54 @@ void ActionEditor::hideWidget() } } +void ActionEditor::showControls(bool show) +{ + if (m_dialog) + m_dialog->showControls(show); +} + +void QmlDesigner::ActionEditor::setMultilne(bool multiline) +{ + if (m_dialog) + m_dialog->setMultiline(multiline); +} + QString ActionEditor::connectionValue() const { if (!m_dialog) return {}; - return m_dialog->editorValue(); + QString value = m_dialog->editorValue().trimmed(); + + //using parsed qml for unenclosed multistring (QDS-10681) + const QString testingString = QString("Item { \n" + " onWidthChanged: %1 \n" + "}") + .arg(value); + + QmlJS::Document::MutablePtr firstAttemptDoc = QmlJS::Document::create({}, + QmlJS::Dialect::QmlQtQuick2); + firstAttemptDoc->setSource(testingString); + firstAttemptDoc->parseQml(); + + if (!firstAttemptDoc->isParsedCorrectly()) { + const QString testingString2 = QString("Item { \n" + " onWidthChanged: { \n" + " %1 \n" + " } \n" + "} \n") + .arg(value); + + QmlJS::Document::MutablePtr secondAttemptDoc = QmlJS::Document::create({}, + QmlJS::Dialect::QmlQtQuick2); + secondAttemptDoc->setSource(testingString2); + secondAttemptDoc->parseQml(); + + if (secondAttemptDoc->isParsedCorrectly()) + return QString("{\n%1\n}").arg(value); + } + + return value; } void ActionEditor::setConnectionValue(const QString &text) @@ -97,6 +139,14 @@ void ActionEditor::setConnectionValue(const QString &text) m_dialog->setEditorValue(text); } +QString ActionEditor::rawConnectionValue() const +{ + if (!m_dialog) + return {}; + + return m_dialog->editorValue(); +} + bool ActionEditor::hasModelIndex() const { return m_index.isValid(); diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h index 0a45e054b09..9a14ad2117c 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h @@ -31,9 +31,14 @@ public: Q_INVOKABLE void showWidget(int x, int y); Q_INVOKABLE void hideWidget(); + Q_INVOKABLE void showControls(bool show); + Q_INVOKABLE void setMultilne(bool multiline); + QString connectionValue() const; void setConnectionValue(const QString &text); + QString rawConnectionValue() const; + bool hasModelIndex() const; void resetModelIndex(); QModelIndex modelIndex() const; diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp index 8fddcb1ade8..86824ac2656 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp @@ -376,6 +376,47 @@ void ActionEditorDialog::updateComboBoxes([[maybe_unused]] int index, ComboBox t } } +void ActionEditorDialog::showControls(bool show) +{ + if (m_comboBoxType) + m_comboBoxType->setVisible(show); + + if (m_actionPlaceholder) + m_actionPlaceholder->setVisible(show); + if (m_assignmentPlaceholder) + m_assignmentPlaceholder->setVisible(show); + + if (m_actionTargetItem) + m_actionTargetItem->setVisible(show); + if (m_actionMethod) + m_actionMethod->setVisible(show); + + if (m_assignmentTargetItem) + m_assignmentTargetItem->setVisible(show); + if (m_assignmentTargetProperty) + m_assignmentTargetProperty->setVisible(show); + if (m_assignmentSourceItem) + m_assignmentSourceItem->setVisible(show); + if (m_assignmentSourceProperty) + m_assignmentSourceProperty->setVisible(show); + + if (m_stackedLayout) + m_stackedLayout->setEnabled(show); + if (m_actionLayout) + m_actionLayout->setEnabled(show); + if (m_assignmentLayout) + m_assignmentLayout->setEnabled(show); + + if (m_comboBoxLayout) + m_comboBoxLayout->setEnabled(show); +} + +void ActionEditorDialog::setMultiline(bool multiline) +{ + if (m_editorWidget) + m_editorWidget->m_isMultiline = multiline; +} + void ActionEditorDialog::setupUIComponents() { m_comboBoxType = new QComboBox(this); diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h index 06427271b38..342f0d21238 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h @@ -96,6 +96,9 @@ public: void updateComboBoxes(int idx, ComboBox type); + void showControls(bool show); + void setMultiline(bool multiline); + private: void setupUIComponents(); diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp index 2b0b9f16e9d..109bcd0854e 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include #include @@ -33,6 +35,8 @@ BindingEditorWidget::BindingEditorWidget() m_context->setContext(context); Core::ICore::addContextObject(m_context); + Utils::TransientScrollAreaSupport::support(this); + /* * We have to register our own active auto completion shortcut, because the original short cut will * use the cursor position of the original editor in the editor manager. @@ -46,7 +50,7 @@ BindingEditorWidget::BindingEditorWidget() ? tr("Meta+Space") : tr("Ctrl+Space"))); - connect(m_completionAction, &QAction::triggered, [this]() { + connect(m_completionAction, &QAction::triggered, this, [this]() { invokeAssist(TextEditor::Completion); }); } @@ -68,8 +72,17 @@ void BindingEditorWidget::unregisterAutoCompletion() bool BindingEditorWidget::event(QEvent *event) { if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && !keyEvent->modifiers()) { + const QKeyEvent *keyEvent = static_cast(event); + const bool returnPressed = (keyEvent->key() == Qt::Key_Return) + || (keyEvent->key() == Qt::Key_Enter); + const Qt::KeyboardModifiers mods = keyEvent->modifiers(); + constexpr Qt::KeyboardModifier submitModifier = Qt::ControlModifier; + const bool submitModed = mods.testFlag(submitModifier); + + if (!m_isMultiline && (returnPressed && !mods)) { + emit returnKeyClicked(); + return true; + } else if (m_isMultiline && (returnPressed && submitModed)) { emit returnKeyClicked(); return true; } @@ -81,8 +94,22 @@ std::unique_ptr BindingEditorWidget::createAssistIn [[maybe_unused]] TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const { return std::make_unique( - textCursor(), Utils::FilePath(), - assistReason, qmljsdocument->semanticInfo()); + textCursor(), Utils::FilePath(), assistReason, qmljsdocument->semanticInfo()); +} + +void BindingEditorWidget::setEditorTextWithIndentation(const QString &text) +{ + auto *doc = document(); + doc->setPlainText(text); + + //we don't need to indent an empty text + //but is also needed for safer text.length()-1 below + if (text.isEmpty()) + return; + + auto modifier = std::make_unique( + doc, QTextCursor{doc}); + modifier->indent(0, text.length()-1); } BindingDocument::BindingDocument() diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.h index be1da5d4ef1..5433996110a 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.h +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.h @@ -32,6 +32,8 @@ public: std::unique_ptr createAssistInterface( TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const override; + void setEditorTextWithIndentation(const QString &text); + signals: void returnKeyClicked(); @@ -39,6 +41,7 @@ public: QmlJSEditor::QmlJSEditorDocument *qmljsdocument = nullptr; Core::IContext *m_context = nullptr; QAction *m_completionAction = nullptr; + bool m_isMultiline = false; }; class BindingDocument : public QmlJSEditor::QmlJSEditorDocument diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h new file mode 100644 index 00000000000..d75fe221e91 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h @@ -0,0 +1,14 @@ +// 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 + +namespace QmlDesigner::CollectionEditor { + +inline constexpr char SOURCEFILE_PROPERTY[] = "sourceFile"; + +inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Models"; +inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.JsonSourceModel"; +inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.CsvSourceModel"; + +} // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp new file mode 100644 index 00000000000..4c49d5e4954 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp @@ -0,0 +1,147 @@ +// 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 "collectionlistmodel.h" + +#include "collectioneditorconstants.h" +#include "variantproperty.h" + +#include +#include + +namespace { + +template +bool containsItem(const std::initializer_list &container, const ValueType &value) +{ + auto begin = std::cbegin(container); + auto end = std::cend(container); + + auto it = std::find(begin, end, value); + return it != end; +} +} // namespace + +namespace QmlDesigner { + +CollectionListModel::CollectionListModel(const ModelNode &sourceModel) + : QStringListModel() + , m_sourceNode(sourceModel) +{ + connect(this, &CollectionListModel::modelReset, this, &CollectionListModel::updateEmpty); + connect(this, &CollectionListModel::rowsRemoved, this, &CollectionListModel::updateEmpty); + connect(this, &CollectionListModel::rowsInserted, this, &CollectionListModel::updateEmpty); +} + +QHash CollectionListModel::roleNames() const +{ + static QHash roles; + if (roles.isEmpty()) { + roles.insert(Super::roleNames()); + roles.insert({ + {IdRole, "collectionId"}, + {NameRole, "collectionName"}, + {SelectedRole, "collectionIsSelected"}, + }); + } + return roles; +} + +bool CollectionListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + if (containsItem({IdRole, Qt::EditRole, Qt::DisplayRole}, role)) { + return Super::setData(index, value); + } else if (role == SelectedRole) { + if (value.toBool() != index.data(SelectedRole).toBool()) { + setSelectedIndex(value.toBool() ? index.row() : -1); + return true; + } + } + return false; +} + +QVariant CollectionListModel::data(const QModelIndex &index, int role) const +{ + QTC_ASSERT(index.isValid(), return {}); + + switch (role) { + case IdRole: + return index.row(); + case NameRole: + return Super::data(index); + case SelectedRole: + return index.row() == m_selectedIndex; + } + + return Super::data(index, role); +} + +int CollectionListModel::selectedIndex() const +{ + return m_selectedIndex; +} + +ModelNode CollectionListModel::sourceNode() const +{ + return m_sourceNode; +} + +QString CollectionListModel::sourceAddress() const +{ + return m_sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); +} + +void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne) +{ + int collectionCount = stringList().size(); + int preferredIndex = -1; + if (collectionCount) { + if (selectAtLeastOne) + preferredIndex = std::max(0, std::min(idx, collectionCount - 1)); + else if (idx > -1 && idx < collectionCount) + preferredIndex = idx; + } + + setSelectedIndex(preferredIndex); +} + +QString CollectionListModel::collectionNameAt(int idx) const +{ + return index(idx).data(NameRole).toString(); +} + +void CollectionListModel::setSelectedIndex(int idx) +{ + idx = (idx > -1 && idx < rowCount()) ? idx : -1; + + if (m_selectedIndex != idx) { + QModelIndex previousIndex = index(m_selectedIndex); + QModelIndex newIndex = index(idx); + + m_selectedIndex = idx; + + if (previousIndex.isValid()) + emit dataChanged(previousIndex, previousIndex, {SelectedRole}); + + if (newIndex.isValid()) + emit dataChanged(newIndex, newIndex, {SelectedRole}); + + emit selectedIndexChanged(idx); + } +} + +void CollectionListModel::updateEmpty() +{ + bool isEmptyNow = stringList().isEmpty(); + if (m_isEmpty != isEmptyNow) { + m_isEmpty = isEmptyNow; + emit isEmptyChanged(m_isEmpty); + + if (m_isEmpty) + setSelectedIndex(-1); + } +} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h new file mode 100644 index 00000000000..e30b16a5ff7 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h @@ -0,0 +1,50 @@ +// 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 "modelnode.h" + +namespace QmlDesigner { + +class CollectionListModel : public QStringListModel +{ + Q_OBJECT + Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + +public: + enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole }; + + explicit CollectionListModel(const ModelNode &sourceModel); + virtual QHash roleNames() const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE int selectedIndex() const; + Q_INVOKABLE ModelNode sourceNode() const; + Q_INVOKABLE QString sourceAddress() const; + + void selectCollectionIndex(int idx, bool selectAtLeastOne = false); + QString collectionNameAt(int idx) const; + +signals: + void selectedIndexChanged(int idx); + void isEmptyChanged(bool); + +private: + void setSelectedIndex(int idx); + + void updateEmpty(); + + using Super = QStringListModel; + int m_selectedIndex = -1; + bool m_isEmpty = false; + const ModelNode m_sourceNode; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp deleted file mode 100644 index 18e651fc2e2..00000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// 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 "collectionmodel.h" - -#include "abstractview.h" -#include "variantproperty.h" - -#include - -namespace QmlDesigner { -CollectionModel::CollectionModel() {} - -int CollectionModel::rowCount(const QModelIndex &) const -{ - return m_collections.size(); -} - -QVariant CollectionModel::data(const QModelIndex &index, int role) const -{ - QTC_ASSERT(index.isValid(), return {}); - - const ModelNode *collection = &m_collections.at(index.row()); - - switch (role) { - case IdRole: - return collection->id(); - case NameRole: - return collection->variantProperty("objectName").value(); - case SelectedRole: - return index.row() == m_selectedIndex; - } - - return {}; -} - -bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid()) - return false; - - ModelNode collection = m_collections.at(index.row()); - switch (role) { - case IdRole: { - if (collection.id() == value) - return false; - - bool duplicatedId = Utils::anyOf(std::as_const(m_collections), - [&collection, &value](const ModelNode &otherCollection) { - return (otherCollection.id() == value - && otherCollection != collection); - }); - if (duplicatedId) - return false; - - collection.setIdWithRefactoring(value.toString()); - } break; - case Qt::DisplayRole: - case NameRole: { - auto collectionName = collection.variantProperty("objectName"); - if (collectionName.value() == value) - return false; - - collectionName.setValue(value.toString()); - } break; - case SelectedRole: { - if (value.toBool() != index.data(SelectedRole).toBool()) - setSelectedIndex(value.toBool() ? index.row() : -1); - else - return false; - } break; - default: - return false; - } - - return true; -} - -bool CollectionModel::removeRows(int row, int count, [[maybe_unused]] const QModelIndex &parent) -{ - const int rowMax = std::min(row + count, rowCount()); - - if (row >= rowMax || row < 0) - return false; - - AbstractView *view = m_collections.at(row).view(); - if (!view) - return false; - - count = rowMax - row; - - bool selectionUpdateNeeded = m_selectedIndex >= row && m_selectedIndex < rowMax; - - // It's better to remove the group of nodes here because of the performance issue for the list, - // and update issue for the view - beginRemoveRows({}, row, rowMax - 1); - - view->executeInTransaction(Q_FUNC_INFO, [row, count, this]() { - for (ModelNode node : Utils::span(m_collections).subspan(row, count)) { - m_collectionsIndexHash.remove(node.internalId()); - node.destroy(); - } - }); - - m_collections.remove(row, count); - - int idx = row; - for (const ModelNode &node : Utils::span(m_collections).subspan(row)) - m_collectionsIndexHash.insert(node.internalId(), ++idx); - - endRemoveRows(); - - if (selectionUpdateNeeded) - updateSelectedCollection(); - - updateEmpty(); - return true; -} - -QHash CollectionModel::roleNames() const -{ - static QHash roles; - if (roles.isEmpty()) { - roles.insert(Super::roleNames()); - roles.insert({ - {IdRole, "collectionId"}, - {NameRole, "collectionName"}, - {SelectedRole, "collectionIsSelected"}, - }); - } - return roles; -} - -void CollectionModel::setCollections(const ModelNodes &collections) -{ - beginResetModel(); - bool wasEmpty = isEmpty(); - m_collections = collections; - m_collectionsIndexHash.clear(); - int i = 0; - for (const ModelNode &collection : collections) - m_collectionsIndexHash.insert(collection.internalId(), i++); - - if (wasEmpty != isEmpty()) - emit isEmptyChanged(isEmpty()); - - endResetModel(); - - updateSelectedCollection(true); -} - -void CollectionModel::removeCollection(const ModelNode &node) -{ - int nodePlace = m_collectionsIndexHash.value(node.internalId(), -1); - if (nodePlace < 0) - return; - - removeRow(nodePlace); -} - -int CollectionModel::collectionIndex(const ModelNode &node) const -{ - return m_collectionsIndexHash.value(node.internalId(), -1); -} - -void CollectionModel::selectCollection(const ModelNode &node) -{ - int nodePlace = m_collectionsIndexHash.value(node.internalId(), -1); - if (nodePlace < 0) - return; - - selectCollectionIndex(nodePlace, true); -} - -QmlDesigner::ModelNode CollectionModel::collectionNodeAt(int idx) -{ - QModelIndex data = index(idx); - if (!data.isValid()) - return {}; - - return m_collections.at(idx); -} - -bool CollectionModel::isEmpty() const -{ - return m_collections.isEmpty(); -} - -void CollectionModel::selectCollectionIndex(int idx, bool selectAtLeastOne) -{ - int collectionCount = m_collections.size(); - int prefferedIndex = -1; - if (collectionCount) { - if (selectAtLeastOne) - prefferedIndex = std::max(0, std::min(idx, collectionCount - 1)); - else if (idx > -1 && idx < collectionCount) - prefferedIndex = idx; - } - - setSelectedIndex(prefferedIndex); -} - -void CollectionModel::deselect() -{ - setSelectedIndex(-1); -} - -void CollectionModel::updateSelectedCollection(bool selectAtLeastOne) -{ - int idx = m_selectedIndex; - m_selectedIndex = -1; - selectCollectionIndex(idx, selectAtLeastOne); -} - -void CollectionModel::updateNodeName(const ModelNode &node) -{ - QModelIndex index = indexOfNode(node); - emit dataChanged(index, index, {NameRole, Qt::DisplayRole}); -} - -void CollectionModel::updateNodeId(const ModelNode &node) -{ - QModelIndex index = indexOfNode(node); - emit dataChanged(index, index, {IdRole}); -} - -void CollectionModel::setSelectedIndex(int idx) -{ - idx = (idx > -1 && idx < m_collections.count()) ? idx : -1; - - if (m_selectedIndex != idx) { - QModelIndex previousIndex = index(m_selectedIndex); - QModelIndex newIndex = index(idx); - - m_selectedIndex = idx; - - if (previousIndex.isValid()) - emit dataChanged(previousIndex, previousIndex, {SelectedRole}); - - if (newIndex.isValid()) - emit dataChanged(newIndex, newIndex, {SelectedRole}); - - emit selectedIndexChanged(idx); - } -} - -void CollectionModel::updateEmpty() -{ - bool isEmptyNow = isEmpty(); - if (m_isEmpty != isEmptyNow) { - m_isEmpty = isEmptyNow; - emit isEmptyChanged(m_isEmpty); - - if (m_isEmpty) - setSelectedIndex(-1); - } -} - -QModelIndex CollectionModel::indexOfNode(const ModelNode &node) const -{ - return index(m_collectionsIndexHash.value(node.internalId(), -1)); -} -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp new file mode 100644 index 00000000000..999ebe449ba --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp @@ -0,0 +1,401 @@ +// 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 "collectionsourcemodel.h" + +#include "abstractview.h" +#include "collectioneditorconstants.h" +#include "collectionlistmodel.h" +#include "variantproperty.h" + +#include + +#include +#include +#include +#include + +namespace { + +QSharedPointer loadCollection( + const QmlDesigner::ModelNode &sourceNode, + QSharedPointer initialCollection = {}) +{ + using namespace QmlDesigner::CollectionEditor; + QString sourceFileAddress = sourceNode.variantProperty(SOURCEFILE_PROPERTY).value().toString(); + + QSharedPointer collectionsList; + auto setupCollectionList = [&sourceNode, &initialCollection, &collectionsList]() { + if (initialCollection.isNull()) + collectionsList.reset(new QmlDesigner::CollectionListModel(sourceNode)); + else if (initialCollection->sourceNode() == sourceNode) + collectionsList = initialCollection; + else + collectionsList.reset(new QmlDesigner::CollectionListModel(sourceNode)); + }; + + if (sourceNode.type() == JSONCOLLECTIONMODEL_TYPENAME) { + QFile sourceFile(sourceFileAddress); + if (!sourceFile.open(QFile::ReadOnly)) + return {}; + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) + return {}; + + setupCollectionList(); + + if (document.isObject()) { + const QJsonObject sourceObject = document.object(); + collectionsList->setStringList(sourceObject.toVariantMap().keys()); + } + } else if (sourceNode.type() == CSVCOLLECTIONMODEL_TYPENAME) { + QmlDesigner::VariantProperty collectionNameProperty = sourceNode.variantProperty( + "objectName"); + setupCollectionList(); + collectionsList->setStringList({collectionNameProperty.value().toString()}); + } + return collectionsList; +} +} // namespace + +namespace QmlDesigner { + +CollectionSourceModel::CollectionSourceModel() {} + +int CollectionSourceModel::rowCount(const QModelIndex &) const +{ + return m_collectionSources.size(); +} + +QVariant CollectionSourceModel::data(const QModelIndex &index, int role) const +{ + QTC_ASSERT(index.isValid(), return {}); + + const ModelNode *collectionSource = &m_collectionSources.at(index.row()); + + switch (role) { + case IdRole: + return collectionSource->id(); + case NameRole: + return collectionSource->variantProperty("objectName").value(); + case SourceRole: + return collectionSource->variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value(); + case SelectedRole: + return index.row() == m_selectedIndex; + case CollectionsRole: + return QVariant::fromValue(m_collectionList.at(index.row()).data()); + } + + return {}; +} + +bool CollectionSourceModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + ModelNode collectionSource = m_collectionSources.at(index.row()); + switch (role) { + case IdRole: { + if (collectionSource.id() == value) + return false; + + bool duplicatedId = Utils::anyOf(std::as_const(m_collectionSources), + [&collectionSource, &value](const ModelNode &otherCollection) { + return (otherCollection.id() == value + && otherCollection != collectionSource); + }); + if (duplicatedId) + return false; + + collectionSource.setIdWithRefactoring(value.toString()); + } break; + case Qt::DisplayRole: + case NameRole: { + auto collectionName = collectionSource.variantProperty("objectName"); + if (collectionName.value() == value) + return false; + + collectionName.setValue(value.toString()); + } break; + case SourceRole: { + auto sourceAddress = collectionSource.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY); + if (sourceAddress.value() == value) + return false; + + sourceAddress.setValue(value.toString()); + } break; + case SelectedRole: { + if (value.toBool() != index.data(SelectedRole).toBool()) + setSelectedIndex(value.toBool() ? index.row() : -1); + else + return false; + } break; + default: + return false; + } + + return true; +} + +bool CollectionSourceModel::removeRows(int row, int count, [[maybe_unused]] const QModelIndex &parent) +{ + const int rowMax = std::min(row + count, rowCount()); + + if (row >= rowMax || row < 0) + return false; + + AbstractView *view = m_collectionSources.at(row).view(); + if (!view) + return false; + + count = rowMax - row; + + bool selectionUpdateNeeded = m_selectedIndex >= row && m_selectedIndex < rowMax; + + // It's better to remove the group of nodes here because of the performance issue for the list, + // and update issue for the view + beginRemoveRows({}, row, rowMax - 1); + + view->executeInTransaction(Q_FUNC_INFO, [row, count, this]() { + for (ModelNode node : Utils::span(m_collectionSources).subspan(row, count)) { + m_sourceIndexHash.remove(node.internalId()); + node.destroy(); + } + m_collectionSources.remove(row, count); + m_collectionList.remove(row, count); + }); + + int idx = row; + for (const ModelNode &node : Utils::span(m_collectionSources).subspan(row)) + m_sourceIndexHash.insert(node.internalId(), ++idx); + + endRemoveRows(); + + if (selectionUpdateNeeded) + updateSelectedSource(); + + updateEmpty(); + return true; +} + +QHash CollectionSourceModel::roleNames() const +{ + static QHash roles; + if (roles.isEmpty()) { + roles.insert(Super::roleNames()); + roles.insert({{IdRole, "sourceId"}, + {NameRole, "sourceName"}, + {SelectedRole, "sourceIsSelected"}, + {SourceRole, "sourceAddress"}, + {CollectionsRole, "collections"}}); + } + return roles; +} + +void CollectionSourceModel::setSources(const ModelNodes &sources) +{ + beginResetModel(); + m_collectionSources = sources; + m_sourceIndexHash.clear(); + m_collectionList.clear(); + int i = -1; + for (const ModelNode &collectionSource : sources) { + m_sourceIndexHash.insert(collectionSource.internalId(), ++i); + + auto loadedCollection = loadCollection(collectionSource); + m_collectionList.append(loadedCollection); + + connect(loadedCollection.data(), + &CollectionListModel::selectedIndexChanged, + this, + &CollectionSourceModel::onSelectedCollectionChanged, + Qt::UniqueConnection); + } + + updateEmpty(); + endResetModel(); + + updateSelectedSource(true); +} + +void CollectionSourceModel::removeSource(const ModelNode &node) +{ + int nodePlace = m_sourceIndexHash.value(node.internalId(), -1); + if (nodePlace < 0) + return; + + removeRow(nodePlace); +} + +int CollectionSourceModel::sourceIndex(const ModelNode &node) const +{ + return m_sourceIndexHash.value(node.internalId(), -1); +} + +void CollectionSourceModel::addSource(const ModelNode &node) +{ + int newRowId = m_collectionSources.count(); + beginInsertRows({}, newRowId, newRowId); + m_collectionSources.append(node); + m_sourceIndexHash.insert(node.internalId(), newRowId); + + auto loadedCollection = loadCollection(node); + m_collectionList.append(loadedCollection); + + connect(loadedCollection.data(), + &CollectionListModel::selectedIndexChanged, + this, + &CollectionSourceModel::onSelectedCollectionChanged, + Qt::UniqueConnection); + + updateEmpty(); + endInsertRows(); + updateSelectedSource(true); +} + +void CollectionSourceModel::selectSource(const ModelNode &node) +{ + int nodePlace = m_sourceIndexHash.value(node.internalId(), -1); + if (nodePlace < 0) + return; + + selectSourceIndex(nodePlace, true); +} + +QmlDesigner::ModelNode CollectionSourceModel::sourceNodeAt(int idx) +{ + QModelIndex data = index(idx); + if (!data.isValid()) + return {}; + + return m_collectionSources.at(idx); +} + +CollectionListModel *CollectionSourceModel::selectedCollectionList() +{ + QModelIndex idx = index(m_selectedIndex); + if (!idx.isValid()) + return {}; + + return idx.data(CollectionsRole).value(); +} + +void CollectionSourceModel::selectSourceIndex(int idx, bool selectAtLeastOne) +{ + int collectionCount = m_collectionSources.size(); + int preferredIndex = -1; + if (collectionCount) { + if (selectAtLeastOne) + preferredIndex = std::max(0, std::min(idx, collectionCount - 1)); + else if (idx > -1 && idx < collectionCount) + preferredIndex = idx; + } + + setSelectedIndex(preferredIndex); +} + +void CollectionSourceModel::deselect() +{ + setSelectedIndex(-1); +} + +void CollectionSourceModel::updateSelectedSource(bool selectAtLeastOne) +{ + int idx = m_selectedIndex; + m_selectedIndex = -1; + selectSourceIndex(idx, selectAtLeastOne); +} + +void CollectionSourceModel::updateNodeName(const ModelNode &node) +{ + QModelIndex index = indexOfNode(node); + emit dataChanged(index, index, {NameRole, Qt::DisplayRole}); + updateCollectionList(index); +} + +void CollectionSourceModel::updateNodeSource(const ModelNode &node) +{ + QModelIndex index = indexOfNode(node); + emit dataChanged(index, index, {SourceRole}); + updateCollectionList(index); +} + +void CollectionSourceModel::updateNodeId(const ModelNode &node) +{ + QModelIndex index = indexOfNode(node); + emit dataChanged(index, index, {IdRole}); +} + +QString CollectionSourceModel::selectedSourceAddress() const +{ + return index(m_selectedIndex).data(SourceRole).toString(); +} + +void CollectionSourceModel::onSelectedCollectionChanged(int collectionIndex) +{ + CollectionListModel *collectionList = qobject_cast(sender()); + if (collectionIndex > -1 && collectionList) { + if (_previousSelectedList && _previousSelectedList != collectionList) + _previousSelectedList->selectCollectionIndex(-1); + + emit collectionSelected(collectionList->sourceNode(), + collectionList->collectionNameAt(collectionIndex)); + + _previousSelectedList = collectionList; + } +} + +void CollectionSourceModel::setSelectedIndex(int idx) +{ + idx = (idx > -1 && idx < m_collectionSources.count()) ? idx : -1; + + if (m_selectedIndex != idx) { + QModelIndex previousIndex = index(m_selectedIndex); + QModelIndex newIndex = index(idx); + + m_selectedIndex = idx; + + if (previousIndex.isValid()) + emit dataChanged(previousIndex, previousIndex, {SelectedRole}); + + if (newIndex.isValid()) + emit dataChanged(newIndex, newIndex, {SelectedRole}); + + emit selectedIndexChanged(idx); + } +} + +void CollectionSourceModel::updateEmpty() +{ + bool isEmptyNow = m_collectionSources.isEmpty(); + if (m_isEmpty != isEmptyNow) { + m_isEmpty = isEmptyNow; + emit isEmptyChanged(m_isEmpty); + + if (m_isEmpty) + setSelectedIndex(-1); + } +} + +void CollectionSourceModel::updateCollectionList(QModelIndex index) +{ + if (!index.isValid()) + return; + + ModelNode sourceNode = sourceNodeAt(index.row()); + QSharedPointer currentList = m_collectionList.at(index.row()); + QSharedPointer newList = loadCollection(sourceNode, currentList); + if (currentList != newList) { + m_collectionList.replace(index.row(), newList); + emit this->dataChanged(index, index, {CollectionsRole}); + } +} + +QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const +{ + return index(m_sourceIndexHash.value(node.internalId(), -1)); +} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h similarity index 52% rename from src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h rename to src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h index 17831732543..bd22d833ad9 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + #pragma once #include "modelnode.h" @@ -7,22 +8,19 @@ #include #include -QT_BEGIN_NAMESPACE -class QJsonArray; -QT_END_NAMESPACE - namespace QmlDesigner { - -class CollectionModel : public QAbstractListModel +class CollectionListModel; +class CollectionSourceModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) public: - enum Roles { IdRole = Qt::UserRole + 1, NameRole, SelectedRole }; + enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole }; - explicit CollectionModel(); + explicit CollectionSourceModel(); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -36,35 +34,44 @@ public: virtual QHash roleNames() const override; - void setCollections(const ModelNodes &collections); - void removeCollection(const ModelNode &node); - int collectionIndex(const ModelNode &node) const; - void selectCollection(const ModelNode &node); + void setSources(const ModelNodes &sources); + void removeSource(const ModelNode &node); + int sourceIndex(const ModelNode &node) const; + void addSource(const ModelNode &node); + void selectSource(const ModelNode &node); - ModelNode collectionNodeAt(int idx); + ModelNode sourceNodeAt(int idx); + CollectionListModel *selectedCollectionList(); - Q_INVOKABLE bool isEmpty() const; - Q_INVOKABLE void selectCollectionIndex(int idx, bool selectAtLeastOne = false); + Q_INVOKABLE void selectSourceIndex(int idx, bool selectAtLeastOne = false); Q_INVOKABLE void deselect(); - Q_INVOKABLE void updateSelectedCollection(bool selectAtLeastOne = false); + Q_INVOKABLE void updateSelectedSource(bool selectAtLeastOne = false); void updateNodeName(const ModelNode &node); + void updateNodeSource(const ModelNode &node); void updateNodeId(const ModelNode &node); + QString selectedSourceAddress() const; + signals: void selectedIndexChanged(int idx); - void renameCollectionTriggered(const QmlDesigner::ModelNode &collection, const QString &newName); - void addNewCollectionTriggered(); + void collectionSelected(const ModelNode &sourceNode, const QString &collectionName); void isEmptyChanged(bool); +private slots: + void onSelectedCollectionChanged(int collectionIndex); + private: void setSelectedIndex(int idx); void updateEmpty(); + void updateCollectionList(QModelIndex index); using Super = QAbstractListModel; QModelIndex indexOfNode(const ModelNode &node) const; - ModelNodes m_collections; - QHash m_collectionsIndexHash; // internalId -> index + ModelNodes m_collectionSources; + QHash m_sourceIndexHash; // internalId -> index + QList> m_collectionList; + QPointer _previousSelectedList; int m_selectedIndex = -1; bool m_isEmpty = true; }; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index aaf2605b64f..53cf76312d4 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -2,12 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "collectionview.h" -#include "collectionmodel.h" + +#include "collectioneditorconstants.h" +#include "collectionsourcemodel.h" #include "collectionwidget.h" #include "designmodecontext.h" -#include "nodelistproperty.h" +#include "nodeabstractproperty.h" #include "nodemetainfo.h" -#include "qmldesignerconstants.h" #include "qmldesignerplugin.h" #include "singlecollectionmodel.h" #include "variantproperty.h" @@ -21,326 +22,21 @@ #include namespace { -using Data = std::variant; -using DataRecord = QMap; - -struct DataHeader +inline bool isStudioCollectionModel(const QmlDesigner::ModelNode &node) { - enum class Type { Unknown, Bool, Numeric, String, DateTime }; - Type type; - QString name; -}; - -using DataHeaderMap = QMap; // Lowercase Name - Header Data - -inline constexpr QStringView BoolDataType{u"Bool"}; -inline constexpr QStringView NumberDataType{u"Number"}; -inline constexpr QStringView StringDataType{u"String"}; -inline constexpr QStringView DateTimeDataType{u"Date/Time"}; - -QString removeSpaces(QString string) -{ - string.replace(" ", "_"); - string.replace("-", "_"); - return string; -} - -DataHeader getDataType(const QString &type, const QString &name) -{ - static const QMap typeMap = { - {BoolDataType.toString().toLower(), DataHeader::Type::Bool}, - {NumberDataType.toString().toLower(), DataHeader::Type::Numeric}, - {StringDataType.toString().toLower(), DataHeader::Type::String}, - {DateTimeDataType.toString().toLower(), DataHeader::Type::DateTime}}; - if (name.isEmpty()) - return {}; - - if (type.isEmpty()) - return {DataHeader::Type::String, removeSpaces(name)}; - - return {typeMap.value(type.toLower(), DataHeader::Type::Unknown), removeSpaces(name)}; -} - -struct JsonDocumentError : public std::exception -{ - enum Error { - InvalidDocumentType, - InvalidCollectionName, - InvalidCollectionId, - InvalidCollectionObject, - InvalidArrayPosition, - InvalidLiteralType, - InvalidCollectionHeader, - IsNotJsonArray, - CollectionHeaderNotFound - }; - - const Error error; - - JsonDocumentError(Error error) - : error(error) - {} - - const char *what() const noexcept override - { - switch (error) { - case InvalidDocumentType: - return "Current JSON document contains errors."; - case InvalidCollectionName: - return "Invalid collection name."; - case InvalidCollectionId: - return "Invalid collection Id."; - case InvalidCollectionObject: - return "A collection should be a json object."; - case InvalidArrayPosition: - return "Arrays are not supported inside the collection."; - case InvalidLiteralType: - return "Invalid literal type for collection items"; - case InvalidCollectionHeader: - return "Invalid Collection Header"; - case IsNotJsonArray: - return "Json file should be an array"; - case CollectionHeaderNotFound: - return "Collection Header not found"; - default: - return "Unknown Json Error"; - } - } -}; - -struct CsvDocumentError : public std::exception -{ - enum Error { - HeaderNotFound, - DataNotFound, - }; - - const Error error; - - CsvDocumentError(Error error) - : error(error) - {} - - const char *what() const noexcept override - { - switch (error) { - case HeaderNotFound: - return "CSV Header not found"; - case DataNotFound: - return "CSV data not found"; - default: - return "Unknown CSV Error"; - } - } -}; - -Data getLiteralDataValue(const QVariant &value, const DataHeader &header, bool *typeWarningCheck = nullptr) -{ - if (header.type == DataHeader::Type::Bool) - return value.toBool(); - - if (header.type == DataHeader::Type::Numeric) - return value.toDouble(); - - if (header.type == DataHeader::Type::String) - return value.toString(); - - if (header.type == DataHeader::Type::DateTime) { - QDateTime dateTimeStr = QDateTime::fromString(value.toString()); - if (dateTimeStr.isValid()) - return dateTimeStr; - } - - if (typeWarningCheck) - *typeWarningCheck = true; - - return value.toString(); -} - -void loadJsonHeaders(QList &collectionHeaders, - DataHeaderMap &headerDataMap, - const QJsonObject &collectionJsonObject) -{ - const QJsonArray collectionHeader = collectionJsonObject.value("headers").toArray(); - for (const QJsonValue &headerValue : collectionHeader) { - const QJsonObject headerJsonObject = headerValue.toObject(); - DataHeader dataHeader = getDataType(headerJsonObject.value("type").toString(), - headerJsonObject.value("name").toString()); - - if (dataHeader.type == DataHeader::Type::Unknown) - throw JsonDocumentError{JsonDocumentError::InvalidCollectionHeader}; - - collectionHeaders.append(dataHeader); - headerDataMap.insert(dataHeader.name.toLower(), dataHeader); - } - - if (collectionHeaders.isEmpty()) - throw JsonDocumentError{JsonDocumentError::CollectionHeaderNotFound}; -} - -void loadJsonRecords(QList &collectionItems, - DataHeaderMap &headerDataMap, - const QJsonObject &collectionJsonObject) -{ - auto addItemFromValue = [&headerDataMap, &collectionItems](const QJsonValue &jsonValue) { - const QVariantMap dataMap = jsonValue.toObject().toVariantMap(); - DataRecord recordData; - for (const auto &dataPair : dataMap.asKeyValueRange()) { - const DataHeader correspondingHeader = headerDataMap.value(removeSpaces( - dataPair.first.toLower()), - {}); - - const QString &fieldName = correspondingHeader.name; - if (fieldName.size()) - recordData.insert(fieldName, - getLiteralDataValue(dataPair.second, correspondingHeader)); - } - if (!recordData.isEmpty()) - collectionItems.append(recordData); - }; - - const QJsonValue jsonDataValue = collectionJsonObject.value("data"); - if (jsonDataValue.isObject()) { - addItemFromValue(jsonDataValue); - } else if (jsonDataValue.isArray()) { - const QJsonArray jsonDataArray = jsonDataValue.toArray(); - for (const QJsonValue &jsonItem : jsonDataArray) { - if (jsonItem.isObject()) - addItemFromValue(jsonItem); - } - } -} - -inline bool isCollectionLib(const QmlDesigner::ModelNode &node) -{ - return node.parentProperty().parentModelNode().isRootNode() - && node.id() == QmlDesigner::Constants::COLLECTION_LIB_ID; -} - -inline bool isListModel(const QmlDesigner::ModelNode &node) -{ - return node.metaInfo().isQtQuickListModel(); -} - -inline bool isListElement(const QmlDesigner::ModelNode &node) -{ - return node.metaInfo().isQtQuickListElement(); -} - -inline bool isCollection(const QmlDesigner::ModelNode &node) -{ - return isCollectionLib(node.parentProperty().parentModelNode()) && isListModel(node); -} - -inline bool isCollectionElement(const QmlDesigner::ModelNode &node) -{ - return isListElement(node) && isCollection(node.parentProperty().parentModelNode()); + using namespace QmlDesigner::CollectionEditor; + return node.metaInfo().typeName() == JSONCOLLECTIONMODEL_TYPENAME + || node.metaInfo().typeName() == CSVCOLLECTIONMODEL_TYPENAME; } } // namespace namespace QmlDesigner { -struct Collection -{ - QString name; - QString id; - QList headers; - QList items; -}; - CollectionView::CollectionView(ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) {} -bool CollectionView::loadJson(const QByteArray &data) -{ - try { - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(data, &parseError); - if (parseError.error != QJsonParseError::NoError) - throw JsonDocumentError{JsonDocumentError::InvalidDocumentType}; - - QList collections; - if (document.isArray()) { - const QJsonArray collectionsJsonArray = document.array(); - - for (const QJsonValue &collectionJson : collectionsJsonArray) { - Collection collection; - if (!collectionJson.isObject()) - throw JsonDocumentError{JsonDocumentError::InvalidCollectionObject}; - - QJsonObject collectionJsonObject = collectionJson.toObject(); - - const QString &collectionName = collectionJsonObject.value(u"name").toString(); - if (!collectionName.size()) - throw JsonDocumentError{JsonDocumentError::InvalidCollectionName}; - - const QString &collectionId = collectionJsonObject.value(u"id").toString(); - if (!collectionId.size()) - throw JsonDocumentError{JsonDocumentError::InvalidCollectionId}; - - DataHeaderMap headerDataMap; - - loadJsonHeaders(collection.headers, headerDataMap, collectionJsonObject); - loadJsonRecords(collection.items, headerDataMap, collectionJsonObject); - - if (collection.items.count()) - collections.append(collection); - } - } else { - throw JsonDocumentError{JsonDocumentError::InvalidDocumentType}; - } - - addLoadedModel(collections); - } catch (const std::exception &error) { - m_widget->warn("Json Import Problem", QString::fromLatin1(error.what())); - return false; - } - - return true; -} - -bool CollectionView::loadCsv(const QString &collectionName, const QByteArray &data) -{ - QTextStream stream(data); - Collection collection; - collection.name = collectionName; - - try { - if (!stream.atEnd()) { - const QStringList recordData = stream.readLine().split(','); - for (const QString &name : recordData) - collection.headers.append(getDataType({}, name)); - } - if (collection.headers.isEmpty()) - throw CsvDocumentError{CsvDocumentError::HeaderNotFound}; - - while (!stream.atEnd()) { - const QStringList recordDataList = stream.readLine().split(','); - DataRecord recordData; - int column = -1; - for (const QString &cellData : recordDataList) { - if (++column == collection.headers.size()) - break; - recordData.insert(collection.headers.at(column).name, cellData); - } - if (recordData.count()) - collection.items.append(recordData); - } - - if (collection.items.isEmpty()) - throw CsvDocumentError{CsvDocumentError::DataNotFound}; - - addLoadedModel({collection}); - } catch (const std::exception &error) { - m_widget->warn("Json Import Problem", QString::fromLatin1(error.what())); - return false; - } - - return true; -} - bool CollectionView::hasWidget() const { return true; @@ -353,12 +49,14 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data()); Core::ICore::addContextObject(collectionEditorContext); - CollectionModel *collectionModel = m_widget->collectionModel().data(); + CollectionSourceModel *sourceModel = m_widget->sourceModel().data(); - connect(collectionModel, &CollectionModel::selectedIndexChanged, this, [&](int selectedIndex) { - m_widget->singleCollectionModel()->setCollection( - m_widget->collectionModel()->collectionNodeAt(selectedIndex)); - }); + connect(sourceModel, + &CollectionSourceModel::collectionSelected, + this, + [this](const ModelNode &sourceNode, const QString &collection) { + m_widget->singleCollectionModel()->loadCollection(sourceNode, collection); + }); } return createWidgetInfo(m_widget.data(), @@ -376,47 +74,31 @@ void CollectionView::modelAttached(Model *model) } void CollectionView::nodeReparented(const ModelNode &node, - const NodeAbstractProperty &newPropertyParent, - const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, [[maybe_unused]] PropertyChangeFlags propertyChange) { - if (!isListModel(node)) - return; - - ModelNode newParentNode = newPropertyParent.parentModelNode(); - ModelNode oldParentNode = oldPropertyParent.parentModelNode(); - bool added = isCollectionLib(newParentNode); - bool removed = isCollectionLib(oldParentNode); - - if (!added && !removed) + if (!isStudioCollectionModel(node)) return; refreshModel(); - if (isCollection(node)) - m_widget->collectionModel()->selectCollection(node); + m_widget->sourceModel()->selectSource(node); } void CollectionView::nodeAboutToBeRemoved(const ModelNode &removedNode) { // removing the collections lib node - if (isCollectionLib(removedNode)) { - m_widget->collectionModel()->setCollections({}); - return; - } - - if (isCollection(removedNode)) - m_widget->collectionModel()->removeCollection(removedNode); + if (isStudioCollectionModel(removedNode)) + m_widget->sourceModel()->removeSource(removedNode); } -void CollectionView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode, - const NodeAbstractProperty &parentProperty, +void CollectionView::nodeRemoved(const ModelNode &removedNode, + [[maybe_unused]] const NodeAbstractProperty &parentProperty, [[maybe_unused]] PropertyChangeFlags propertyChange) { - if (parentProperty.parentModelNode().id() != Constants::COLLECTION_LIB_ID) - return; - - m_widget->collectionModel()->updateSelectedCollection(true); + if (isStudioCollectionModel(removedNode)) + m_widget->sourceModel()->updateSelectedSource(true); } void CollectionView::variantPropertiesChanged(const QList &propertyList, @@ -424,11 +106,13 @@ void CollectionView::variantPropertiesChanged(const QList &prop { for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - if (isCollection(node)) { + if (isStudioCollectionModel(node)) { if (property.name() == "objectName") - m_widget->collectionModel()->updateNodeName(node); + m_widget->sourceModel()->updateNodeName(node); + else if (property.name() == CollectionEditor::SOURCEFILE_PROPERTY) + m_widget->sourceModel()->updateNodeSource(node); else if (property.name() == "id") - m_widget->collectionModel()->updateNodeId(node); + m_widget->sourceModel()->updateNodeId(node); } } } @@ -436,50 +120,36 @@ void CollectionView::variantPropertiesChanged(const QList &prop void CollectionView::selectedNodesChanged(const QList &selectedNodeList, [[maybe_unused]] const QList &lastSelectedNodeList) { - QList selectedCollections = Utils::filtered(selectedNodeList, &isCollection); + QList selectedJsonCollections = Utils::filtered(selectedNodeList, + &isStudioCollectionModel); // More than one collections are selected. So ignore them - if (selectedCollections.size() > 1) + if (selectedJsonCollections.size() > 1) return; - if (selectedCollections.size() == 1) { // If exactly one collection is selected - m_widget->collectionModel()->selectCollection(selectedCollections.first()); + if (selectedJsonCollections.size() == 1) { // If exactly one collection is selected + m_widget->sourceModel()->selectSource(selectedJsonCollections.first()); return; } - - // If no collection is selected, check the elements - QList selectedElements = Utils::filtered(selectedNodeList, &isCollectionElement); - if (selectedElements.size()) { - const ModelNode parentElement = selectedElements.first().parentProperty().parentModelNode(); - bool haveSameParent = Utils::allOf(selectedElements, [&parentElement](const ModelNode &element) { - return element.parentProperty().parentModelNode() == parentElement; - }); - if (haveSameParent) - m_widget->collectionModel()->selectCollection(parentElement); - } } -void CollectionView::addNewCollection(const QString &name) +void CollectionView::addResource(const QUrl &url, const QString &name, const QString &type) { - executeInTransaction(__FUNCTION__, [&] { - ensureCollectionLibraryNode(); - ModelNode collectionLib = collectionLibraryNode(); - if (!collectionLib.isValid()) - return; - - NodeMetaInfo listModelMetaInfo = model()->qtQmlModelsListModelMetaInfo(); - ModelNode collectionNode = createModelNode(listModelMetaInfo.typeName(), - listModelMetaInfo.majorVersion(), - listModelMetaInfo.minorVersion()); - QString collectionName = name.isEmpty() ? "Collection" : name; - renameCollection(collectionNode, collectionName); - - QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); - - auto headersProperty = collectionNode.variantProperty("headers"); - headersProperty.setDynamicTypeNameAndValue("string", {}); - - collectionLib.defaultNodeListProperty().reparentHere(collectionNode); + executeInTransaction(Q_FUNC_INFO, [this, &url, &name, &type]() { + ensureStudioModelImport(); + QString sourceAddress = url.isLocalFile() ? url.toLocalFile() : url.toString(); + const NodeMetaInfo resourceMetaInfo = type.compare("json", Qt::CaseInsensitive) == 0 + ? jsonCollectionMetaInfo() + : csvCollectionMetaInfo(); + ModelNode resourceNode = createModelNode(resourceMetaInfo.typeName(), + resourceMetaInfo.majorVersion(), + resourceMetaInfo.minorVersion()); + VariantProperty sourceProperty = resourceNode.variantProperty( + CollectionEditor::SOURCEFILE_PROPERTY); + VariantProperty nameProperty = resourceNode.variantProperty("objectName"); + sourceProperty.setValue(sourceAddress); + nameProperty.setValue(name); + rootModelNode().defaultNodeAbstractProperty().reparentHere(resourceNode); }); } @@ -488,118 +158,32 @@ void CollectionView::refreshModel() if (!model()) return; - ModelNode collectionLib = modelNodeForId(Constants::COLLECTION_LIB_ID); - ModelNodes collections; - - if (collectionLib.isValid()) { - const QList collectionLibNodes = collectionLib.directSubModelNodes(); - for (const ModelNode &node : collectionLibNodes) { - if (isCollection(node)) - collections.append(node); - } - } - - m_widget->collectionModel()->setCollections(collections); + // Load Json Collections + const ModelNodes jsonSourceNodes = rootModelNode().subModelNodesOfType(jsonCollectionMetaInfo()); + m_widget->sourceModel()->setSources(jsonSourceNodes); } -ModelNode CollectionView::getNewCollectionNode(const Collection &collection) +NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const { - QTC_ASSERT(model(), return {}); - ModelNode collectionNode; - executeInTransaction(__FUNCTION__, [&] { - NodeMetaInfo listModelMetaInfo = model()->qtQmlModelsListModelMetaInfo(); - collectionNode = createModelNode(listModelMetaInfo.typeName(), - listModelMetaInfo.majorVersion(), - listModelMetaInfo.minorVersion()); - QString collectionName = collection.name.isEmpty() ? "Collection" : collection.name; - renameCollection(collectionNode, collectionName); - QStringList headers; - for (const DataHeader &header : collection.headers) - headers.append(header.name); - - QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); - - auto headersProperty = collectionNode.variantProperty("headers"); - headersProperty.setDynamicTypeNameAndValue("string", headers.join(",")); - - NodeMetaInfo listElementMetaInfo = model()->qtQmlModelsListElementMetaInfo(); - for (const DataRecord &item : collection.items) { - ModelNode elementNode = createModelNode(listElementMetaInfo.typeName(), - listElementMetaInfo.majorVersion(), - listElementMetaInfo.minorVersion()); - for (const auto &headerMapElement : item.asKeyValueRange()) { - auto property = elementNode.variantProperty(headerMapElement.first.toLatin1()); - QVariant value = std::visit([](const auto &data) - -> QVariant { return QVariant::fromValue(data); }, - headerMapElement.second); - property.setValue(value); - } - collectionNode.defaultNodeListProperty().reparentHere(elementNode); - } - }); - return collectionNode; + return model()->metaInfo(CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME); } -void CollectionView::addLoadedModel(const QList &newCollection) +NodeMetaInfo CollectionView::csvCollectionMetaInfo() const +{ + return model()->metaInfo(CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME); +} + +void CollectionView::ensureStudioModelImport() { executeInTransaction(__FUNCTION__, [&] { - ensureCollectionLibraryNode(); - ModelNode collectionLib = collectionLibraryNode(); - if (!collectionLib.isValid()) - return; - - for (const Collection &collection : newCollection) { - ModelNode collectionNode = getNewCollectionNode(collection); - collectionLib.defaultNodeListProperty().reparentHere(collectionNode); + Import import = Import::createLibraryImport(CollectionEditor::COLLECTIONMODEL_IMPORT); + try { + if (!model()->hasImport(import, true, true)) + model()->changeImports({import}, {}); + } catch (const Exception &) { + QTC_ASSERT(false, return); } }); } -void CollectionView::renameCollection(ModelNode &collection, const QString &newName) -{ - QTC_ASSERT(collection.isValid(), return); - - QVariant objName = collection.variantProperty("objectName").value(); - if (objName.isValid() && objName.toString() == newName) - return; - - executeInTransaction(__FUNCTION__, [&] { - collection.setIdWithRefactoring(model()->generateIdFromName(newName, "collection")); - - VariantProperty objNameProp = collection.variantProperty("objectName"); - objNameProp.setValue(newName); - }); -} - -void CollectionView::ensureCollectionLibraryNode() -{ - ModelNode collectionLib = modelNodeForId(Constants::COLLECTION_LIB_ID); - if (collectionLib.isValid() - || (!rootModelNode().metaInfo().isQtQuick3DNode() - && !rootModelNode().metaInfo().isQtQuickItem())) { - return; - } - - executeInTransaction(__FUNCTION__, [&] { - // Create collection library node -#ifdef QDS_USE_PROJECTSTORAGE - TypeName nodeTypeName = rootModelNode().metaInfo().isQtQuick3DNode() ? "Node" : "Item"; - matLib = createModelNode(nodeTypeName, -1, -1); -#else - auto nodeType = rootModelNode().metaInfo().isQtQuick3DNode() - ? model()->qtQuick3DNodeMetaInfo() - : model()->qtQuickItemMetaInfo(); - collectionLib = createModelNode(nodeType.typeName(), - nodeType.majorVersion(), - nodeType.minorVersion()); -#endif - collectionLib.setIdWithoutRefactoring(Constants::COLLECTION_LIB_ID); - rootModelNode().defaultNodeListProperty().reparentHere(collectionLib); - }); -} - -ModelNode CollectionView::collectionLibraryNode() -{ - return modelNodeForId(Constants::COLLECTION_LIB_ID); -} } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h index 9372c74150f..6d81d00f064 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h @@ -1,5 +1,6 @@ // 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 "abstractview.h" @@ -9,7 +10,6 @@ namespace QmlDesigner { -struct Collection; class CollectionWidget; class CollectionView : public AbstractView @@ -19,9 +19,6 @@ class CollectionView : public AbstractView public: explicit CollectionView(ExternalDependenciesInterface &externalDependencies); - bool loadJson(const QByteArray &data); - bool loadCsv(const QString &collectionName, const QByteArray &data); - bool hasWidget() const override; WidgetInfo widgetInfo() override; @@ -44,15 +41,13 @@ public: void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; - void addNewCollection(const QString &name); + void addResource(const QUrl &url, const QString &name, const QString &type); private: void refreshModel(); - ModelNode getNewCollectionNode(const Collection &collection); - void addLoadedModel(const QList &newCollection); - void renameCollection(ModelNode &material, const QString &newName); - void ensureCollectionLibraryNode(); - ModelNode collectionLibraryNode(); + NodeMetaInfo jsonCollectionMetaInfo() const; + NodeMetaInfo csvCollectionMetaInfo() const; + void ensureStudioModelImport(); QPointer m_widget; }; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index 7e65516143c..c4d9631cefe 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "collectionwidget.h" -#include "collectionmodel.h" +#include "collectionsourcemodel.h" #include "collectionview.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,7 @@ namespace QmlDesigner { CollectionWidget::CollectionWidget(CollectionView *view) : QFrame() , m_view(view) - , m_model(new CollectionModel) + , m_sourceModel(new CollectionSourceModel) , m_singleCollectionModel(new SingleCollectionModel) , m_quickWidget(new StudioQuickWidget(this)) { @@ -65,7 +66,7 @@ CollectionWidget::CollectionWidget(CollectionView *view) auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); map->setProperties( {{"rootView", QVariant::fromValue(this)}, - {"model", QVariant::fromValue(m_model.data())}, + {"model", QVariant::fromValue(m_sourceModel.data())}, {"singleCollectionModel", QVariant::fromValue(m_singleCollectionModel.data())}}); auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); @@ -82,9 +83,9 @@ void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback) callback({}); } -QPointer CollectionWidget::collectionModel() const +QPointer CollectionWidget::sourceModel() const { - return m_model; + return m_sourceModel; } QPointer CollectionWidget::singleCollectionModel() const @@ -103,26 +104,23 @@ void CollectionWidget::reloadQmlSource() bool CollectionWidget::loadJsonFile(const QString &jsonFileAddress) { - QUrl jsonUrl(jsonFileAddress); - QString fileAddress = jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString(); - QFile file(fileAddress); - if (file.open(QFile::ReadOnly)) - return m_view->loadJson(file.readAll()); + if (!isJsonFile(jsonFileAddress)) + return false; - warn("Unable to open the file", file.errorString()); - return false; + QUrl jsonUrl(jsonFileAddress); + QFileInfo fileInfo(jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString()); + + m_view->addResource(jsonUrl, fileInfo.completeBaseName(), "json"); + + return true; } bool CollectionWidget::loadCsvFile(const QString &collectionName, const QString &csvFileAddress) { QUrl csvUrl(csvFileAddress); - QString fileAddress = csvUrl.isLocalFile() ? csvUrl.toLocalFile() : csvUrl.toString(); - QFile file(fileAddress); - if (file.open(QFile::ReadOnly)) - return m_view->loadCsv(collectionName, file.readAll()); + m_view->addResource(csvUrl, collectionName, "csv"); - warn("Unable to open the file", file.errorString()); - return false; + return true; } bool CollectionWidget::isJsonFile(const QString &jsonFileAddress) const @@ -155,10 +153,10 @@ bool CollectionWidget::isCsvFile(const QString &csvFileAddress) const return true; } -bool CollectionWidget::addCollection(const QString &collectionName) const +bool CollectionWidget::addCollection([[maybe_unused]] const QString &collectionName) const { - m_view->addNewCollection(collectionName); - return true; + // TODO + return false; } void CollectionWidget::warn(const QString &title, const QString &body) diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index e76237d0c14..de2b4d8d9fc 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -1,5 +1,6 @@ // 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 @@ -10,7 +11,7 @@ class StudioQuickWidget; namespace QmlDesigner { -class CollectionModel; +class CollectionSourceModel; class CollectionView; class SingleCollectionModel; @@ -22,7 +23,7 @@ public: CollectionWidget(CollectionView *view); void contextHelp(const Core::IContext::HelpCallback &callback) const; - QPointer collectionModel() const; + QPointer sourceModel() const; QPointer singleCollectionModel() const; void reloadQmlSource(); @@ -37,7 +38,7 @@ public: private: QPointer m_view; - QPointer m_model; + QPointer m_sourceModel; QPointer m_singleCollectionModel; QScopedPointer m_quickWidget; }; diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp index 040f9307b67..62a95474a55 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp @@ -3,31 +3,36 @@ #include "singlecollectionmodel.h" -#include "nodemetainfo.h" +#include "collectioneditorconstants.h" +#include "modelnode.h" #include "variantproperty.h" #include -namespace { -inline bool isListElement(const QmlDesigner::ModelNode &node) -{ - return node.metaInfo().isQtQuickListElement(); -} +#include +#include +#include -inline QByteArrayList getHeaders(const QByteArray &headersValue) +namespace { + +QStringList getJsonHeaders(const QJsonArray &collectionArray) { - QByteArrayList result; - const QByteArrayList initialHeaders = headersValue.split(','); - for (QByteArray header : initialHeaders) { - header = header.trimmed(); - if (header.size()) - result.append(header); + QSet result; + for (const QJsonValue &value : collectionArray) { + if (value.isObject()) { + const QJsonObject object = value.toObject(); + const QStringList headers = object.toVariantMap().keys(); + for (const QString &header : headers) + result.insert(header); + } } - return result; + + return result.values(); } } // namespace namespace QmlDesigner { + SingleCollectionModel::SingleCollectionModel(QObject *parent) : QAbstractTableModel(parent) {} @@ -47,11 +52,11 @@ QVariant SingleCollectionModel::data(const QModelIndex &index, int) const if (!index.isValid()) return {}; - const QByteArray &propertyName = m_headers.at(index.column()); - const ModelNode &elementNode = m_elements.at(index.row()); + const QString &propertyName = m_headers.at(index.column()); + const QJsonObject &elementNode = m_elements.at(index.row()); - if (elementNode.hasVariantProperty(propertyName)) - return elementNode.variantProperty(propertyName).value(); + if (elementNode.contains(propertyName)) + return elementNode.value(propertyName).toVariant(); return {}; } @@ -79,32 +84,110 @@ QVariant SingleCollectionModel::headerData(int section, return {}; } -void SingleCollectionModel::setCollection(const ModelNode &collection) +void SingleCollectionModel::loadCollection(const ModelNode &sourceNode, const QString &collection) +{ + QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); + + if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) + loadJsonCollection(fileName, collection); + else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) + loadCsvCollection(fileName, collection); +} + +void SingleCollectionModel::loadJsonCollection(const QString &source, const QString &collection) { beginResetModel(); - m_collectionNode = collection; - updateCollectionName(); + setCollectionName(collection); + QFile sourceFile(source); + QJsonArray collectionNodes; + bool jsonFileIsOk = false; + if (sourceFile.open(QFile::ReadOnly)) { + QJsonParseError jpe; + QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); + if (jpe.error == QJsonParseError::NoError) { + jsonFileIsOk = true; + if (document.isObject()) { + QJsonObject collectionMap = document.object(); + if (collectionMap.contains(collection)) { + QJsonValue collectionVal = collectionMap.value(collection); + if (collectionVal.isArray()) + collectionNodes = collectionVal.toArray(); + else + collectionNodes.append(collectionVal); + } + } + } + } - QTC_ASSERT(collection.isValid() && collection.hasVariantProperty("headers"), { + setCollectionSourceFormat(jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown); + + if (collectionNodes.isEmpty()) { m_headers.clear(); m_elements.clear(); endResetModel(); return; - }); + } + + m_headers = getJsonHeaders(collectionNodes); + + m_elements.clear(); + for (const QJsonValue &value : std::as_const(collectionNodes)) { + if (value.isObject()) { + QJsonObject object = value.toObject(); + m_elements.append(object); + } + } - m_headers = getHeaders(collection.variantProperty("headers").value().toByteArray()); - m_elements = Utils::filtered(collection.allSubModelNodes(), &isListElement); endResetModel(); } -void SingleCollectionModel::updateCollectionName() +void SingleCollectionModel::loadCsvCollection(const QString &source, const QString &collectionName) +{ + beginResetModel(); + + setCollectionName(collectionName); + QFile sourceFile(source); + m_headers.clear(); + m_elements.clear(); + bool csvFileIsOk = false; + + if (sourceFile.open(QFile::ReadOnly)) { + QTextStream stream(&sourceFile); + + if (!stream.atEnd()) + m_headers = stream.readLine().split(','); + + if (!m_headers.isEmpty()) { + while (!stream.atEnd()) { + const QStringList recordDataList = stream.readLine().split(','); + int column = -1; + QJsonObject recordData; + for (const QString &cellData : recordDataList) { + if (++column == m_headers.size()) + break; + recordData.insert(m_headers.at(column), cellData); + } + if (recordData.count()) + m_elements.append(recordData); + } + csvFileIsOk = true; + } + } + + setCollectionSourceFormat(csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown); + endResetModel(); +} + +void SingleCollectionModel::setCollectionName(const QString &newCollectionName) { - QString newCollectionName = m_collectionNode.isValid() - ? m_collectionNode.variantProperty("objectName").value().toString() - : ""; if (m_collectionName != newCollectionName) { m_collectionName = newCollectionName; emit this->collectionNameChanged(m_collectionName); } } + +void SingleCollectionModel::setCollectionSourceFormat(SourceFormat sourceFormat) +{ + m_sourceFormat = sourceFormat; +} } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h index 8d2c6c41b4f..471e43b9674 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h @@ -1,16 +1,15 @@ // 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 "modelnode.h" - #include - -QT_BEGIN_NAMESPACE -class QJsonArray; -QT_END_NAMESPACE +#include namespace QmlDesigner { + +class ModelNode; + class SingleCollectionModel : public QAbstractTableModel { Q_OBJECT @@ -18,6 +17,7 @@ class SingleCollectionModel : public QAbstractTableModel Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged) public: + enum class SourceFormat { Unknown, Json, Csv }; explicit SingleCollectionModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; @@ -29,18 +29,21 @@ public: Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - void setCollection(const ModelNode &collection); + void loadCollection(const ModelNode &sourceNode, const QString &collection); signals: void collectionNameChanged(const QString &collectionName); private: - void updateCollectionName(); + void setCollectionName(const QString &newCollectionName); + void setCollectionSourceFormat(SourceFormat sourceFormat); + void loadJsonCollection(const QString &source, const QString &collection); + void loadCsvCollection(const QString &source, const QString &collectionName); - QByteArrayList m_headers; - ModelNodes m_elements; - ModelNode m_collectionNode; + QStringList m_headers; + QList m_elements; QString m_collectionName; + SourceFormat m_sourceFormat = SourceFormat::Unknown; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 228934b2f83..4f800bf939f 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -23,10 +23,11 @@ #include -#include -#include -#include #include +#include +#include +#include +#include #include #include @@ -419,7 +420,7 @@ public: parentNode = selectionContext().currentSingleSelectedNode().parentProperty().parentModelNode(); - if (!ModelNode::isThisOrAncestorLocked(parentNode)) { + if (!ModelUtils::isThisOrAncestorLocked(parentNode)) { ActionTemplate *selectionAction = new ActionTemplate("SELECTION", {}, &ModelNodeOperations::select); selectionAction->setParent(menu()); selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Parent"))); @@ -432,11 +433,9 @@ public: } } for (const ModelNode &node : selectionContext().view()->allModelNodes()) { - if (node != selectionContext().currentSingleSelectedNode() - && node != parentNode - && contains(node, selectionContext().scenePosition()) - && !node.isRootNode() - && !ModelNode::isThisOrAncestorLocked(node)) { + if (node != selectionContext().currentSingleSelectedNode() && node != parentNode + && contains(node, selectionContext().scenePosition()) && !node.isRootNode() + && !ModelUtils::isThisOrAncestorLocked(node)) { selectionContext().setTargetNode(node); QString what = QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select: %1")).arg(captionForModelNode(node)); ActionTemplate *selectionAction = new ActionTemplate("SELECT", what, &ModelNodeOperations::select); @@ -741,19 +740,7 @@ public: activeSignalHandlerGroup->addMenu(editSlotGroup); } - ActionTemplate *openEditorAction = new ActionTemplate( - (propertyName + "OpenEditorId").toLatin1(), - QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), - [=](const SelectionContext &) { - signalHandler.parentModelNode().view()->executeInTransaction( - "ConnectionsModelNodeActionGroup::" - "openConnectionsEditor", - [signalHandler]() { - ActionEditor::invokeEditor(signalHandler, removeSignal); - }); - }); - - activeSignalHandlerGroup->addAction(openEditorAction); + //add an action to open Connection Form from here: ActionTemplate *removeSignalHandlerAction = new ActionTemplate( (propertyName + "RemoveSignalHandlerId").toLatin1(), @@ -822,25 +809,7 @@ public: } } - ActionTemplate *openEditorAction = new ActionTemplate( - (signalStr + "OpenEditorId").toLatin1(), - QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), - [=](const SelectionContext &) { - currentNode.view()->executeInTransaction( - "ConnectionsModelNodeActionGroup::" - "openConnectionsEditor", - [=]() { - ModelNode newConnectionNode = createNewConnection(currentNode); - - SignalHandlerProperty newHandler = newConnectionNode.signalHandlerProperty( - prependSignal(signalStr).toLatin1()); - - newHandler.setSource( - QString("console.log(\"%1.%2\")").arg(currentNode.id(), signalStr)); - ActionEditor::invokeEditor(newHandler, removeSignal, true); - }); - }); - newSignal->addAction(openEditorAction); + //add an action to open Connection Form from here addConnection->addMenu(newSignal); } @@ -1270,9 +1239,9 @@ bool isPositioner(const SelectionContext &context) bool layoutOptionVisible(const SelectionContext &context) { - return selectionCanBeLayoutedAndQtQuickLayoutPossible(context) - || singleSelectionAndInQtQuickLayout(context) - || isLayout(context); + return (selectionCanBeLayoutedAndQtQuickLayoutPossible(context) + || singleSelectionAndInQtQuickLayout(context) || isLayout(context)) + && !DesignerMcuManager::instance().isMCUProject(); } bool positionOptionVisible(const SelectionContext &context) @@ -1840,29 +1809,31 @@ void DesignerActionManager::createDefaultDesignerActions() &isStackedContainerAndIndexCanBeIncreased, &isStackedContainer)); - addDesignerAction(new ModelNodeAction( - layoutRowLayoutCommandId, - layoutRowLayoutDisplayName, - Utils::Icon({{":/qmldesigner/icon/designeractions/images/row.png", - Utils::Theme::IconsBaseColor}}).icon(), - layoutRowLayoutToolTip, - layoutCategory, - QKeySequence("Ctrl+u"), - 2, - &layoutRowLayout, - &selectionCanBeLayoutedAndQtQuickLayoutPossible)); + addDesignerAction( + new ModelNodeAction(layoutRowLayoutCommandId, + layoutRowLayoutDisplayName, + Utils::Icon({{":/qmldesigner/icon/designeractions/images/row.png", + Utils::Theme::IconsBaseColor}}) + .icon(), + layoutRowLayoutToolTip, + layoutCategory, + QKeySequence("Ctrl+u"), + 2, + &layoutRowLayout, + &selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU)); - addDesignerAction(new ModelNodeAction( - layoutColumnLayoutCommandId, - layoutColumnLayoutDisplayName, - Utils::Icon({{":/qmldesigner/icon/designeractions/images/column.png", - Utils::Theme::IconsBaseColor}}).icon(), - layoutColumnLayoutToolTip, - layoutCategory, - QKeySequence("Ctrl+l"), - 3, - &layoutColumnLayout, - &selectionCanBeLayoutedAndQtQuickLayoutPossible)); + addDesignerAction( + new ModelNodeAction(layoutColumnLayoutCommandId, + layoutColumnLayoutDisplayName, + Utils::Icon({{":/qmldesigner/icon/designeractions/images/column.png", + Utils::Theme::IconsBaseColor}}) + .icon(), + layoutColumnLayoutToolTip, + layoutCategory, + QKeySequence("Ctrl+l"), + 3, + &layoutColumnLayout, + &selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU)); addDesignerAction(new ModelNodeAction( layoutGridLayoutCommandId, @@ -1937,9 +1908,7 @@ void DesignerActionManager::createDefaultDesignerActions() &addMouseAreaFillCheck, &singleSelection)); - const bool standaloneMode = QmlProjectManager::QmlProject::isQtDesignStudio(); - - if (!standaloneMode) { + if (!Core::ICore::isQtDesignStudio()) { addDesignerAction(new ModelNodeContextMenuAction(goToImplementationCommandId, goToImplementationDisplayName, {}, diff --git a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp index 89a5868d89d..9c6ef49a05a 100644 --- a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp +++ b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp @@ -196,7 +196,7 @@ void LayoutInGridLayout::doIt() static bool hasQtQuickLayoutImport(const SelectionContext &context) { if (context.view() && context.view()->model()) { - Import import = Import::createLibraryImport(QStringLiteral("QtQuick.Layouts"), QStringLiteral("1.0")); + Import import = Import::createLibraryImport(QStringLiteral("QtQuick.Layouts"), {}); return context.view()->model()->hasImport(import, true, true); } @@ -206,7 +206,7 @@ static bool hasQtQuickLayoutImport(const SelectionContext &context) void LayoutInGridLayout::ensureLayoutImport(const SelectionContext &context) { if (!hasQtQuickLayoutImport(context)) { - Import layoutImport = Import::createLibraryImport("QtQuick.Layouts", "1.0"); + Import layoutImport = Import::createLibraryImport("QtQuick.Layouts", {}); context.view()-> model()->changeImports({layoutImport}, {}); } } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 176f4d7bc78..9aac781e8ba 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -521,13 +521,8 @@ void layoutFlowPositioner(const SelectionContext &selectionContext) void layoutRowLayout(const SelectionContext &selectionContext) { try { - if (DesignerMcuManager::instance().isMCUProject()) { - layoutHelperFunction(selectionContext, "QtQuick.Row", compareByX); - } - else { - LayoutInGridLayout::ensureLayoutImport(selectionContext); - layoutHelperFunction(selectionContext, "QtQuick.Layouts.RowLayout", compareByX); - } + LayoutInGridLayout::ensureLayoutImport(selectionContext); + layoutHelperFunction(selectionContext, "QtQuick.Layouts.RowLayout", compareByX); } catch (RewritingException &e) { //better safe than sorry e.showException(); } @@ -536,13 +531,8 @@ void layoutRowLayout(const SelectionContext &selectionContext) void layoutColumnLayout(const SelectionContext &selectionContext) { try { - if (DesignerMcuManager::instance().isMCUProject()) { - layoutHelperFunction(selectionContext, "QtQuick.Column", compareByX); - } - else { - LayoutInGridLayout::ensureLayoutImport(selectionContext); - layoutHelperFunction(selectionContext, "QtQuick.Layouts.ColumnLayout", compareByY); - } + LayoutInGridLayout::ensureLayoutImport(selectionContext); + layoutHelperFunction(selectionContext, "QtQuick.Layouts.ColumnLayout", compareByY); } catch (RewritingException &e) { //better safe than sorry e.showException(); } @@ -553,13 +543,8 @@ void layoutGridLayout(const SelectionContext &selectionContext) try { Q_ASSERT(!DesignerMcuManager::instance().isMCUProject()); //remove this line when grids are finally supported - if (DesignerMcuManager::instance().isMCUProject()) { - //qt for mcu doesn't support any grids yet - } - else { - LayoutInGridLayout::ensureLayoutImport(selectionContext); - LayoutInGridLayout::layout(selectionContext); - } + LayoutInGridLayout::ensureLayoutImport(selectionContext); + LayoutInGridLayout::layout(selectionContext); } catch (RewritingException &e) { //better safe than sorry e.showException(); } diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp new file mode 100644 index 00000000000..8cc84058d20 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp @@ -0,0 +1,324 @@ +// 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 "propertycomponentgenerator.h" + +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace Qt::StringLiterals; + +namespace QmlDesigner { + +namespace { + +QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath) +{ + QmlJS::SimpleReader reader; + const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml"; + auto templateConfiguration = reader.readFile(fileName); + + if (!templateConfiguration) + qWarning().nospace() << "template definitions:" << reader.errors(); + + return templateConfiguration; +} + +template +Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name) +{ + if (auto property = node->property(name)) { + const auto &value = property.value; + if (value.type() == QVariant::List) { + auto list = value.toList(); + if (list.size()) + return list.front().value(); + } else { + return property.value.value(); + } + } + + return {}; +} + +QString getContent(const QString &path) +{ + QFile file{path}; + + if (file.open(QIODevice::ReadOnly)) + return QString::fromUtf8(file.readAll()); + + return {}; +} + +QString generateComponentText(Utils::SmallStringView propertyName, + QStringView source, + Utils::SmallStringView typeName, + bool needsTypeArg) +{ + QString underscoreName{propertyName}; + underscoreName.replace('.', '_'); + if (needsTypeArg) + return source.arg(QString{propertyName}, underscoreName, QString{typeName}); + + return source.arg(QString{propertyName}, underscoreName); +} + +PropertyComponentGenerator::Property generate(const PropertyMetaInfo &property, + const PropertyComponentGenerator::Entry &entry) +{ + auto propertyName = property.name(); + auto component = generateComponentText(propertyName, + entry.propertyTemplate, + entry.typeName, + entry.needsTypeArg); + + if (entry.separateSection) + return PropertyComponentGenerator::ComplexProperty{propertyName, component}; + + return PropertyComponentGenerator::BasicProperty{propertyName, component}; +} + +auto getRandomExportedName(const NodeMetaInfo &metaInfo) +{ + const auto &names = metaInfo.allExportedTypeNames(); + + using Result = decltype(names.front().name); + + if (!names.empty()) + return names.front().name; + + return Result{}; +} + +} // namespace + +QString PropertyComponentGenerator::generateSubComponentText(Utils::SmallStringView propertyBaseName, + const PropertyMetaInfo &subProperty) const +{ + auto propertyType = subProperty.propertyType(); + if (auto entry = findEntry(propertyType)) { + auto propertyName = Utils::SmallString::join({propertyBaseName, subProperty.name()}); + return generateComponentText(propertyName, + entry->propertyTemplate, + entry->typeName, + entry->needsTypeArg); + } + + return {}; +} + +QString PropertyComponentGenerator::generateComplexComponentText(Utils::SmallStringView propertyName, + const NodeMetaInfo &propertyType) const +{ + auto subProperties = propertyType.properties(); + + if (std::empty(subProperties)) + return {}; + + auto propertyTypeName = getRandomExportedName(propertyType); + static QString templateText = QStringLiteral( + R"xy( + Section { + caption: %1 - %2 + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 8 + rightPadding: 0 + expanded: false + level: 1 + SectionLayout { + )xy"); + + auto component = templateText.arg(QString{propertyName}, QString{propertyTypeName}); + + auto propertyBaseName = Utils::SmallString::join({propertyName, "."}); + + bool subPropertiesHaveContent = false; + for (const auto &subProperty : propertyType.properties()) { + auto subPropertyContent = generateSubComponentText(propertyBaseName, subProperty); + subPropertiesHaveContent = subPropertiesHaveContent || subPropertyContent.size(); + component += subPropertyContent; + } + + component += "}\n}\n"_L1; + + if (subPropertiesHaveContent) + return component; + + return {}; +} + +PropertyComponentGenerator::Property PropertyComponentGenerator::generateComplexComponent( + const PropertyMetaInfo &property, const NodeMetaInfo &propertyType) const +{ + auto propertyName = property.name(); + auto component = generateComplexComponentText(propertyName, propertyType); + + if (component.isEmpty()) + return {}; + + return ComplexProperty{propertyName, component}; +} + +namespace { + +std::optional createEntry(QmlJS::SimpleReaderNode *node, + Model *model, + const QString &templatesPath) +{ + auto moduleName = getProperty(node, "module"); + if (moduleName.isEmpty()) + return {}; + + auto module = model->module(moduleName); + + auto typeName = getProperty(node, "typeNames"); + + auto type = model->metaInfo(module, typeName); + if (!type) + return {}; + + auto path = getProperty(node, "sourceFile"); + if (path.isEmpty()) + return {}; + + auto content = getContent(templatesPath + path); + if (content.isEmpty()) + return {}; + + bool needsTypeArg = getProperty(node, "needsTypeArg"); + + bool separateSection = getProperty(node, "separateSection"); + + return PropertyComponentGenerator::Entry{std::move(type), + std::move(typeName), + std::move(content), + separateSection, + needsTypeArg}; +} + +std::tuple createEntries( + QmlJS::SimpleReaderNode::Ptr templateConfiguration, Model *model, const QString &templatesPath) +{ + bool hasInvalidTemplates = false; + PropertyComponentGenerator::Entries entries; + entries.reserve(32); + + const auto &nodes = templateConfiguration->children(); + for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) { + if (auto entry = createEntry(node.get(), model, templatesPath)) + entries.push_back(*entry); + else + hasInvalidTemplates = true; + } + + return {entries, hasInvalidTemplates}; +} + +QStringList createImports(QmlJS::SimpleReaderNode *templateConfiguration) +{ + auto property = templateConfiguration->property("imports"); + return Utils::transform(property.value.toList(), + [](const auto &entry) { return entry.toString(); }); +} + +} // namespace + +PropertyComponentGenerator::PropertyComponentGenerator(const QString &propertyEditorResourcesPath, + Model *model) + : m_templateConfiguration{createTemplateConfiguration(propertyEditorResourcesPath)} + , m_propertyTemplatesPath{propertyEditorResourcesPath + "/PropertyTemplates/"} +{ + setModel(model); + m_imports = createImports(m_templateConfiguration.get()); +} + +PropertyComponentGenerator::Property PropertyComponentGenerator::create(const PropertyMetaInfo &property) const +{ + auto propertyType = property.propertyType(); + if (auto entry = findEntry(propertyType)) + return generate(property, *entry); + + if (property.isWritable() && property.isPointer()) + return {}; + + return generateComplexComponent(property, propertyType); +} + +void PropertyComponentGenerator::setModel(Model *model) +{ + if (model && m_model && m_model->projectStorage() == model->projectStorage()) { + m_model = model; + return; + } + + if (model) { + setEntries(m_templateConfiguration, model, m_propertyTemplatesPath); + } else { + m_entries.clear(); + m_entryTypeIds.clear(); + } + m_model = model; +} + +namespace { + +bool insect(const TypeIds &first, const TypeIds &second) +{ + bool intersecting = false; + + std::set_intersection(first.begin(), + first.end(), + second.begin(), + second.end(), + Utils::make_iterator([&](const auto &) { intersecting = true; })); + + return intersecting; +} + +} // namespace + +void PropertyComponentGenerator::setEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration, + Model *model, + const QString &propertyTemplatesPath) +{ + auto [entries, hasInvalidTemplates] = createEntries(templateConfiguration, + model, + propertyTemplatesPath); + m_entries = std::move(entries); + m_hasInvalidTemplates = hasInvalidTemplates; + m_entryTypeIds = Utils::transform(m_entries, + [](const auto &entry) { return entry.type.id(); }); + std::sort(m_entryTypeIds.begin(), m_entryTypeIds.end()); +} + +void PropertyComponentGenerator::refreshMetaInfos(const TypeIds &deletedTypeIds) +{ + if (!insect(deletedTypeIds, m_entryTypeIds) && !m_hasInvalidTemplates) + return; + + setEntries(m_templateConfiguration, m_model, m_propertyTemplatesPath); +} + +const PropertyComponentGenerator::Entry *PropertyComponentGenerator::findEntry(const NodeMetaInfo &type) const +{ + auto found = std::find_if(m_entries.begin(), m_entries.end(), [&](const auto &entry) { + return entry.type == type; + }); + + if (found != m_entries.end()) + return std::addressof(*found); + + return nullptr; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h new file mode 100644 index 00000000000..dfa5f30db2d --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h @@ -0,0 +1,68 @@ +// 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 "propertycomponentgeneratorinterface.h" + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace QmlDesigner { + +class PropertyComponentGenerator final : public PropertyComponentGeneratorInterface +{ +public: + PropertyComponentGenerator(const QString &propertyEditorResourcesPath, Model *model); + + Property create(const PropertyMetaInfo &property) const override; + + struct Entry + { + NodeMetaInfo type; + Utils::SmallString typeName; + QString propertyTemplate; + bool separateSection = false; + bool needsTypeArg = false; + }; + + using Entries = std::vector; + + QStringList imports() const override { return m_imports; } + + void setModel(Model *model); + + void refreshMetaInfos(const TypeIds &deletedTypeIds); + +private: + const Entry *findEntry(const NodeMetaInfo &type) const; + QString generateSubComponentText(Utils::SmallStringView propertyBaseName, + const PropertyMetaInfo &subProperty) const; + QString generateComplexComponentText(Utils::SmallStringView propertyName, + const NodeMetaInfo &propertyType) const; + Property generateComplexComponent(const PropertyMetaInfo &property, + const NodeMetaInfo &propertyType) const; + + void setEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration, + Model *model, + const QString &propertyTemplatesPath); + +private: + Entries m_entries; + TypeIds m_entryTypeIds; + QStringList m_imports; + QPointer m_model; + QmlJS::SimpleReaderNode::Ptr m_templateConfiguration; + QString m_propertyTemplatesPath; + bool m_hasInvalidTemplates = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h new file mode 100644 index 00000000000..ab4047c957b --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h @@ -0,0 +1,46 @@ +// 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 +#include +#include + +namespace QmlDesigner { + +class PropertyComponentGeneratorInterface +{ +public: + PropertyComponentGeneratorInterface() = default; + PropertyComponentGeneratorInterface(const PropertyComponentGeneratorInterface &) = delete; + PropertyComponentGeneratorInterface &operator=(const PropertyComponentGeneratorInterface &) = delete; + + struct BasicProperty + { + Utils::SmallString propertyName; + QString component; + }; + + struct ComplexProperty + { + Utils::SmallString propertyName; + QString component; + }; + + using Property = std::variant; + + virtual Property create(const PropertyMetaInfo &property) const = 0; + + virtual QStringList imports() const = 0; + +protected: + ~PropertyComponentGeneratorInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp new file mode 100644 index 00000000000..9b066d5fec0 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp @@ -0,0 +1,175 @@ +// 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 "propertyeditorcomponentgenerator.h" + +#include +#include + +#include + +namespace QmlDesigner { + +namespace { + +using GeneratorProperty = PropertyComponentGeneratorInterface::Property; +using BasicProperty = PropertyComponentGeneratorInterface::BasicProperty; +using ComplexProperty = PropertyComponentGeneratorInterface::ComplexProperty; +using GeneratorProperties = std::vector; + +QString createImports(const QStringList &imports) +{ + QString importsContent; + importsContent.reserve(512); + + for (const QString &import : imports) { + importsContent += import; + importsContent += '\n'; + } + + return importsContent; +} + +QString componentButton(bool isComponent) +{ + if (isComponent) + return "ComponentButton {}"; + + return {}; +} + +void createBasicPropertySections(QString &components, + Utils::span generatorProperties) +{ + components += R"xy( + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout {)xy"; + + for (const auto &generatorProperty : generatorProperties) + components += std::get_if(&generatorProperty)->component; + + components += R"xy( + } + })xy"; +} + +void createComplexPropertySections(QString &components, + Utils::span generatorProperties) +{ + for (const auto &generatorProperty : generatorProperties) + components += std::get_if(&generatorProperty)->component; +} + +Utils::SmallStringView propertyName(const GeneratorProperty &property) +{ + return std::visit( + [](const auto &p) -> Utils::SmallStringView { + if constexpr (!std::is_same_v, std::monostate>) + return p.propertyName; + else + return {}; + }, + property); +} + +PropertyMetaInfos getUnmangedProperties(const NodeMetaInfos &prototypes) +{ + PropertyMetaInfos properties; + properties.reserve(128); + + for (const auto &prototype : prototypes) { + if (prototype.propertyEditorPathId()) + break; + + auto localProperties = prototype.localProperties(); + + properties.insert(properties.end(), localProperties.begin(), localProperties.end()); + } + + return properties; +} + +GeneratorProperties createSortedGeneratorProperties( + const PropertyMetaInfos &properties, const PropertyComponentGeneratorType &propertyGenerator) +{ + GeneratorProperties generatorProperties; + generatorProperties.reserve(properties.size()); + + for (const auto &property : properties) { + auto generatedProperty = propertyGenerator.create(property); + if (!std::holds_alternative(generatedProperty)) + generatorProperties.push_back(std::move(generatedProperty)); + } + std::sort(generatorProperties.begin(), + generatorProperties.end(), + [](const auto &first, const auto &second) { + // this is sensitive to the order of the variant types but the test should catch any error + return std::forward_as_tuple(first.index(), propertyName(first)) + < std::forward_as_tuple(second.index(), propertyName(second)); + }); + + return generatorProperties; +} + +QString createPropertySections(const PropertyComponentGeneratorType &propertyGenerator, + const NodeMetaInfos &prototypeChain) +{ + QString propertyComponents; + propertyComponents.reserve(100000); + + auto generatorProperties = createSortedGeneratorProperties(getUnmangedProperties(prototypeChain), + propertyGenerator); + + const auto begin = generatorProperties.begin(); + const auto end = generatorProperties.end(); + + auto point = std::partition_point(begin, end, [](const auto &p) { + return !std::holds_alternative(p); + }); + + if (begin != point) + createBasicPropertySections(propertyComponents, Utils::span{begin, point}); + + if (point != end) + createComplexPropertySections(propertyComponents, Utils::span{point, end}); + + return propertyComponents; +} + +} // namespace + +PropertyEditorComponentGenerator::PropertyEditorComponentGenerator( + const PropertyComponentGeneratorType &propertyGenerator) + : m_propertyGenerator{propertyGenerator} +{} + +QString PropertyEditorComponentGenerator::create(const NodeMetaInfos &prototypeChain, bool isComponent) +{ + return QString{R"xy( + %1 + Column { + width: parent.width + %2 + Section { + caption: "%3" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + %4 + } + } + })xy"} + .arg(createImports(m_propertyGenerator.imports()), + componentButton(isComponent), + QObject::tr("Exposed Custom Properties"), + createPropertySections(m_propertyGenerator, prototypeChain)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h new file mode 100644 index 00000000000..56fa5ef78dc --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h @@ -0,0 +1,29 @@ +// 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 "propertycomponentgenerator.h" + +#include + +namespace QmlDesigner { + +#ifdef UNIT_TESTS +using PropertyComponentGeneratorType = PropertyComponentGeneratorInterface; +#else +using PropertyComponentGeneratorType = PropertyComponentGenerator; +#endif +class PropertyEditorComponentGenerator +{ + +public: + PropertyEditorComponentGenerator(const PropertyComponentGeneratorType &propertyGenerator); + + [[nodiscard]] QString create(const NodeMetaInfos &prototypeChain, bool isComponent); + +private: + const PropertyComponentGeneratorType &m_propertyGenerator; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 1124e9b4e84..6edd3338f5c 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -31,6 +31,10 @@ public: addTable, add_medium, add_small, + addcolumnleft_medium, + addcolumnright_medium, + addrowabove_medium, + addrowbelow_medium, adsClose, adsDetach, adsDropDown, @@ -83,6 +87,8 @@ public: closeLink, close_small, code, + codeEditor_medium, + codeview_medium, colorPopupClose, colorSelection_medium, columnsAndRows, @@ -115,6 +121,8 @@ public: deleteTable, delete_medium, delete_small, + deletecolumn_medium, + deleterow_medium, designMode_large, detach, directionalLight_small, @@ -133,7 +141,11 @@ public: download, downloadUnavailable, downloadUpdate, + downloadcsv_large, + downloadcsv_medium, downloaded, + downloadjson_large, + downloadjson_medium, dragmarks, duplicate_small, edit, @@ -281,6 +293,8 @@ public: snapping_conf_medium, snapping_medium, snapping_small, + sortascending_medium, + sortdescending_medium, sphere_medium, sphere_small, splitColumns, @@ -337,6 +351,10 @@ public: upDownSquare2, updateAvailable_medium, updateContent_medium, + uploadcsv_large, + uploadcsv_medium, + uploadjson_large, + uploadjson_medium, visibilityOff, visibilityOn, visible_medium, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 8c06085393c..26f9a974211 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -56,7 +55,6 @@ public: , contentLibraryView{externalDependencies} , componentView{externalDependencies} , edit3DView{externalDependencies} - , effectMakerView{externalDependencies} , formEditorView{externalDependencies} , textEditorView{externalDependencies} , assetsLibraryView{externalDependencies} @@ -79,7 +77,6 @@ public: ContentLibraryView contentLibraryView; ComponentView componentView; Edit3DView edit3DView; - EffectMakerView effectMakerView; FormEditorView formEditorView; TextEditorView textEditorView; AssetsLibraryView assetsLibraryView; @@ -212,9 +209,6 @@ QList ViewManager::standardViews() const .toBool()) list.append(&d->debugView); - if (qEnvironmentVariableIsSet("ENABLE_QDS_EFFECTMAKER")) - list.append(&d->effectMakerView); - if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW")) list.append(&d->collectionView); @@ -393,9 +387,6 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->textureEditorView.widgetInfo()); widgetInfoList.append(d->statesEditorView.widgetInfo()); - if (qEnvironmentVariableIsSet("ENABLE_QDS_EFFECTMAKER")) - widgetInfoList.append(d->effectMakerView.widgetInfo()); - if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW")) widgetInfoList.append(d->collectionView.widgetInfo()); diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp index e967be04ec8..a389442ec96 100644 --- a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp @@ -126,6 +126,7 @@ QWidget *ZoomAction::createWidget(QWidget *parent) if (!m_combo && parentIsToolBar(parent)) { m_combo = createZoomComboBox(parent); m_combo->setProperty(Utils::StyleHelper::C_HIDE_BORDER, true); + m_combo->setProperty(Utils::StyleHelper::C_TOOLBAR_ACTIONWIDGET, true); m_combo->setCurrentIndex(m_index); m_combo->setToolTip(m_combo->currentText()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 0b38b9d35d9..8ba1347bbfb 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -3,8 +3,8 @@ #include "bindingmodel.h" #include "bindingmodelitem.h" -#include "connectionview.h" #include "connectioneditorutils.h" +#include "connectionview.h" #include "modelfwd.h" #include @@ -16,6 +16,8 @@ #include +#include + namespace QmlDesigner { BindingModel::BindingModel(ConnectionView *parent) @@ -142,11 +144,15 @@ void BindingModel::setCurrentProperty(const AbstractProperty &property) void BindingModel::updateItem(const BindingProperty &property) { - if (auto *item = itemForProperty(property)) + if (auto *item = itemForProperty(property)) { item->updateProperty(property); - else - appendRow(new BindingModelItem(property)); - + } else { + ModelNode node = property.parentModelNode(); + if (connectionView()->isSelectedModelNode(node)) { + appendRow(new BindingModelItem(property)); + setCurrentProperty(property); + } + } m_delegate->update(currentProperty(), m_connectionView); } @@ -250,15 +256,15 @@ BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent) , m_sourceNodeProperty() { connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() { - expressionChanged(); + sourceNodeChanged(); }); connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() { - expressionChanged(); + sourcePropertyNameChanged(); }); connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() { - propertyNameChanged(); + targetPropertyNameChanged(); }); } @@ -267,7 +273,7 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra if (!property.isValid()) return; - auto addName = [](QStringList&& list, const QString& name) { + auto addName = [](QStringList &&list, const QString &name) { if (!list.contains(name)) list.prepend(name); return std::move(list); @@ -282,7 +288,8 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra m_sourceNode.setModel(sourceNodes); m_sourceNode.setCurrentText(sourceNodeName); - auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName); + auto availableProperties = availableSourceProperties(sourceNodeName, property, view); + auto sourceproperties = addName(std::move(availableProperties), sourcePropertyName); m_sourceNodeProperty.setModel(sourceproperties); m_sourceNodeProperty.setCurrentText(sourcePropertyName); @@ -316,14 +323,41 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty() return &m_sourceNodeProperty; } -void BindingModelBackendDelegate::expressionChanged() const +void BindingModelBackendDelegate::sourceNodeChanged() { - auto commit = [this]() { + BindingModel *model = qobject_cast(parent()); + QTC_ASSERT(model, return); + + ConnectionView *view = model->connectionView(); + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return ); + + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + + BindingProperty targetProperty = model->currentProperty(); + QStringList properties = availableSourceProperties(sourceNode, targetProperty, view); + + if (!properties.contains(sourceProperty)) { + QSignalBlocker blocker(this); + properties.prepend("---"); + m_sourceNodeProperty.setModel(properties); + m_sourceNodeProperty.setCurrentText({"---"}); + } + sourcePropertyNameChanged(); +} + +void BindingModelBackendDelegate::sourcePropertyNameChanged() const +{ + const QString sourceProperty = m_sourceNodeProperty.currentText(); + if (sourceProperty.isEmpty() || sourceProperty == "---") + return; + + auto commit = [this, sourceProperty]() { BindingModel *model = qobject_cast(parent()); QTC_ASSERT(model, return); const QString sourceNode = m_sourceNode.currentText(); - const QString sourceProperty = m_sourceNodeProperty.currentText(); QString expression; if (sourceProperty.isEmpty()) expression = sourceNode; @@ -337,7 +371,7 @@ void BindingModelBackendDelegate::expressionChanged() const callLater(commit); } -void BindingModelBackendDelegate::propertyNameChanged() const +void BindingModelBackendDelegate::targetPropertyNameChanged() const { auto commit = [this]() { BindingModel *model = qobject_cast(parent()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h index 4c3dd1210e5..4f5032afc24 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -86,8 +86,9 @@ public: private: QString targetNode() const; - void expressionChanged() const; - void propertyNameChanged() const; + void sourceNodeChanged(); + void sourcePropertyNameChanged() const; + void targetPropertyNameChanged() const; StudioQmlComboBoxBackend *property(); StudioQmlComboBoxBackend *sourceNode(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp index d44077e4ff6..5bb96390253 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp @@ -760,14 +760,14 @@ QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statem newDoc->parseJavaScript(); if (!newDoc->isParsedCorrectly()) - return ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME; + return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; newDoc->ast()->accept(&evaluator); const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded; if (!valid) - return ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME; + return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; auto result = evaluator.resultNode(); @@ -967,11 +967,11 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldEx bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression) { if (d->isInIfCondition()) - d->checkValidityAndReturn(false, "Functions are not allowd in the expressions"); + return d->checkValidityAndReturn(false, "Functions are not allowd in the expressions"); MatchedStatement *currentStatement = d->currentStatement(); if (!currentStatement) - d->checkValidityAndReturn(false, "Invalid place to call an expression"); + return d->checkValidityAndReturn(false, "Invalid place to call an expression"); if (ConnectionEditorStatements::isEmptyStatement(*currentStatement)) { if (d->parentNodeStatus().childId() == 0) { diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h index 63d457a84a4..06e6434b5ab 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h @@ -21,8 +21,8 @@ inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( "QmlDesigner::ConnectionEditorStatements", "Print"); inline constexpr char EMPTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( "QmlDesigner::ConnectionEditorStatements", "Empty"); -inline constexpr char UNKNOWN_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Unknown"); +inline constexpr char CUSTOM_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( + "QmlDesigner::ConnectionEditorStatements", "Custom"); struct Variable; struct MatchedFunction; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp index d43ac648a6f..c8f16cb3506 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "connectioneditorutils.h" -#include #include #include #include @@ -58,61 +57,66 @@ PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode return {}; } -NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property) +NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model) { // Note: Uses old mechanism to create the NodeMetaInfo and supports // only types we care about in the connection editor. // TODO: Support all possible AbstractProperty types and move to the // AbstractProperty class. - if (property.dynamicTypeName() == "bool") - return property.model()->boolMetaInfo(); - else if (property.dynamicTypeName() == "int") - return property.model()->metaInfo("QML.int"); - else if (property.dynamicTypeName() == "real") - return property.model()->metaInfo("QML.real"); - else if (property.dynamicTypeName() == "color") - return property.model()->metaInfo("QML.color"); - else if (property.dynamicTypeName() == "string") - return property.model()->metaInfo("QML.string"); - else if (property.dynamicTypeName() == "url") - return property.model()->metaInfo("QML.url"); - else if (property.dynamicTypeName() == "variant") - return property.model()->metaInfo("QML.variant"); + if (typeName == "bool") + return model->boolMetaInfo(); + else if (typeName == "int") + return model->metaInfo("QML.int"); + else if (typeName == "real") + return model->metaInfo("QML.real"); + else if (typeName == "color") + return model->metaInfo("QML.color"); + else if (typeName == "string") + return model->metaInfo("QML.string"); + else if (typeName == "url") + return model->metaInfo("QML.url"); + else if (typeName == "variant") + return model->metaInfo("QML.variant"); else - qWarning() << __FUNCTION__ << " type " << property.dynamicTypeName() << "not found"; - return { }; + qWarning() << __FUNCTION__ << " type " << typeName << "not found"; + return {}; } -bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &sourceType, const NodeMetaInfo &targetType) +NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property) { - if (sourceType.isVariant()) + return dynamicTypeNameToNodeMetaInfo(property.dynamicTypeName(), property.model()); +} + +bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &target, const NodeMetaInfo &source) +{ + if (target.isVariant()) return true; - if (sourceType.isBool() && targetType.isBool()) + if (target == source) return true; - if (sourceType == targetType) + if (target.isBool() && source.isBool()) return true; - if (sourceType.isNumber() && targetType.isNumber()) + if (target.isNumber() && source.isNumber()) return true; - if (sourceType.isString() && targetType.isString()) + if (target.isString() && source.isString()) return true; - if (sourceType.isUrl() && targetType.isUrl()) + if (target.isUrl() && source.isUrl()) return true; - if (sourceType.isColor() && targetType.isColor()) + if (target.isColor() && source.isColor()) return true; return false; } -bool metaInfoIsCompatible(const NodeMetaInfo &sourceType, const PropertyMetaInfo &metaInfo) +bool metaInfoIsCompatible(const NodeMetaInfo &targetType, const PropertyMetaInfo &sourceInfo) { - NodeMetaInfo targetType = metaInfo.propertyType(); - return metaInfoIsCompatibleUnsafe(sourceType, targetType); + NodeMetaInfo sourceType = sourceInfo.propertyType(); + return metaInfoIsCompatibleUnsafe(targetType, sourceType); } QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName) @@ -194,7 +198,7 @@ void convertBindingToVariantProperty(const BindingProperty &property, const QVar convertPropertyType(property, value); } -bool isBindingExpression(const QVariant& value) +bool isBindingExpression(const QVariant &value) { if (value.metaType().id() != QMetaType::QString) return false; @@ -250,11 +254,30 @@ QString defaultExpressionForType(const TypeName &type) return expression; } +std::pair splitExpression(const QString &expression) +{ + // ### Todo from original code (getExpressionStrings): + // We assume no expressions yet + const QStringList stringList = expression.split(QLatin1String(".")); + + QString sourceNode = stringList.constFirst(); + QString propertyName; + for (int i = 1; i < stringList.size(); ++i) { + propertyName += stringList.at(i); + if (i != stringList.size() - 1) + propertyName += QLatin1String("."); + } + if (propertyName.isEmpty()) + std::swap(sourceNode, propertyName); + + return {sourceNode, propertyName}; +} + QStringList singletonsFromView(AbstractView *view) { RewriterView *rv = view->rewriterView(); if (!rv) - return { }; + return {}; QStringList out; for (const QmlTypeData &data : rv->getQMLTypes()) { @@ -264,6 +287,29 @@ QStringList singletonsFromView(AbstractView *view) return out; } +std::vector propertiesFromSingleton(const QString &name, AbstractView *view) +{ + Model *model = view->model(); + QTC_ASSERT(model, return {}); + + if (NodeMetaInfo metaInfo = model->metaInfo(name.toUtf8()); metaInfo.isValid()) + return metaInfo.properties(); + + return {}; +} + +QList dynamicPropertiesFromNode(const ModelNode &node) +{ + auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); }; + auto byName = [](const AbstractProperty &a, const AbstractProperty &b) { + return a.name() < b.name(); + }; + + QList dynamicProperties = Utils::filtered(node.properties(), isDynamic); + Utils::sort(dynamicProperties, byName); + return dynamicProperties; +} + QStringList availableSources(AbstractView *view) { QStringList sourceNodes; @@ -295,7 +341,7 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty) return writableProperties; } - return { }; + return {}; } ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode) @@ -309,19 +355,17 @@ ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const Model return {}; } -QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view) +QStringList availableSourceProperties(const QString &id, + const BindingProperty &targetProperty, + AbstractView *view) { - const QString expression = bindingProperty.expression(); - const QStringList stringlist = expression.split(QLatin1String(".")); + ModelNode modelNode = getNodeByIdOrParent(view, id, targetProperty.parentModelNode()); - const QString &id = stringlist.constFirst(); - ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode()); - - NodeMetaInfo type; - if (bindingProperty.isDynamic()) { - type = dynamicTypeMetaInfo(bindingProperty); - } else if (auto metaInfo = bindingProperty.parentModelNode().metaInfo(); metaInfo.isValid()) { - type = metaInfo.property(bindingProperty.name()).propertyType(); + NodeMetaInfo targetType; + if (targetProperty.isDynamic()) { + targetType = dynamicTypeMetaInfo(targetProperty); + } else if (auto metaInfo = targetProperty.parentModelNode().metaInfo(); metaInfo.isValid()) { + targetType = metaInfo.property(targetProperty.name()).propertyType(); } else qWarning() << __FUNCTION__ << " no meta info for target node"; @@ -329,23 +373,19 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab if (!modelNode.isValid()) { QStringList singletons = singletonsFromView(view); if (singletons.contains(id)) { - Model *model = view->model(); - QTC_ASSERT(model, return {}); - if (NodeMetaInfo metaInfo = model->metaInfo(id.toUtf8()); metaInfo.isValid()) { - for (const auto &property : metaInfo.properties()) { - if (metaInfoIsCompatible(type, property)) - possibleProperties.push_back(QString::fromUtf8(property.name())); - } + for (const auto &property : propertiesFromSingleton(id, view)) { + if (metaInfoIsCompatible(targetType, property)) + possibleProperties.push_back(QString::fromUtf8(property.name())); } return possibleProperties; } - qWarning() << __FUNCTION__ << " invalid model node"; + qWarning() << __FUNCTION__ << " invalid model node: " << id; return {}; - } + } - auto isCompatible = [type](const AbstractProperty& other) { + auto isCompatible = [targetType](const AbstractProperty &other) { auto otherType = dynamicTypeMetaInfo(other); - return metaInfoIsCompatibleUnsafe(type, otherType); + return metaInfoIsCompatibleUnsafe(targetType, otherType); }; for (const VariantProperty &variantProperty : modelNode.variantProperties()) { @@ -361,7 +401,7 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab NodeMetaInfo metaInfo = modelNode.metaInfo(); if (metaInfo.isValid()) { for (const auto &property : metaInfo.properties()) { - if (metaInfoIsCompatible(type, property) ) + if (metaInfoIsCompatible(targetType, property)) possibleProperties.push_back(QString::fromUtf8(property.name())); } } else { @@ -371,35 +411,4 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab return possibleProperties; } -QList dynamicPropertiesFromNode(const ModelNode &node) -{ - auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); }; - auto byName = [](const AbstractProperty &a, const AbstractProperty &b) { - return a.name() < b.name(); - }; - - QList dynamicProperties = Utils::filtered(node.properties(), isDynamic); - Utils::sort(dynamicProperties, byName); - return dynamicProperties; -} - -std::pair splitExpression(const QString &expression) -{ - // ### Todo from original code (getExpressionStrings): - // We assume no expressions yet - const QStringList stringList = expression.split(QLatin1String(".")); - - QString sourceNode = stringList.constFirst(); - QString propertyName; - for (int i = 1; i < stringList.size(); ++i) { - propertyName += stringList.at(i); - if (i != stringList.size() - 1) - propertyName += QLatin1String("."); - } - if (propertyName.isEmpty()) - std::swap(sourceNode, propertyName); - - return {sourceNode, propertyName}; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h index 1f05d654596..b81cc97bbae 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h @@ -24,21 +24,27 @@ void showErrorMessage(const QString &text); QString idOrTypeName(const ModelNode &modelNode); PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode); -NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property); +NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property); +NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model); + QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName); void convertVariantToBindingProperty(const VariantProperty &property, const QVariant &value); void convertBindingToVariantProperty(const BindingProperty &property, const QVariant &value); -bool isBindingExpression(const QVariant& value); +bool isBindingExpression(const QVariant &value); bool isDynamicVariantPropertyType(const TypeName &type); QVariant defaultValueForType(const TypeName &type); QString defaultExpressionForType(const TypeName &type); - -QStringList availableSources(AbstractView *view); -QStringList availableTargetProperties(const BindingProperty &bindingProperty); -QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view); -QList dynamicPropertiesFromNode(const ModelNode &node); - std::pair splitExpression(const QString &expression); +QStringList singletonsFromView(AbstractView *view); +std::vector propertiesFromSingleton(const QString &name, AbstractView *view); + +QList dynamicPropertiesFromNode(const ModelNode &node); +QStringList availableSources(AbstractView *view); +QStringList availableTargetProperties(const BindingProperty &bindingProperty); +QStringList availableSourceProperties(const QString &id, + const BindingProperty &targetProp, + AbstractView *view); + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 15ab1341a38..5791b8ad6c6 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -8,21 +8,25 @@ #include #include #include +#include #include #include #include -#include -#include +#include #include #include #include #include +#include +#include #include -#include #include +#include #include +#include +#include #include namespace { @@ -66,7 +70,7 @@ Qt::ItemFlags ConnectionModel::flags(const QModelIndex &modelIndex) const const int internalId = data(index(modelIndex.row(), TargetModelNodeRow), UserRoles::InternalIdRole).toInt(); ModelNode modelNode = m_connectionView->modelNodeForInternalId(internalId); - if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode)) + if (modelNode.isValid() && ModelUtils::isThisOrAncestorLocked(modelNode)) return Qt::ItemIsEnabled; return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; @@ -300,6 +304,46 @@ ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connectio return result; } +static QString addOnToSignalName(const QString &signal) +{ + QString ret = signal; + ret[0] = ret.at(0).toUpper(); + ret.prepend("on"); + return ret; +} + +static PropertyName getFirstSignalForTarget(const NodeMetaInfo &target) +{ + PropertyName ret = "clicked"; + + if (!target.isValid()) + return ret; + + const auto signalNames = target.signalNames(); + if (signalNames.isEmpty()) + return ret; + + const PropertyNameList priorityList = {"clicked", + "toggled", + "started", + "stopped", + "moved", + "valueChanged", + "visualPostionChanged", + "accepted", + "currentIndexChanged", + "activeFocusChanged"}; + + for (const auto &signal : priorityList) { + if (signalNames.contains(signal)) + return signal; + } + + ret = target.signalNames().first(); + + return ret; +} + void ConnectionModel::addConnection() { QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_CONNECTION_ADDED); @@ -311,33 +355,46 @@ void ConnectionModel::addConnection() NodeMetaInfo nodeMetaInfo = connectionView()->model()->qtQuickConnectionsMetaInfo(); if (nodeMetaInfo.isValid()) { - connectionView()->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){ - ModelNode newNode = connectionView()->createModelNode("QtQuick.Connections", - nodeMetaInfo.majorVersion(), - nodeMetaInfo.minorVersion()); - QString source = "console.log(\"clicked\")"; + ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst(); + const PropertyName signalHandlerName = addOnToSignalName( + QString::fromUtf8(getFirstSignalForTarget( + selectedNode.metaInfo()))) + .toUtf8(); - if (connectionView()->selectedModelNodes().size() == 1) { - ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst(); - if (QmlItemNode::isValidQmlItemNode(selectedNode)) - selectedNode.nodeAbstractProperty("data").reparentHere(newNode); - else - rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode); + connectionView() + ->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode]() { + ModelNode newNode = connectionView() + ->createModelNode("QtQuick.Connections", + nodeMetaInfo.majorVersion(), + nodeMetaInfo.minorVersion()); + QString source = "console.log(\"clicked\")"; - if (QmlItemNode(selectedNode).isFlowActionArea() || QmlVisualNode(selectedNode).isFlowTransition()) - source = selectedNode.validId() + ".trigger()"; + if (connectionView()->selectedModelNodes().size() == 1) { + ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst(); + if (QmlItemNode::isValidQmlItemNode(selectedNode)) + selectedNode.nodeAbstractProperty("data").reparentHere(newNode); + else + rootModelNode + .nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()) + .reparentHere(newNode); - if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty()) - newNode.bindingProperty("target").setExpression(selectedNode.validId()); - } else { - rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode); - newNode.bindingProperty("target").setExpression(rootModelNode.validId()); - } + if (QmlItemNode(selectedNode).isFlowActionArea() + || QmlVisualNode(selectedNode).isFlowTransition()) + source = selectedNode.validId() + ".trigger()"; - newNode.signalHandlerProperty("onClicked").setSource(source); + if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty()) + newNode.bindingProperty("target").setExpression(selectedNode.validId()); + } else { + rootModelNode + .nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()) + .reparentHere(newNode); + newNode.bindingProperty("target").setExpression(rootModelNode.validId()); + } - selectProperty(newNode.signalHandlerProperty("onClicked")); - }); + newNode.signalHandlerProperty(signalHandlerName).setSource(source); + + selectProperty(newNode.signalHandlerProperty(signalHandlerName)); + }); } } } @@ -363,7 +420,11 @@ void ConnectionModel::abstractPropertyChanged(const AbstractProperty &abstractPr void ConnectionModel::deleteConnectionByRow(int currentRow) { SignalHandlerProperty targetSignal = signalHandlerPropertyForRow(currentRow); - QTC_ASSERT(targetSignal.isValid(), return ); + SignalHandlerProperty selectedSignal = signalHandlerPropertyForRow(currentIndex()); + + const bool targetEqualsSelected = targetSignal == selectedSignal; + + QTC_ASSERT(targetSignal.isValid(), return); ModelNode node = targetSignal.parentModelNode(); QTC_ASSERT(node.isValid(), return ); @@ -374,6 +435,9 @@ void ConnectionModel::deleteConnectionByRow(int currentRow) } else { node.destroy(); } + + if (!targetEqualsSelected) + selectProperty(selectedSignal); } void ConnectionModel::removeRowFromTable(const SignalHandlerProperty &property) @@ -424,6 +488,17 @@ void ConnectionModel::selectProperty(const SignalHandlerProperty &property) } } +void ConnectionModel::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + SignalHandlerProperty selectedSignal = signalHandlerPropertyForRow(currentIndex()); + if (selectedSignal.isValid()) { + ModelNode targetNode = getTargetNodeForConnection(selectedSignal.parentModelNode()); + if (targetNode == removedNode) { + emit m_delegate->popupShouldClose(); + } + } +} + void ConnectionModel::handleException() { QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); @@ -645,7 +720,6 @@ QString generateDefaultStatement(ConnectionModelBackendDelegate::ActionType acti void ConnectionModelBackendDelegate::changeActionType(ActionType actionType) { - qDebug() << Q_FUNC_INFO << actionType; QTC_ASSERT(actionType != ConnectionModelStatementDelegate::Custom, return ); ConnectionModel *model = qobject_cast(parent()); @@ -664,14 +738,19 @@ void ConnectionModelBackendDelegate::changeActionType(ActionType actionType) ConnectionEditorStatements::MatchedStatement &okStatement = ConnectionEditorStatements::okStatement(m_handler); + ConnectionEditorStatements::MatchedStatement &koStatement + = ConnectionEditorStatements::koStatement(m_handler); + + koStatement = ConnectionEditorStatements::EmptyBlock(); + //We expect a valid id on the root node const QString validId = model->connectionView()->rootModelNode().validId(); QString statementSource = generateDefaultStatement(actionType, validId); auto tempHandler = ConnectionEditorEvaluator::parseStatement(statementSource); - qDebug() << ConnectionEditorStatements::toString(tempHandler); + auto newOkStatement = ConnectionEditorStatements::okStatement(tempHandler); - qDebug() << "newOk" << statementSource; + QTC_ASSERT(!ConnectionEditorStatements::isEmptyStatement(newOkStatement), return ); okStatement = newOkStatement; @@ -758,6 +837,14 @@ void ConnectionModelBackendDelegate::removeElse() setupHandlerAndStatements(); } +void ConnectionModelBackendDelegate::setNewSource(const QString &newSource) +{ + setSource(newSource); + commitNewSource(newSource); + setupHandlerAndStatements(); + setupCondition(); +} + int ConnectionModelBackendDelegate::currentRow() const { return m_currentRow; @@ -773,14 +860,6 @@ QString removeOnFromSignalName(const QString &signal) return ret; } -QString addOnToSignalName(const QString &signal) -{ - QString ret = signal; - ret[0] = ret.at(0).toUpper(); - ret.prepend("on"); - return ret; -} - void ConnectionModelBackendDelegate::setCurrentRow(int i) { if (m_currentRow == i) @@ -796,6 +875,9 @@ void ConnectionModelBackendDelegate::update() if (m_blockReflection) return; + if (m_currentRow == -1) + return; + m_propertyTreeModel.resetModel(); m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex); @@ -831,9 +913,6 @@ void ConnectionModelBackendDelegate::update() setupHandlerAndStatements(); - qDebug() << Q_FUNC_INFO << ConnectionEditorStatements::toString(m_handler) - << ConnectionEditorStatements::toJavascript(m_handler); - setupCondition(); QTC_ASSERT(model, return ); @@ -897,6 +976,19 @@ ConditionListModel *ConnectionModelBackendDelegate::conditionListModel() return &m_conditionListModel; } +QString ConnectionModelBackendDelegate::indentedSource() const +{ + if (m_source.isEmpty()) + return {}; + + QTextDocument doc(m_source); + QTextCursor cursor(&doc); + IndentingTextEditModifier mod(&doc, cursor); + + mod.indent(0, m_source.length() - 1); + return mod.text(); +} + QString ConnectionModelBackendDelegate::source() const { return m_source; @@ -1422,7 +1514,6 @@ void ConnectionModelStatementDelegate::setupCallFunction() void ConnectionModelStatementDelegate::setupChangeState() { - qDebug() << Q_FUNC_INFO; QTC_ASSERT(std::holds_alternative(m_statement), return ); QTC_ASSERT(m_model->connectionView()->isAttached(), return ); @@ -1476,8 +1567,6 @@ void ConnectionModelStatementDelegate::setupStates() const auto stateSet = std::get(m_statement); - qDebug() << Q_FUNC_INFO << stateSet.stateName; - const QString nodeId = m_stateTargets.currentText(); const ModelNode node = m_model->connectionView()->modelNodeForId(nodeId); @@ -1734,20 +1823,16 @@ void ConditionListModel::command(const QString &string) //TODO remove from prodcution code QStringList list = string.split("%", Qt::SkipEmptyParts); - qDebug() << Q_FUNC_INFO << string << list.size(); - if (list.size() < 2) return; if (list.size() == 2) { if (list.first() == "A") { - qDebug() << "Append" << list.last(); appendToken(list.last()); } else if (list.first() == "R") { bool ok = true; int index = list.last().toInt(&ok); - qDebug() << "Remove" << index; if (ok) removeToken(index); } @@ -1760,15 +1845,12 @@ void ConditionListModel::command(const QString &string) if (ok) updateToken(index, list.last()); - qDebug() << "Update" << index << list.last(); } else if (list.first() == "I") { bool ok = true; int index = list.at(1).toInt(&ok); if (ok) insertToken(index, list.last()); - - qDebug() << "Insert" << index << list.last(); } } } @@ -2027,4 +2109,9 @@ ConnectionEditorStatements::ComparativeStatement ConditionListModel::toStatement return {}; } +void QmlDesigner::ConnectionModel::modelAboutToBeDetached() +{ + emit m_delegate->popupShouldClose(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index bef5b79637d..d25d5aa2d3b 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -71,6 +71,9 @@ public: void selectProperty(const SignalHandlerProperty &property); + void nodeAboutToBeRemoved(const ModelNode &removedNode); + void modelAboutToBeDetached(); + signals: void currentIndexChanged(); @@ -266,6 +269,7 @@ class ConnectionModelBackendDelegate : public QObject Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) Q_PROPERTY(QString source READ source NOTIFY sourceChanged) + Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged) Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT) Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT) @@ -284,6 +288,8 @@ public: Q_INVOKABLE void addElse(); Q_INVOKABLE void removeElse(); + Q_INVOKABLE void setNewSource(const QString &newSource); + void setCurrentRow(int i); void update(); @@ -293,6 +299,7 @@ signals: void hasConditionChanged(); void hasElseChanged(); void sourceChanged(); + void popupShouldClose(); private: int currentRow() const; @@ -306,6 +313,7 @@ private: ConnectionModelStatementDelegate *okStatement(); ConnectionModelStatementDelegate *koStatement(); ConditionListModel *conditionListModel(); + QString indentedSource() const; QString source() const; void setSource(const QString &source); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 41eee97be48..b96afd69665 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -169,6 +169,7 @@ void ConnectionView::modelAboutToBeDetached(Model *model) bindingModel()->reset(); dynamicPropertiesModel()->reset(); connectionModel()->resetModel(); + connectionModel()->modelAboutToBeDetached(); } void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/) @@ -176,6 +177,11 @@ void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/) connectionModel()->resetModel(); } +void ConnectionView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + connectionModel()->nodeAboutToBeRemoved(removedNode); +} + void ConnectionView::nodeRemoved(const ModelNode & /*removedNode*/, const NodeAbstractProperty & /*parentProperty*/, AbstractView::PropertyChangeFlags /*propertyChange*/) diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index b865bf51cc4..338d1fb14ed 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -39,6 +39,7 @@ public: void modelAboutToBeDetached(Model *model) override; void nodeCreated(const ModelNode &createdNode) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 69f51a277d2..560cecd3e1b 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -151,10 +151,15 @@ void DynamicPropertiesModel::updateItem(const AbstractProperty &property) if (!property.isDynamic()) return; - if (auto *item = itemForProperty(property)) + if (auto *item = itemForProperty(property)) { item->updateProperty(property); - else - addProperty(property); + } else { + ModelNode node = property.parentModelNode(); + if (selectedNodes().contains(node)) { + addProperty(property); + setCurrentProperty(property); + } + } } void DynamicPropertiesModel::removeItem(const AbstractProperty &property) diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp index ae0f8972c5d..073b8214927 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp @@ -101,10 +101,45 @@ const std::vector priorityListSignals = {"clicked", "opacityChanged", "rotationChanged"}; -const std::vector priorityListProperties - = {"opacity", "visible", "value", "x", "y", "width", "height", "rotation", - "color", "scale", "state", "enabled", "z", "text", "pressed", "containsMouse", - "checked", "hovered", "down", "clip", "parent", "from", "true", "focus"}; +const std::vector priorityListProperties = {"opacity", + "checked", + "hovered", + "visible", + "value", + "down", + "x", + "y", + "width", + "height", + "from", + "to", + "rotation", + "color", + "scale", + "state", + "enabled", + "z", + "text", + "pressed", + "containsMouse", + "down", + "clip", + "parent", + "radius", + "smooth", + "true", + "focus", + "border.width", + "border.color", + "eulerRotation.x", + "eulerRotation.y", + "eulerRotation.z", + "scale.x", + "scale.y", + "scale.z", + "position.x", + "position.y", + "position.z"}; const std::vector priorityListSlots = {"toggle", "increase", @@ -335,6 +370,11 @@ int PropertyTreeModel::columnCount(const QModelIndex &) const return 1; } +void PropertyTreeModel::setIncludeDotPropertiesOnFirstLevel(bool b) +{ + m_includeDotPropertiesOnFirstLevel = b; +} + void PropertyTreeModel::setPropertyType(PropertyTypes type) { if (m_type == type) @@ -498,7 +538,6 @@ const std::vector PropertyTreeModel::getDynamicProperties( return propertyType == "url"; case ColorType: return propertyType == "color"; - case BoolType: return propertyType == "bool"; default: @@ -519,12 +558,8 @@ const std::vector PropertyTreeModel::sortedAndFilteredPropertyName const PropertyName name = metaInfo.name(); - if (name.contains(".")) - return false; - - if (name.startsWith("icon.")) - return false; - if (name.startsWith("transformOriginPoint.")) + if (!m_includeDotPropertiesOnFirstLevel + && name.contains(".")) return false; return filterProperty(name, metaInfo, recursive); @@ -721,6 +756,9 @@ bool PropertyTreeModel::filterProperty(const PropertyName &name, const NodeMetaInfo propertyType = metaInfo.propertyType(); + if (m_includeDotPropertiesOnFirstLevel && metaInfo.isPointer()) + return false; + //We want to keep sub items with matching properties if (!recursive && metaInfo.isPointer() && sortedAndFilteredPropertyNames(propertyType, true).size() > 0) @@ -842,6 +880,8 @@ PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *parent) : m connect(&m_idCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() { handleIdChanged(); }); + + m_model.setIncludeDotPropertiesOnFirstLevel(true); } void PropertyTreeModelDelegate::setPropertyType(PropertyTreeModel::PropertyTypes type) diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h index 67836cb2698..b93820ac7b9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h @@ -65,6 +65,8 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; + void setIncludeDotPropertiesOnFirstLevel(bool b); + struct DataCacheItem { ModelNode modelNode; @@ -125,6 +127,7 @@ private: PropertyTypes m_type = AllTypes; QString m_filter; mutable QHash> m_sortedAndFilteredPropertyNamesSignalsSlots; + bool m_includeDotPropertiesOnFirstLevel = false; }; class PropertyListProxyModel : public QAbstractListModel diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 54032d28273..81be76ed320 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -11,6 +11,7 @@ #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" #include "contentlibrarywidget.h" +#include "externaldependenciesinterface.h" #include "nodelistproperty.h" #include "qmldesignerconstants.h" #include "qmlobjectnode.h" @@ -223,6 +224,7 @@ void ContentLibraryView::modelAttached(Model *model) const bool hasLibrary = materialLibraryNode().isValid(); m_widget->setHasMaterialLibrary(hasLibrary); m_widget->setHasQuick3DImport(m_hasQuick3DImport); + m_widget->setIsQt6Project(externalDependencies().isQt6Project()); m_sceneId = model->active3DSceneId(); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index cf968a33a89..8b7c4d7d344 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -681,6 +681,20 @@ void ContentLibraryWidget::setHasActive3DScene(bool b) emit hasActive3DSceneChanged(); } +bool ContentLibraryWidget::isQt6Project() const +{ + return m_isQt6Project; +} + +void ContentLibraryWidget::setIsQt6Project(bool b) +{ + if (m_isQt6Project == b) + return; + + m_isQt6Project = b; + emit isQt6ProjectChanged(); +} + void ContentLibraryWidget::reloadQmlSource() { const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml"; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index 5e41f289141..ab71a3dc799 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -32,6 +32,7 @@ class ContentLibraryWidget : public QFrame Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(bool hasActive3DScene READ hasActive3DScene WRITE setHasActive3DScene NOTIFY hasActive3DSceneChanged) + Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged) // Needed for a workaround for a bug where after drag-n-dropping an item, the ScrollView scrolls to a random position Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged) @@ -53,6 +54,9 @@ public: bool hasActive3DScene() const; void setHasActive3DScene(bool b); + bool isQt6Project() const; + void setIsQt6Project(bool b); + Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); void setMaterialsModel(QPointer newMaterialsModel); @@ -83,6 +87,7 @@ signals: void hasMaterialLibraryChanged(); void hasActive3DSceneChanged(); void isDraggingChanged(); + void isQt6ProjectChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -121,6 +126,7 @@ private: bool m_hasActive3DScene = false; bool m_hasQuick3DImport = false; bool m_isDragging = false; + bool m_isQt6Project = false; QString m_baseUrl; QString m_texturesUrl; QString m_textureIconsUrl; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index c1637bf322b..f8c1cc2a7e8 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -120,7 +120,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) const QString cameraFrustumKey = QStringLiteral("showCameraFrustum"); const QString particleEmitterKey = QStringLiteral("showParticleEmitter"); const QString particlesPlayKey = QStringLiteral("particlePlay"); - const QString syncBgColorKey = QStringLiteral("syncBackgroundColor"); + const QString syncEnvBgKey = QStringLiteral("syncEnvBackground"); if (sceneState.contains(sceneKey)) { qint32 newActiveScene = sceneState[sceneKey].value(); @@ -195,9 +195,11 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) bool syncValue = false; bool syncEnabled = false; bool desiredSyncValue = false; - if (sceneState.contains(syncBgColorKey)) - desiredSyncValue = sceneState[syncBgColorKey].toBool(); + if (sceneState.contains(syncEnvBgKey)) + desiredSyncValue = sceneState[syncEnvBgKey].toBool(); ModelNode checkNode = active3DSceneNode(); + const bool activeSceneValid = checkNode.isValid(); + while (checkNode.isValid()) { if (checkNode.metaInfo().isQtQuick3DView3D()) { syncValue = desiredSyncValue; @@ -210,15 +212,15 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) break; } - if (syncValue != desiredSyncValue) { + if (activeSceneValid && syncValue != desiredSyncValue) { // Update actual toolstate as well if we overrode it. QTimer::singleShot(0, this, [this, syncValue]() { - emitView3DAction(View3DActionType::SyncBackgroundColor, syncValue); + emitView3DAction(View3DActionType::SyncEnvBackground, syncValue); }); } - m_syncBackgroundColorAction->action()->setChecked(syncValue); - m_syncBackgroundColorAction->action()->setEnabled(syncEnabled); + m_syncEnvBackgroundAction->action()->setChecked(syncValue); + m_syncEnvBackgroundAction->action()->setEnabled(syncEnabled); // Selection context change updates visible and enabled states SelectionContext selectionContext(this); @@ -449,23 +451,23 @@ QSize Edit3DView::canvasSize() const return {}; } -void Edit3DView::createSelectBackgroundColorAction(QAction *syncBackgroundColorAction) +void Edit3DView::createSelectBackgroundColorAction(QAction *syncEnvBackgroundAction) { QString description = QCoreApplication::translate("SelectBackgroundColorAction", "Select Background Color"); QString tooltip = QCoreApplication::translate("SelectBackgroundColorAction", "Select a color for the background of the 3D view."); - auto operation = [this, syncBackgroundColorAction](const SelectionContext &) { + auto operation = [this, syncEnvBackgroundAction](const SelectionContext &) { BackgroundColorSelection::showBackgroundColorSelectionWidget( edit3DWidget(), DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, this, edit3dBgColorProperty, - [this, syncBackgroundColorAction]() { - if (syncBackgroundColorAction->isChecked()) { - emitView3DAction(View3DActionType::SyncBackgroundColor, false); - syncBackgroundColorAction->setChecked(false); + [this, syncEnvBackgroundAction]() { + if (syncEnvBackgroundAction->isChecked()) { + emitView3DAction(View3DActionType::SyncEnvBackground, false); + syncEnvBackgroundAction->setChecked(false); } }); }; @@ -510,14 +512,14 @@ void Edit3DView::createGridColorSelectionAction() tooltip); } -void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction) +void Edit3DView::createResetColorAction(QAction *syncEnvBackgroundAction) { QString description = QCoreApplication::translate("ResetEdit3DColorsAction", "Reset Colors"); QString tooltip = QCoreApplication::translate("ResetEdit3DColorsAction", "Reset the background color and the color of the " "grid lines of the 3D view to the default values."); - auto operation = [this, syncBackgroundColorAction](const SelectionContext &) { + auto operation = [this, syncEnvBackgroundAction](const SelectionContext &) { QList bgColors = {QRgb(0x222222), QRgb(0x999999)}; Edit3DViewConfig::setColors(this, edit3dBgColorProperty, bgColors); Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors); @@ -526,9 +528,9 @@ void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction) Edit3DViewConfig::setColors(this, edit3dGridColorProperty, {gridColor}); Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, {gridColor}); - if (syncBackgroundColorAction->isChecked()) { - emitView3DAction(View3DActionType::SyncBackgroundColor, false); - syncBackgroundColorAction->setChecked(false); + if (syncEnvBackgroundAction->isChecked()) { + emitView3DAction(View3DActionType::SyncEnvBackground, false); + syncEnvBackgroundAction->setChecked(false); } }; @@ -545,17 +547,17 @@ void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction) tooltip); } -void Edit3DView::createSyncBackgroundColorAction() +void Edit3DView::createSyncEnvBackgroundAction() { - QString description = QCoreApplication::translate("SyncEdit3DColorAction", - "Use Scene Environment Color"); - QString tooltip = QCoreApplication::translate("SyncEdit3DColorAction", + QString description = QCoreApplication::translate("SyncEnvBackgroundAction", + "Use Scene Environment"); + QString tooltip = QCoreApplication::translate("SyncEnvBackgroundAction", "Sets the 3D view to use the Scene Environment " - "color as background color."); + "color or skybox as background color."); - m_syncBackgroundColorAction = std::make_unique( - QmlDesigner::Constants::EDIT3D_EDIT_SYNC_BACKGROUND_COLOR, - View3DActionType::SyncBackgroundColor, + m_syncEnvBackgroundAction = std::make_unique( + QmlDesigner::Constants::EDIT3D_EDIT_SYNC_ENV_BACKGROUND, + View3DActionType::SyncEnvBackground, description, QKeySequence(), true, @@ -589,9 +591,17 @@ QPoint Edit3DView::resolveToolbarPopupPos(Edit3DAction *action) const const QList &objects = action->action()->associatedObjects(); for (QObject *obj : objects) { if (auto button = qobject_cast(obj)) { - // Add small negative modifier to Y coordinate, so highlighted toolbar buttons don't - // peek from under the popup - pos = button->mapToGlobal(QPoint(0, -2)); + if (auto toolBar = qobject_cast(button->parent())) { + // If the button has not yet been shown (i.e. it starts under extension menu), + // the button x value will be zero. + if (button->x() >= toolBar->width() - button->width() || button->x() == 0) { + pos = toolBar->mapToGlobal(QPoint(toolBar->width() - button->width(), 4)); + } else { + // Add small negative modifier to Y coordinate, so highlighted toolbar buttons don't + // peek from under the popup + pos = button->mapToGlobal(QPoint(0, -2)); + } + } break; } } @@ -958,8 +968,15 @@ void Edit3DView::createEdit3DActions() snapToggleTrigger); SelectionContextOperation snapConfigTrigger = [this](const SelectionContext &) { - if (!m_snapConfiguration) + if (!m_snapConfiguration) { m_snapConfiguration = new SnapConfiguration(this); + connect(m_snapConfiguration.data(), &SnapConfiguration::posIntChanged, + this, [this]() { + // Notify every change of position interval as that causes visible changes in grid + rootModelNode().setAuxiliaryData(edit3dSnapPosIntProperty, + m_snapConfiguration->posInt()); + }); + } m_snapConfiguration->showConfigDialog(resolveToolbarPopupPos(m_snapConfigAction.get())); }; @@ -1011,14 +1028,14 @@ void Edit3DView::createEdit3DActions() m_visibilityToggleActions << m_showCameraFrustumAction.get(); m_visibilityToggleActions << m_showParticleEmitterAction.get(); - createSyncBackgroundColorAction(); - createSelectBackgroundColorAction(m_syncBackgroundColorAction->action()); + createSyncEnvBackgroundAction(); + createSelectBackgroundColorAction(m_syncEnvBackgroundAction->action()); createGridColorSelectionAction(); - createResetColorAction(m_syncBackgroundColorAction->action()); + createResetColorAction(m_syncEnvBackgroundAction->action()); m_backgroundColorActions << m_selectBackgroundColorAction.get(); m_backgroundColorActions << m_selectGridColorAction.get(); - m_backgroundColorActions << m_syncBackgroundColorAction.get(); + m_backgroundColorActions << m_syncEnvBackgroundAction.get(); m_backgroundColorActions << m_resetColorAction.get(); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 2b2bd57d93f..2fb1e0451cd 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -101,10 +101,10 @@ private: void showMaterialPropertiesView(); void updateAlignActionStates(); - void createSelectBackgroundColorAction(QAction *syncBackgroundColorAction); + void createSelectBackgroundColorAction(QAction *syncEnvBackgroundAction); void createGridColorSelectionAction(); - void createResetColorAction(QAction *syncBackgroundColorAction); - void createSyncBackgroundColorAction(); + void createResetColorAction(QAction *syncEnvBackgroundAction); + void createSyncEnvBackgroundAction(); void createSeekerSliderAction(); QPoint resolveToolbarPopupPos(Edit3DAction *action) const; @@ -135,7 +135,7 @@ private: std::unique_ptr m_particlesPlayAction; std::unique_ptr m_particlesRestartAction; std::unique_ptr m_seekerAction; - std::unique_ptr m_syncBackgroundColorAction; + std::unique_ptr m_syncEnvBackgroundAction; std::unique_ptr m_selectBackgroundColorAction; std::unique_ptr m_selectGridColorAction; std::unique_ptr m_resetColorAction; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 8c3d54bb4f2..c054b712ce3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -9,6 +9,7 @@ #include "edit3dtoolbarmenu.h" #include "edit3dview.h" #include "edit3dviewconfig.h" +#include "externaldependenciesinterface.h" #include "materialutils.h" #include "metainfo.h" #include "modelnodeoperations.h" @@ -24,6 +25,7 @@ #include #include #include +#include #include #include @@ -43,7 +45,8 @@ namespace QmlDesigner { -static inline QIcon contextIcon(const DesignerIcons::IconId &iconId) { +inline static QIcon contextIcon(const DesignerIcons::IconId &iconId) +{ return DesignerActionManager::instance().contextIcon(iconId); }; @@ -168,28 +171,8 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view) createContextMenu(); - m_mcuLabel = new QLabel(this); - m_mcuLabel->setText(tr("MCU project does not support Qt Quick 3D.")); - m_mcuLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - fillLayout->addWidget(m_mcuLabel.data()); - // Onboarding label contains instructions for new users how to get 3D content into the project m_onboardingLabel = new QLabel(this); - QString labelText = - tr("Your file does not import Qt Quick 3D.

      " - "To create a 3D view, add the" - " QtQuick3D" - " module in the" - " Components" - " view or click" - " here" - ".

      " - "To import 3D assets, select" - " +" - " in the" - " Assets" - " view."); - m_onboardingLabel->setText(labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name())); m_onboardingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); connect(m_onboardingLabel, &QLabel::linkActivated, this, &Edit3DWidget::linkActivated); fillLayout->addWidget(m_onboardingLabel.data()); @@ -276,17 +259,16 @@ void Edit3DWidget::createContextMenu() m_contextMenu->addSeparator(); m_selectParentAction = m_contextMenu->addAction( - contextIcon(DesignerIcons::ParentIcon), - tr("Select Parent"), [&] { - ModelNode parentNode = ModelNode::lowestCommonAncestor(view()->selectedModelNodes()); - if (!parentNode.isValid()) - return; + contextIcon(DesignerIcons::ParentIcon), tr("Select Parent"), [&] { + ModelNode parentNode = ModelUtils::lowestCommonAncestor(view()->selectedModelNodes()); + if (!parentNode.isValid()) + return; - if (!parentNode.isRootNode() && view()->isSelectedModelNode(parentNode)) - parentNode = parentNode.parentProperty().parentModelNode(); + if (!parentNode.isRootNode() && view()->isSelectedModelNode(parentNode)) + parentNode = parentNode.parentProperty().parentModelNode(); - view()->setSelectedModelNode(parentNode); - }); + view()->setSelectedModelNode(parentNode); + }); QAction *defaultToggleGroupAction = view()->edit3DAction(View3DActionType::SelectionModeToggle)->action(); m_toggleGroupAction = m_contextMenu->addAction( @@ -310,12 +292,48 @@ bool Edit3DWidget::isSceneLocked() const { if (m_view && m_view->hasModelNodeForInternalId(m_canvas->activeScene())) { ModelNode node = m_view->modelNodeForInternalId(m_canvas->activeScene()); - if (ModelNode::isThisOrAncestorLocked(node)) + if (ModelUtils::isThisOrAncestorLocked(node)) return true; } return false; } +void Edit3DWidget::showOnboardingLabel() +{ + QString text; + const DesignerMcuManager &mcuManager = DesignerMcuManager::instance(); + if (mcuManager.isMCUProject()) { + const QStringList mcuAllowedList = mcuManager.allowedImports(); + if (!mcuAllowedList.contains("QtQuick3d")) + text = tr("3D view is not supported in MCU projects."); + } + + if (text.isEmpty()) { + if (m_view->externalDependencies().isQt6Project()) { + QString labelText = + tr("Your file does not import Qt Quick 3D.

      " + "To create a 3D view, add the" + " QtQuick3D" + " module in the" + " Components" + " view or click" + " here" + ".

      " + "To import 3D assets, select" + " +" + " in the" + " Assets" + " view."); + text = labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name()); + } else { + text = tr("3D view is not supported in Qt5 projects."); + } + } + + m_onboardingLabel->setText(text); + m_onboardingLabel->setVisible(true); +} + // Called by the view to update the "create" sub-menu when the Quick3D entries are ready. void Edit3DWidget::updateCreateSubMenu(const QList &entriesList) { @@ -420,23 +438,10 @@ void Edit3DWidget::showCanvas(bool show) } m_canvas->setVisible(show); - if (show) { + if (show) m_onboardingLabel->setVisible(false); - m_mcuLabel->setVisible(false); - } else { - bool quick3dAllowed = true; - const DesignerMcuManager &mcuManager = DesignerMcuManager::instance(); - if (mcuManager.isMCUProject()) { - const QStringList mcuAllowedList = mcuManager.allowedImports(); - if (!mcuAllowedList.contains("QtQuick3d")) - quick3dAllowed = false; - } - - m_onboardingLabel->setVisible(quick3dAllowed); - m_mcuLabel->setVisible(!quick3dAllowed); - } - - + else + showOnboardingLabel(); } QMenu *Edit3DWidget::visibilityTogglesMenu() const @@ -521,7 +526,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) // Block all drags if scene root node is locked if (m_view->hasModelNodeForInternalId(m_canvas->activeScene())) { ModelNode node = m_view->modelNodeForInternalId(m_canvas->activeScene()); - if (ModelNode::isThisOrAncestorLocked(node)) + if (ModelUtils::isThisOrAncestorLocked(node)) return; } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index f764f068bf4..b9826ca07b3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -66,6 +66,8 @@ private: bool isPasteAvailable() const; bool isSceneLocked() const; + void showOnboardingLabel(); + QPointer m_edit3DView; QPointer m_view; QPointer m_canvas; diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp index 44fb7ef1322..f1d8889364b 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp @@ -11,7 +11,8 @@ #include -#include +#include +#include #include #include #include @@ -46,7 +47,8 @@ SnapConfiguration::SnapConfiguration(Edit3DView *view) SnapConfiguration::~SnapConfiguration() { - cleanup(); + delete m_configDialog; + restoreCursor(); } void SnapConfiguration::apply() @@ -66,10 +68,10 @@ void SnapConfiguration::apply() m_rotationInterval); Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL, m_scaleInterval); - m_view->syncSnapAuxPropsToSettings(); + if (!m_view.isNull()) + m_view->syncSnapAuxPropsToSettings(); } - - cancel(); + deleteLater(); } void SnapConfiguration::resetDefaults() @@ -83,6 +85,45 @@ void SnapConfiguration::resetDefaults() setScaleInt(defaultScaleInt); } +void SnapConfiguration::hideCursor() +{ + if (QGuiApplication::overrideCursor()) + return; + + QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + + if (QWindow *w = QGuiApplication::focusWindow()) + m_lastPos = QCursor::pos(w->screen()); +} + +void SnapConfiguration::restoreCursor() +{ + if (!QGuiApplication::overrideCursor()) + return; + + QGuiApplication::restoreOverrideCursor(); + + if (QWindow *w = QGuiApplication::focusWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +void SnapConfiguration::holdCursorInPlace() +{ + if (!QGuiApplication::overrideCursor()) + return; + + if (QWindow *w = QGuiApplication::focusWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +int SnapConfiguration::devicePixelRatio() +{ + if (QWindow *w = QGuiApplication::focusWindow()) + return w->devicePixelRatio(); + + return 1; +} + void SnapConfiguration::showConfigDialog(const QPoint &pos) { bool posEnabled = Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION, true).toBool(); @@ -116,9 +157,7 @@ void SnapConfiguration::showConfigDialog(const QPoint &pos) m_configDialog->setModality(Qt::NonModal); m_configDialog->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_configDialog->rootContext()->setContextProperties({ - {"rootView", QVariant::fromValue(this)} - }); + m_configDialog->rootContext()->setContextObject(this); m_configDialog->setSource(QUrl::fromLocalFile(path)); m_configDialog->installEventFilter(this); @@ -194,9 +233,12 @@ void SnapConfiguration::setScaleInt(double value) } } -void SnapConfiguration::cleanup() +void SnapConfiguration::asyncClose() { - delete m_configDialog; + QTimer::singleShot(0, this, [this]() { + if (!m_configDialog.isNull() && m_configDialog->isVisible()) + m_configDialog->close(); + }); } void SnapConfiguration::cancel() @@ -209,15 +251,13 @@ void SnapConfiguration::cancel() bool SnapConfiguration::eventFilter(QObject *obj, QEvent *event) { - // Closing dialog always applies the changes - if (obj == m_configDialog) { if (event->type() == QEvent::FocusOut) { - apply(); + asyncClose(); } else if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) - apply(); + asyncClose(); } else if (event->type() == QEvent::Close) { apply(); } diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h index ae1a4a0d93b..729e6ce7d10 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h @@ -5,11 +5,11 @@ #include #include +#include #include QT_BEGIN_NAMESPACE class QQuickView; -class QPoint; QT_END_NAMESPACE namespace QmlDesigner { @@ -47,6 +47,10 @@ public: ~SnapConfiguration(); Q_INVOKABLE void resetDefaults(); + Q_INVOKABLE void hideCursor(); + Q_INVOKABLE void restoreCursor(); + Q_INVOKABLE void holdCursorInPlace(); + Q_INVOKABLE int devicePixelRatio(); void cancel(); void apply(); @@ -86,7 +90,7 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override; private: - void cleanup(); + void asyncClose(); QPointer m_configDialog; QPointer m_view; @@ -98,6 +102,7 @@ private: double m_rotationInterval = 0.; double m_scaleInterval = 0.; bool m_changes = false; + QPoint m_lastPos; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakerview.cpp b/src/plugins/qmldesigner/components/effectmaker/effectmakerview.cpp deleted file mode 100644 index 641b41a8ce2..00000000000 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakerview.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// 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 "effectmakerview.h" - -#include "effectmakerwidget.h" -#include "effectmakernodesmodel.h" -#include "designmodecontext.h" -#include "nodeinstanceview.h" - -#include - -#include -#include -#include -#include -#include - -namespace QmlDesigner { - -EffectMakerView::EffectMakerView(ExternalDependenciesInterface &externalDependencies) - : AbstractView{externalDependencies} -{ -} - -EffectMakerView::~EffectMakerView() -{} - -bool EffectMakerView::hasWidget() const -{ - return true; -} - -WidgetInfo EffectMakerView::widgetInfo() -{ - if (m_widget.isNull()) { - m_widget = new EffectMakerWidget{this}; - - auto context = new Internal::EffectMakerContext(m_widget.data()); - Core::ICore::addContextObject(context); - } - - return createWidgetInfo(m_widget.data(), "Effect Maker", WidgetInfo::LeftPane, 0, tr("Effect Maker")); -} - -void EffectMakerView::customNotification(const AbstractView * /*view*/, - const QString & /*identifier*/, - const QList & /*nodeList*/, - const QList & /*data*/) -{ - // TODO -} - -void EffectMakerView::modelAttached(Model *model) -{ - AbstractView::modelAttached(model); - - // Add some dummy effects data - //m_widget->effectMakerModel()->setEffects({"Drop Shadow", "Colorize", "Fast Blue"}); // TODO - m_widget->effectMakerNodesModel()->loadModel(); - m_widget->initView(); -} - -void EffectMakerView::modelAboutToBeDetached(Model *model) -{ - AbstractView::modelAboutToBeDetached(model); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/effectmaker/effectmakerview.h b/src/plugins/qmldesigner/components/effectmaker/effectmakerview.h deleted file mode 100644 index 53e58acc67d..00000000000 --- a/src/plugins/qmldesigner/components/effectmaker/effectmakerview.h +++ /dev/null @@ -1,34 +0,0 @@ -// 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 "abstractview.h" - -#include - -namespace QmlDesigner { - -class EffectMakerWidget; - -class EffectMakerView : public AbstractView -{ -public: - EffectMakerView(ExternalDependenciesInterface &externalDependencies); - ~EffectMakerView() override; - - bool hasWidget() const override; - WidgetInfo widgetInfo() override; - - // AbstractView - void modelAttached(Model *model) override; - void modelAboutToBeDetached(Model *model) override; - -private: - void customNotification(const AbstractView *view, const QString &identifier, - const QList &nodeList, const QList &data) override; - - QPointer m_widget; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp index 774b4c0d911..657fb4773e0 100644 --- a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp @@ -9,6 +9,8 @@ #include "modelnodecontextmenu.h" #include "qmldesignerconstants.h" +#include + #include #include #include @@ -180,7 +182,7 @@ FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &poi if (formEditorItem->parentItem() && !formEditorItem->parentItem()->isContentVisible()) continue; - if (formEditorItem && ModelNode::isThisOrAncestorLocked(formEditorItem->qmlItemNode().modelNode())) + if (formEditorItem && ModelUtils::isThisOrAncestorLocked(formEditorItem->qmlItemNode().modelNode())) continue; if (!nearestItem) diff --git a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp index 07a16de7bbd..9d9139cf017 100644 --- a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp +++ b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp @@ -65,6 +65,7 @@ QWidget *BackgroundAction::createWidget(QWidget *parent) this, &BackgroundAction::emitBackgroundChanged); comboBox->setProperty(Utils::StyleHelper::C_HIDE_BORDER, true); + comboBox->setProperty(Utils::StyleHelper::C_TOOLBAR_ACTIONWIDGET, true); comboBox->setToolTip(tr("Set the color of the canvas.")); m_comboBox = comboBox; return comboBox; diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 6b580a7d5f3..2a433e6764a 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -652,9 +652,8 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, editorItem->setFrameColor(data.value()); } - if (key == contextImageProperty) { + if (key == contextImageProperty && !Qml3DNode::isValidVisualRoot(rootModelNode())) m_formEditorWidget->setBackgoundImage(data.value()); - } } static void updateTransitions(FormEditorScene *scene, const QmlItemNode &qmlItemNode) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.cpp index 66afa19f8b8..8b3f2dee79d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "itemlibraryaddimportmodel.h" +#include "itemlibraryconstants.h" #include #include @@ -56,6 +57,14 @@ QHash ItemLibraryAddImportModel::roleNames() const return m_roleNames; } +namespace { +bool isPriorityImport(QStringView importUrl) +{ + return std::find(std::begin(priorityImports), std::end(priorityImports), importUrl) + != std::end(priorityImports); +} +} // namespace + void ItemLibraryAddImportModel::update(const Imports &possibleImports) { beginResetModel(); @@ -77,7 +86,7 @@ void ItemLibraryAddImportModel::update(const Imports &possibleImports) filteredImports = possibleImports; } - Utils::sort(filteredImports, [this](const Import &firstImport, const Import &secondImport) { + Utils::sort(filteredImports, [](const Import &firstImport, const Import &secondImport) { if (firstImport.url() == secondImport.url()) return firstImport.toString() < secondImport.toString(); @@ -87,8 +96,8 @@ void ItemLibraryAddImportModel::update(const Imports &possibleImports) if (secondImport.url() == "QtQuick") return false; - const bool firstPriority = m_priorityImports.contains(firstImport.url()); - if (firstPriority != m_priorityImports.contains(secondImport.url())) + const bool firstPriority = isPriorityImport(firstImport.url()); + if (firstPriority != isPriorityImport(secondImport.url())) return firstPriority; if (firstImport.isLibraryImport() && secondImport.isFileImport()) @@ -109,11 +118,11 @@ void ItemLibraryAddImportModel::update(const Imports &possibleImports) // create import sections bool previousIsPriority = false; for (const Import &import : std::as_const(filteredImports)) { - bool currentIsPriority = m_priorityImports.contains(import.url()); - if (previousIsPriority && !currentIsPriority) - m_importList.append(Import::empty()); // empty import acts as a separator - m_importList.append(import); - previousIsPriority = currentIsPriority; + bool currentIsPriority = isPriorityImport(import.url()); + if (previousIsPriority && !currentIsPriority) + m_importList.append(Import::empty()); // empty import acts as a separator + m_importList.append(import); + previousIsPriority = currentIsPriority; } endResetModel(); @@ -151,9 +160,4 @@ Import ItemLibraryAddImportModel::getImportAt(int index) const return m_importList.at(index); } -void ItemLibraryAddImportModel::setPriorityImports(const QSet &priorityImports) -{ - m_priorityImports = priorityImports; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.h index 2989fd51642..c70ef9f4046 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryaddimportmodel.h @@ -28,7 +28,6 @@ public: void setSearchText(const QString &searchText); Import getImportAt(int index) const; - void setPriorityImports(const QSet &priorityImports); Import getImport(const QString &importUrl) const; private: diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryconstants.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryconstants.h new file mode 100644 index 00000000000..139d3fe3380 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryconstants.h @@ -0,0 +1,18 @@ +// 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 + +namespace QmlDesigner { + +inline constexpr QStringView priorityImports[] = {u"QtQuick.Controls", + u"QtQuick.Layouts", + u"QtQuick.Studio.Effects", + u"QtQuick.Studio.Components", + u"QtQuick.Studio.MultiText", + u"QtQuick.Studio.LogicHelper", + u"Qt.SafeRenderer", + u"QtQuick3D", + u"FlowView"}; // have to be sorted + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index d2435fbf15a..8d3603ea841 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -3,6 +3,7 @@ #include "itemlibrarywidget.h" +#include "itemlibraryconstants.h" #include "itemlibraryiconimageprovider.h" #include "itemlibraryimport.h" @@ -183,16 +184,11 @@ void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) if (m_itemLibraryInfo) { disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, this, &ItemLibraryWidget::delayedUpdateModel); - disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::priorityImportsChanged, - this, &ItemLibraryWidget::handlePriorityImportsChanged); } m_itemLibraryInfo = itemLibraryInfo; if (itemLibraryInfo) { connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, this, &ItemLibraryWidget::delayedUpdateModel); - connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::priorityImportsChanged, - this, &ItemLibraryWidget::handlePriorityImportsChanged); - m_addModuleModel->setPriorityImports(m_itemLibraryInfo->priorityImports()); } delayedUpdateModel(); } @@ -354,14 +350,6 @@ void ItemLibraryWidget::updateSearch() m_addModuleModel->setSearchText(m_filterText); } -void ItemLibraryWidget::handlePriorityImportsChanged() -{ - if (!m_itemLibraryInfo.isNull()) { - m_addModuleModel->setPriorityImports(m_itemLibraryInfo->priorityImports()); - m_addModuleModel->update(set_difference(m_model->possibleImports(), m_model->imports())); - } -} - void ItemLibraryWidget::setIsDragging(bool val) { if (m_isDragging != val) { diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index 38cd782d097..e791fceb698 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -90,7 +90,6 @@ private: void reloadQmlSource(); void updateSearch(); - void handlePriorityImportsChanged(); void setIsDragging(bool val); static QString getDependencyImport(const Import &import); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 81ec4cbb3a5..d36e78512bb 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -187,6 +187,20 @@ void MaterialBrowserModel::setHasMaterialLibrary(bool b) emit hasMaterialLibraryChanged(); } +bool MaterialBrowserModel::isQt6Project() const +{ + return m_isQt6Project; +} + +void MaterialBrowserModel::setIsQt6Project(bool b) +{ + if (m_isQt6Project == b) + return; + + m_isQt6Project = b; + emit isQt6ProjectChanged(); +} + QString MaterialBrowserModel::copiedMaterialType() const { return m_copiedMaterialType; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 24c34394386..337dce05507 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -23,6 +23,7 @@ class MaterialBrowserModel : public QAbstractListModel Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged) Q_PROPERTY(QString copiedMaterialType READ copiedMaterialType WRITE setCopiedMaterialType NOTIFY copiedMaterialTypeChanged) Q_PROPERTY(QStringList defaultMaterialSections MEMBER m_defaultMaterialSections NOTIFY materialSectionsChanged) Q_PROPERTY(QStringList principledMaterialSections MEMBER m_principledMaterialSections NOTIFY materialSectionsChanged) @@ -49,6 +50,9 @@ public: bool hasMaterialLibrary() const; void setHasMaterialLibrary(bool b); + bool isQt6Project() const; + void setIsQt6Project(bool b); + bool isEmpty() const { return m_isEmpty; } QString copiedMaterialType() const; @@ -105,6 +109,7 @@ signals: const QmlDesigner::ModelNode &material, const QList &props, bool all); + void isQt6ProjectChanged(); private: bool isValidIndex(int idx) const; @@ -126,6 +131,7 @@ private: bool m_hasModelSelection = false; bool m_hasMaterialLibrary = false; bool m_allPropsCopied = true; + bool m_isQt6Project = false; QString m_copiedMaterialType; QPointer m_view; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 5349fc26ce7..7c2ca0abbaf 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -6,6 +6,7 @@ #include "bindingproperty.h" #include "createtexture.h" #include "designmodecontext.h" +#include "externaldependenciesinterface.h" #include "materialbrowsermodel.h" #include "materialbrowsertexturesmodel.h" #include "materialbrowserwidget.h" @@ -230,6 +231,7 @@ void MaterialBrowserView::modelAttached(Model *model) m_widget->clearSearchFilter(); m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_hasQuick3DImport = model->hasImport("QtQuick3D"); + m_widget->materialBrowserModel()->setIsQt6Project(externalDependencies().isQt6Project()); // Project load is already very busy and may even trigger puppet reset, so let's wait a moment // before refreshing the model diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index 552133ac887..5661436a8f3 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -331,6 +331,20 @@ void MaterialEditorContextObject::setHasMaterialLibrary(bool b) emit hasMaterialLibraryChanged(); } +bool MaterialEditorContextObject::isQt6Project() const +{ + return m_isQt6Project; +} + +void MaterialEditorContextObject::setIsQt6Project(bool b) +{ + if (m_isQt6Project == b) + return; + + m_isQt6Project = b; + emit isQt6ProjectChanged(); +} + bool MaterialEditorContextObject::hasModelSelection() const { return m_hasModelSelection; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h index 4bb91134d80..8772f3e1d2b 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h @@ -39,6 +39,7 @@ class MaterialEditorContextObject : public QObject Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -96,6 +97,9 @@ public: bool hasMaterialLibrary() const; void setHasMaterialLibrary(bool b); + bool isQt6Project() const; + void setIsQt6Project(bool b); + bool hasModelSelection() const; void setHasModelSelection(bool b); @@ -134,6 +138,7 @@ signals: void hasQuick3DImportChanged(); void hasMaterialLibraryChanged(); void hasModelSelectionChanged(); + void isQt6ProjectChanged(); private: void updatePossibleTypeIndex(); @@ -163,6 +168,7 @@ private: bool m_hasQuick3DImport = false; bool m_hasMaterialLibrary = false; bool m_hasModelSelection = false; + bool m_isQt6Project = false; ModelNode m_selectedMaterial; }; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index ac3a92c5d44..487d300ef97 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -9,6 +9,7 @@ #include "designdocument.h" #include "designmodewidget.h" #include "dynamicpropertiesmodel.h" +#include "externaldependenciesinterface.h" #include "itemlibraryinfo.h" #include "materialeditorqmlbackend.h" #include "materialeditorcontextobject.h" @@ -598,6 +599,7 @@ void MaterialEditorView::setupQmlBackend() currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); currentQmlBackend->contextObject()->setCurrentType(currentTypeName); + currentQmlBackend->contextObject()->setIsQt6Project(externalDependencies().isQt6Project()); m_qmlBackEnd = currentQmlBackend; diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp index a955a81c0ee..1a3814f9618 100644 --- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp @@ -12,10 +12,11 @@ #include "qproxystyle.h" #include +#include #include +#include #include #include -#include #include #include @@ -212,7 +213,7 @@ void NameItemDelegate::paint(QPainter *painter, } ModelNode node = getModelNode(modelIndex); - if (!ModelNode::isThisOrAncestorLocked(node)) { + if (!ModelUtils::isThisOrAncestorLocked(node)) { NavigatorWidget *widget = qobject_cast(styleOption.widget->parent()); if (widget && !widget->dragType().isEmpty()) { QByteArray dragType = widget->dragType(); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 8b7f1f8a61e..6abda00dbac 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -200,7 +200,7 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked; if (role == ItemOrAncestorLocked) - return ModelNode::isThisOrAncestorLocked(modelNode); + return ModelUtils::isThisOrAncestorLocked(modelNode); if (role == ModelNodeRole) return QVariant::fromValue(modelNode); @@ -273,13 +273,13 @@ Qt::ItemFlags NavigatorTreeModel::flags(const QModelIndex &index) const if (index.column() == ColumnType::Alias || index.column() == ColumnType::Visibility || index.column() == ColumnType::Lock) { - if (ModelNode::isThisOrAncestorLocked(modelNode)) + if (ModelUtils::isThisOrAncestorLocked(modelNode)) return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; else return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } - if (ModelNode::isThisOrAncestorLocked(modelNode)) + if (ModelUtils::isThisOrAncestorLocked(modelNode)) return Qt::NoItemFlags; if (index.column() == ColumnType::Name) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index 9a4bbd280e9..59236c4fe21 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -51,7 +51,7 @@ private: PropertyEditorValue *m_editorValue = nullptr; }; -class PropertyEditorValue : public QObject +class QMLDESIGNERCORE_EXPORT PropertyEditorValue : public QObject { Q_OBJECT diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index bf35e2512eb..1fba74f2178 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -65,6 +66,8 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, , m_timerId(0) , m_stackedWidget(new PropertyEditorWidget()) , m_qmlBackEndForCurrentType(nullptr) + , m_propertyComponentGenerator{QmlDesigner::PropertyEditorQmlBackend::propertyEditorResourcesPath(), + model()} , m_locked(false) , m_setupCompleted(false) , m_singleShotTimer(new QTimer(this)) @@ -373,6 +376,11 @@ void PropertyEditorView::currentTimelineChanged(const ModelNode &) m_qmlBackEndForCurrentType->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); } +void PropertyEditorView::refreshMetaInfos(const TypeIds &deletedTypeIds) +{ + m_propertyComponentGenerator.refreshMetaInfos(deletedTypeIds); +} + void PropertyEditorView::updateSize() { if (!m_qmlBackEndForCurrentType) @@ -439,8 +447,8 @@ void PropertyEditorView::resetView() namespace { -std::tuple diffType(const NodeMetaInfo &commonAncestor, - const NodeMetaInfo &specificsClassMetaInfo) +[[maybe_unused]] std::tuple diffType(const NodeMetaInfo &commonAncestor, + const NodeMetaInfo &specificsClassMetaInfo) { NodeMetaInfo diffClassMetaInfo; QUrl qmlSpecificsFile; @@ -463,9 +471,9 @@ std::tuple diffType(const NodeMetaInfo &commonAncestor, return {diffClassMetaInfo, qmlSpecificsFile}; } -QString getSpecificQmlData(const NodeMetaInfo &commonAncestor, - const ModelNode &selectedNode, - const NodeMetaInfo &diffClassMetaInfo) +[[maybe_unused]] QString getSpecificQmlData(const NodeMetaInfo &commonAncestor, + const ModelNode &selectedNode, + const NodeMetaInfo &diffClassMetaInfo) { if (commonAncestor.isValid() && diffClassMetaInfo != selectedNode.metaInfo()) return PropertyEditorQmlBackend::templateGeneration(commonAncestor, @@ -534,33 +542,90 @@ void setupWidget(PropertyEditorQmlBackend *currentQmlBackend, stackedWidget->setCurrentWidget(currentQmlBackend->widget()); currentQmlBackend->contextObject()->triggerSelectionChanged(); } + +[[maybe_unused]] auto findPaneAndSpecificsPath(const NodeMetaInfos &prototypes, + const SourcePathCacheInterface &pathCache) +{ + Utils::PathString panePath; + Utils::PathString specificsPath; + + for (const NodeMetaInfo &prototype : prototypes) { + auto sourceId = prototype.propertyEditorPathId(); + if (sourceId) { + auto path = pathCache.sourcePath(sourceId); + if (path.endsWith("Pane.qml")) { + panePath = path; + if (panePath.size() && specificsPath.size()) + return std::make_tuple(panePath, specificsPath); + } else if (path.endsWith("Specifics.qml")) { + specificsPath = path; + if (panePath.size() && specificsPath.size()) + return std::make_tuple(panePath, specificsPath); + } + } + } + + return std::make_tuple(panePath, specificsPath); +} } // namespace void PropertyEditorView::setupQmlBackend() { - const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(m_selectedNode); + if constexpr (useProjectStorage()) { + auto selfAndPrototypes = m_selectedNode.metaInfo().selfAndPrototypes(); + auto specificQmlData = m_propertyEditorComponentGenerator.create(selfAndPrototypes, + m_selectedNode.isComponent()); + auto [panePath, specificsPath] = findPaneAndSpecificsPath(selfAndPrototypes, + model()->pathCache()); + PropertyEditorQmlBackend *currentQmlBackend = getQmlBackend(m_qmlBackendHash, + QUrl::fromLocalFile( + QString{panePath}), + m_imageCache, + m_stackedWidget, + this); - const auto [qmlFileUrl, specificsClassMetaInfo] = PropertyEditorQmlBackend::getQmlUrlForMetaInfo( - commonAncestor); + setupCurrentQmlBackend(currentQmlBackend, + m_selectedNode, + QUrl::fromLocalFile(QString{specificsPath}), + currentState(), + this, + specificQmlData); - auto [diffClassMetaInfo, qmlSpecificsFile] = diffType(commonAncestor, specificsClassMetaInfo); + setupWidget(currentQmlBackend, this, m_stackedWidget); - QString specificQmlData = getSpecificQmlData(commonAncestor, m_selectedNode, diffClassMetaInfo); + m_qmlBackEndForCurrentType = currentQmlBackend; - PropertyEditorQmlBackend *currentQmlBackend = getQmlBackend(m_qmlBackendHash, - qmlFileUrl, - m_imageCache, - m_stackedWidget, - this); + setupInsight(rootModelNode(), currentQmlBackend); + } else { + const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor( + m_selectedNode); - setupCurrentQmlBackend( - currentQmlBackend, m_selectedNode, qmlSpecificsFile, currentState(), this, specificQmlData); + const auto [qmlFileUrl, specificsClassMetaInfo] = PropertyEditorQmlBackend::getQmlUrlForMetaInfo( + commonAncestor); - setupWidget(currentQmlBackend, this, m_stackedWidget); + auto [diffClassMetaInfo, qmlSpecificsFile] = diffType(commonAncestor, specificsClassMetaInfo); - m_qmlBackEndForCurrentType = currentQmlBackend; + QString specificQmlData = getSpecificQmlData(commonAncestor, m_selectedNode, diffClassMetaInfo); - setupInsight(rootModelNode(), currentQmlBackend); + PropertyEditorQmlBackend *currentQmlBackend = getQmlBackend(m_qmlBackendHash, + qmlFileUrl, + m_imageCache, + m_stackedWidget, + this); + + setupCurrentQmlBackend(currentQmlBackend, + m_selectedNode, + qmlSpecificsFile, + currentState(), + this, + specificQmlData); + + setupWidget(currentQmlBackend, this, m_stackedWidget); + + m_qmlBackEndForCurrentType = currentQmlBackend; + + setupInsight(rootModelNode(), currentQmlBackend); + } } void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) @@ -646,6 +711,9 @@ void PropertyEditorView::modelAttached(Model *model) { AbstractView::modelAttached(model); + if constexpr (useProjectStorage()) + m_propertyComponentGenerator.setModel(model); + if (debug) qDebug() << Q_FUNC_INFO; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index bb2f9dc360c..cc9b5228392 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -9,6 +9,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QEvent; @@ -81,6 +82,8 @@ public: void currentTimelineChanged(const ModelNode &node) override; + void refreshMetaInfos(const TypeIds &deletedTypeIds) override; + protected: void timerEvent(QTimerEvent *event) override; void setupPane(const TypeName &typeName); @@ -113,6 +116,8 @@ private: //variables QString m_qmlDir; QHash m_qmlBackendHash; PropertyEditorQmlBackend *m_qmlBackEndForCurrentType; + PropertyComponentGenerator m_propertyComponentGenerator; + PropertyEditorComponentGenerator m_propertyEditorComponentGenerator{m_propertyComponentGenerator}; bool m_locked; bool m_setupCompleted; QTimer *m_singleShotTimer; diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h index baee63efc1c..4740b01fbda 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h @@ -9,7 +9,7 @@ namespace QmlDesigner { -class QmlModelNodeProxy : public QObject +class QMLDESIGNERCORE_EXPORT QmlModelNodeProxy : public QObject { Q_OBJECT diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index 8d1b03bddd6..73e784846bb 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -159,6 +159,20 @@ void TextureEditorContextObject::setHasMaterialLibrary(bool b) emit hasMaterialLibraryChanged(); } +bool TextureEditorContextObject::isQt6Project() const +{ + return m_isQt6Project; +} + +void TextureEditorContextObject::setIsQt6Project(bool b) +{ + if (m_isQt6Project == b) + return; + + m_isQt6Project = b; + emit isQt6ProjectChanged(); +} + bool TextureEditorContextObject::hasSingleModelSelection() const { return m_hasSingleModelSelection; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h index 595f05e7c91..67cc23e063a 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -39,6 +39,7 @@ class TextureEditorContextObject : public QObject Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection WRITE setHasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -95,6 +96,9 @@ public: bool hasMaterialLibrary() const; void setHasMaterialLibrary(bool b); + bool isQt6Project() const; + void setIsQt6Project(bool b); + bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); @@ -133,6 +137,7 @@ signals: void hasMaterialLibraryChanged(); void hasSingleModelSelectionChanged(); void activeDragSuffixChanged(); + void isQt6ProjectChanged(); private: QUrl m_specificsUrl; @@ -157,6 +162,7 @@ private: bool m_hasQuick3DImport = false; bool m_hasMaterialLibrary = false; bool m_hasSingleModelSelection = false; + bool m_isQt6Project = false; ModelNode m_selectedTexture; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 42d909400c3..d088dd7a26a 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -461,6 +462,7 @@ void TextureEditorView::setupQmlBackend() currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); currentQmlBackend->contextObject()->setHasSingleModelSelection(hasValidSelection); + currentQmlBackend->contextObject()->setIsQt6Project(externalDependencies().isQt6Project()); m_qmlBackEnd = currentQmlBackend; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp index 7a733c37291..b67c70bb0bc 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -301,7 +302,7 @@ void TimelineSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) if (event->button() == Qt::LeftButton) { event->accept(); - if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + if (!ModelUtils::isThisOrAncestorLocked(m_targetNode)) toggleCollapsed(); } } @@ -334,7 +335,7 @@ void TimelineSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + if (!ModelUtils::isThisOrAncestorLocked(m_targetNode)) toggleCollapsed(); } update(); diff --git a/src/plugins/qmldesigner/components/toolbar/toolbar.cpp b/src/plugins/qmldesigner/components/toolbar/toolbar.cpp index c5a6e9745b6..893c1809605 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbar.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbar.cpp @@ -68,6 +68,8 @@ Utils::UniqueObjectPtr ToolBar::create() toolBar->setFloatable(false); toolBar->setMovable(false); + toolBar->setProperty("_q_custom_style_skipolish", true); + toolBar->setContentsMargins(0, 0, 0, 0); auto quickWidget = std::make_unique(); diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index 8458cf8746f..c28ac7d207b 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -503,17 +504,33 @@ void ToolBarBackend::setCurrentStyle(int index) view->resetPuppet(); } +ProjectExplorer::Kit *kitForDisplayName(const QString &displayName) +{ + const auto kits = ProjectExplorer::KitManager::kits(); + + for (auto kit : kits) { + if (kit->displayName() == displayName) + return kit; + } + + return {}; +} + void ToolBarBackend::setCurrentKit(int index) { auto project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return ); - const auto kits = ProjectExplorer::KitManager::kits(); + const auto kits = ToolBarBackend::kits(); - QTC_ASSERT(kits.size() > index, return); + QTC_ASSERT(kits.size() > index, return ); QTC_ASSERT(index >= 0, return ); - const auto kit = kits.at(index); + const auto kitDisplayName = kits.at(index); + + const auto kit = kitForDisplayName(kitDisplayName); + + QTC_ASSERT(kit, return ); auto newTarget = project->target(kit); if (!newTarget) @@ -621,7 +638,10 @@ QStringList ToolBarBackend::kits() const { auto kits = Utils::filtered(ProjectExplorer::KitManager::kits(), [](ProjectExplorer::Kit *kit) { const auto qtVersion = QtSupport::QtKitAspect::qtVersion(kit); + const auto dev = ProjectExplorer::DeviceKitAspect::device(kit); + return kit->isValid() && !kit->isReplacementKit() && qtVersion && qtVersion->isValid() + && dev /*&& kit->isAutoDetected() */; }); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp index a5ec84fad2c..e12b6e915b6 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -349,7 +350,7 @@ void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent if (event->button() == Qt::LeftButton) { event->accept(); - if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + if (!ModelUtils::isThisOrAncestorLocked(m_targetNode)) toggleCollapsed(); } } @@ -382,7 +383,7 @@ void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *ev if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + if (!ModelUtils::isThisOrAncestorLocked(m_targetNode)) toggleCollapsed(); } update(); diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 9035aba0fde..0a3350b693b 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -148,6 +148,8 @@ public: virtual void modelAttached(Model *model); virtual void modelAboutToBeDetached(Model *model); + virtual void refreshMetaInfos(const TypeIds &deletedTypeIds); + virtual void nodeCreated(const ModelNode &createdNode); virtual void nodeAboutToBeRemoved(const ModelNode &removedNode); virtual void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, diff --git a/src/plugins/qmldesigner/designercore/include/annotation.h b/src/plugins/qmldesigner/designercore/include/annotation.h index 2026dddf736..647c1dafda1 100644 --- a/src/plugins/qmldesigner/designercore/include/annotation.h +++ b/src/plugins/qmldesigner/designercore/include/annotation.h @@ -26,8 +26,6 @@ public: GlobalAnnotationStatus(); GlobalAnnotationStatus(Status status); - ~GlobalAnnotationStatus() = default; - void setStatus(int statusId); void setStatus(Status status); Status status() const; diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index 034af72410f..2974d58d0b5 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -88,15 +88,8 @@ public: bool containsEntry(const ItemLibraryEntry &entry); void clearEntries(); - QStringList blacklistImports() const; - QSet priorityImports() const; - - void addBlacklistImports(const QStringList &list); - void addPriorityImports(const QSet &set); - signals: void entriesChanged(); - void priorityImportsChanged(); private: // functions ItemLibraryInfo(QObject *parent = nullptr); @@ -105,9 +98,6 @@ private: // functions private: // variables QHash m_nameToEntryHash; QPointer m_baseInfo; - - QStringList m_blacklistImports; - QSet m_priorityImports; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/metainforeader.h b/src/plugins/qmldesigner/designercore/include/metainforeader.h index 4b4a3837e34..b10c7413c59 100644 --- a/src/plugins/qmldesigner/designercore/include/metainforeader.h +++ b/src/plugins/qmldesigner/designercore/include/metainforeader.h @@ -65,7 +65,6 @@ private: ParserSate readExtraFileElement(const QString &name); void readTypeProperty(const QString &name, const QVariant &value); - void readImportsProperty(const QString &name, const QVariant &value); void readItemLibraryEntryProperty(const QString &name, const QVariant &value); void readPropertyProperty(const QString &name, const QVariant &value); void readQmlSourceProperty(const QString &name, const QVariant &value); diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index c404525a9c0..cc3b4aadf9c 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include @@ -125,11 +127,16 @@ public: const MetaInfo metaInfo() const; MetaInfo metaInfo(); + Module module(Utils::SmallStringView moduleName); NodeMetaInfo metaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; + NodeMetaInfo metaInfo(Module module, + Utils::SmallStringView typeName, + Storage::Version version = Storage::Version{}) const; bool hasNodeMetaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; void setMetaInfo(const MetaInfo &metaInfo); NodeMetaInfo boolMetaInfo() const; + NodeMetaInfo doubleMetaInfo() const; NodeMetaInfo flowViewFlowActionAreaMetaInfo() const; NodeMetaInfo flowViewFlowDecisionMetaInfo() const; NodeMetaInfo flowViewFlowItemMetaInfo() const; diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 676a211cd61..0517599f5be 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -223,9 +223,6 @@ public: bool locked() const; void setLocked(bool value); - static bool isThisOrAncestorLocked(const ModelNode &node); - static ModelNode lowestCommonAncestor(const QList &nodes); - qint32 internalId() const; void setNodeSource(const QString&); diff --git a/src/plugins/qmldesigner/designercore/include/module.h b/src/plugins/qmldesigner/designercore/include/module.h new file mode 100644 index 00000000000..5078f3ff8f5 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/module.h @@ -0,0 +1,35 @@ +// 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 + +namespace QmlDesigner { + +class Module +{ +public: + Module() = default; + + Module(ModuleId moduleId, NotNullPointer projectStorage) + : m_projectStorage{projectStorage} + , m_id{moduleId} + {} + + ModuleId id() const { return m_id; } + + bool isValid() const { return bool(m_id); } + + explicit operator bool() const { return isValid(); } + + const ProjectStorageType &projectStorage() const { return *m_projectStorage; } + + friend bool operator==(Module first, Module second) { return first.m_id == second.m_id; } + +private: + NotNullPointer m_projectStorage = {}; + ModuleId m_id; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index b91b650e50c..02a0c81ca4a 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -197,6 +197,7 @@ public: bool isVector3D() const; bool isVector4D() const; bool isView() const; + bool usesCustomParser() const; bool isEnumeration() const; QString importDirectoryPath() const; diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index cbde96e9926..9f542826156 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -334,35 +334,6 @@ void ItemLibraryInfo::clearEntries() emit entriesChanged(); } -QStringList ItemLibraryInfo::blacklistImports() const -{ - auto list = m_blacklistImports; - if (m_baseInfo) - list.append(m_baseInfo->m_blacklistImports); - return list; -} - -QSet ItemLibraryInfo::priorityImports() const -{ - QSet set = m_priorityImports; - if (m_baseInfo) - set.unite(m_baseInfo->m_priorityImports); - return set; -} - -void ItemLibraryInfo::addBlacklistImports(const QStringList &list) -{ - m_blacklistImports.append(list); -} - -void ItemLibraryInfo::addPriorityImports(const QSet &set) -{ - if (!set.isEmpty()) { - m_priorityImports.unite(set); - emit priorityImportsChanged(); - } -} - void ItemLibraryInfo::setBaseInfo(ItemLibraryInfo *baseInfo) { m_baseInfo = baseInfo; diff --git a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp index a56221f6438..af61ef65735 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp @@ -116,7 +116,8 @@ void MetaInfoReader::propertyDefinition(const QString &name, { switch (parserState()) { case ParsingType: readTypeProperty(name, value); break; - case ParsingImports: readImportsProperty(name, value); break; + case ParsingImports: + break; // not supported anymore case ParsingItemLibrary: readItemLibraryEntryProperty(name, value); break; case ParsingProperty: readPropertyProperty(name, value); break; case ParsingQmlSource: readQmlSourceProperty(name, value); break; @@ -220,24 +221,6 @@ MetaInfoReader::ParserSate MetaInfoReader::readExtraFileElement(const QString &n return Error; } -void MetaInfoReader::readImportsProperty(const QString &name, const QVariant &value) -{ - const auto values = value.toStringList(); - - if (name == "blacklistImports" && !values.isEmpty()) { - m_metaInfo.itemLibraryInfo()->addBlacklistImports(values); - } else if ((name == "priorityImports" || name == "showTagsForImports") && !values.isEmpty()) { - // Flow tags are no longer shown, but the old property is still supported for prioritizing - // imports to keep compatibility with old metainfo files. - m_metaInfo.itemLibraryInfo()->addPriorityImports(Utils::toSet(values)); - } else { - addError(::QmlDesigner::Internal::MetaInfoReader::tr("Unknown property for Imports %1") - .arg(name), - currentSourceLocation()); - setParserState(Error); - } -} - void MetaInfoReader::readTypeProperty(const QString &name, const QVariant &value) { if (name == QLatin1String("name")) { diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index c43a9b2fb45..ef7eca0d8f6 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -719,7 +719,7 @@ PropertyName NodeMetaInfoPrivate::defaultPropertyName() const return PropertyName("data"); } -inline static TypeName stringIdentifier(const TypeName &type, int maj, int min) +static TypeName stringIdentifier(const TypeName &type, int maj, int min) { return type + QByteArray::number(maj) + '_' + QByteArray::number(min); } @@ -729,13 +729,30 @@ std::shared_ptr NodeMetaInfoPrivate::create(Model *model, int major, int minor) { + auto stringfiedType = stringIdentifier(type, major, minor); auto &cache = model->d->nodeMetaInfoCache(); - if (auto found = cache.find(stringIdentifier(type, major, minor)); found != cache.end()) + if (auto found = cache.find(stringfiedType); found != cache.end()) return *found; auto newData = std::make_shared(model, type, major, minor); - if (newData->isValid()) - cache.insert(stringIdentifier(type, major, minor), newData); + + if (!newData->isValid()) + return newData; + + auto stringfiedQualifiedType = stringIdentifier(newData->qualfiedTypeName(), + newData->majorVersion(), + newData->minorVersion()); + + if (auto found = cache.find(stringfiedQualifiedType); found != cache.end()) { + cache.insert(stringfiedType, *found); + return *found; + } + + if (stringfiedQualifiedType != stringfiedType) + cache.insert(stringfiedQualifiedType, newData); + + cache.insert(stringfiedType, newData); + return newData; } @@ -2204,6 +2221,23 @@ bool NodeMetaInfo::isView() const } } +bool NodeMetaInfo::usesCustomParser() const +{ + if constexpr (useProjectStorage()) { + return isValid() && bool(typeData().traits & Storage::TypeTraits::UsesCustomParser); + } else { + if (!isValid()) + return false; + + auto type = typeName(); + return type == "QtQuick.VisualItemModel" || type == "Qt.VisualItemModel" + || type == "QtQuick.VisualDataModel" || type == "Qt.VisualDataModel" + || type == "QtQuick.ListModel" || type == "Qt.ListModel" + || type == "QtQml.Models.ListModel" || type == "QtQuick.XmlListModel" + || type == "Qt.XmlListModel" || type == "QtQml.XmlListModel.XmlListModel"; + } +} + namespace { template @@ -2790,8 +2824,8 @@ bool NodeMetaInfo::isQmlComponent() const auto type = m_privateData->qualfiedTypeName(); return type == "Component" || type == "Qt.Component" || type == "QtQuick.Component" - || type == "QtQml.Component" || type == ".QQmlComponent" - || type == "QQmlComponent"; + || type == "QtQml.Component" || type == ".QQmlComponent" || type == "QQmlComponent" + || type == "QML.Component" || type == "QtQml.Base.Component"; } } @@ -2816,7 +2850,7 @@ bool NodeMetaInfo::isColor() const auto type = m_privateData->qualfiedTypeName(); - return type == "QColor" || type == "color"; + return type == "QColor" || type == "color" || type == "QtQuick.color"; } } diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 17cdffb6f7e..a982d8dc5e5 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -8,12 +8,13 @@ #include "internalnode_p.h" #include "model.h" #include "model_p.h" +#include "modelutils.h" #include "nodeinstanceview.h" #include "nodelistproperty.h" #include "nodemetainfo.h" +#include "qmldesignerconstants.h" #include "qmlstate.h" #include "qmltimeline.h" -#include "qmldesignerconstants.h" #include "rewritertransaction.h" #include "variantproperty.h" @@ -163,6 +164,8 @@ void AbstractView::modelAboutToBeDetached(Model *) removeModel(); } +void AbstractView::refreshMetaInfos(const TypeIds &) {} + /*! \enum QmlDesigner::AbstractView::PropertyChangeFlag @@ -412,7 +415,7 @@ void AbstractView::setSelectedModelNodes(const QList &selectedNodeLis QList unlockedNodes; for (const auto &modelNode : selectedNodeList) { - if (!ModelNode::isThisOrAncestorLocked(modelNode)) + if (!ModelUtils::isThisOrAncestorLocked(modelNode)) unlockedNodes.push_back(modelNode); } @@ -421,7 +424,7 @@ void AbstractView::setSelectedModelNodes(const QList &selectedNodeLis void AbstractView::setSelectedModelNode(const ModelNode &modelNode) { - if (ModelNode::isThisOrAncestorLocked(modelNode)) { + if (ModelUtils::isThisOrAncestorLocked(modelNode)) { clearSelectedModelNodes(); return; } diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index d511ae5d64d..7b5868d6cf5 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -82,6 +82,9 @@ ModelPrivate::ModelPrivate(Model *model, m_currentStateNode = m_rootInternalNode; m_currentTimelineNode = m_rootInternalNode; + + if constexpr (useProjectStorage()) + projectStorage->addRefreshCallback(&m_metaInfoRefreshCallback); } ModelPrivate::ModelPrivate(Model *model, @@ -103,6 +106,9 @@ ModelPrivate::ModelPrivate(Model *model, m_currentStateNode = m_rootInternalNode; m_currentTimelineNode = m_rootInternalNode; + + if constexpr (useProjectStorage()) + projectStorage->addRefreshCallback(&m_metaInfoRefreshCallback); } ModelPrivate::ModelPrivate(Model *model, @@ -123,10 +129,17 @@ ModelPrivate::ModelPrivate(Model *model, m_currentTimelineNode = m_rootInternalNode; } -ModelPrivate::~ModelPrivate() = default; +ModelPrivate::~ModelPrivate() +{ + if constexpr (useProjectStorage()) + projectStorage->removeRefreshCallback(&m_metaInfoRefreshCallback); +}; void ModelPrivate::detachAllViews() { + if constexpr (useProjectStorage()) + projectStorage->removeRefreshCallback(&m_metaInfoRefreshCallback); + for (const QPointer &view : std::as_const(m_viewList)) detachView(view.data(), true); @@ -381,6 +394,11 @@ void ModelPrivate::setTypeId(InternalNode *node, Utils::SmallStringView typeName } } +void ModelPrivate::emitRefreshMetaInfos(const TypeIds &deletedTypeIds) +{ + notifyNodeInstanceViewLast([&](AbstractView *view) { view->refreshMetaInfos(deletedTypeIds); }); +} + void ModelPrivate::handleResourceSet(const ModelResourceSet &resourceSet) { for (const ModelNode &node : resourceSet.removeModelNodes) { @@ -2071,6 +2089,16 @@ NodeMetaInfo Model::boolMetaInfo() const } } +NodeMetaInfo Model::doubleMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QML.double"); + } +} + template NodeMetaInfo Model::createNodeMetaInfo() const { @@ -2422,6 +2450,16 @@ NodeMetaInfo Model::metaInfo(const TypeName &typeName, int majorVersion, int min } } +NodeMetaInfo Model::metaInfo(Module module, Utils::SmallStringView typeName, Storage::Version version) const +{ + if constexpr (useProjectStorage()) { + return NodeMetaInfo(d->projectStorage->typeId(module.id(), typeName, version), + d->projectStorage); + } else { + return {}; + } +} + /*! \brief Returns list of QML types available within the model. */ @@ -2430,6 +2468,15 @@ MetaInfo Model::metaInfo() return d->metaInfo(); } +Module Model::module(Utils::SmallStringView moduleName) +{ + if constexpr (useProjectStorage()) { + return Module(d->projectStorage->moduleId(moduleName), d->projectStorage); + } + + return {}; +} + /*! \name View related functions */ //\{ diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index 8ba7c69b595..d09e4054ca2 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -19,6 +19,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QPlainTextEdit; @@ -111,6 +112,9 @@ public: ~ModelPrivate() override; + ModelPrivate(const ModelPrivate &) = delete; + ModelPrivate &operator=(const ModelPrivate &) = delete; + QUrl fileUrl() const; void setFileUrl(const QUrl &url); @@ -313,6 +317,7 @@ private: EnabledViewRange enabledViews() const; ImportedTypeNameId importedTypeNameId(Utils::SmallStringView typeName); void setTypeId(InternalNode *node, Utils::SmallStringView typeName); + void emitRefreshMetaInfos(const TypeIds &deletedTypeIds); public: NotNullPointer projectStorage = nullptr; @@ -321,6 +326,8 @@ public: private: Model *m_model = nullptr; MetaInfo m_metaInfo; + std::function m_metaInfoRefreshCallback{ + [&](const TypeIds &deletedTypeIds) { emitRefreshMetaInfos(deletedTypeIds); }}; Imports m_imports; Imports m_possibleImportList; Imports m_usedImportList; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index c285161e833..fe973f04abd 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -745,7 +745,7 @@ bool ModelNode::isRootNode() const if (!isValid()) return false; - return view()->rootModelNode() == *this; + return m_model->d->rootNode() == m_internalNode; } /*! \brief returns the list of all property names @@ -1143,7 +1143,7 @@ void ModelNode::removeAnnotation() Annotation ModelNode::globalAnnotation() const { Annotation result; - ModelNode root = view()->rootModelNode(); + ModelNode root = m_model->rootModelNode(); auto data = root.auxiliaryData(globalAnnotationProperty); @@ -1155,24 +1155,24 @@ Annotation ModelNode::globalAnnotation() const bool ModelNode::hasGlobalAnnotation() const { - return view()->rootModelNode().hasAuxiliaryData(globalAnnotationProperty); + return m_model->rootModelNode().hasAuxiliaryData(globalAnnotationProperty); } void ModelNode::setGlobalAnnotation(const Annotation &annotation) { - view()->rootModelNode().setAuxiliaryData(globalAnnotationProperty, - QVariant::fromValue(annotation.toQString())); + m_model->rootModelNode().setAuxiliaryData(globalAnnotationProperty, + QVariant::fromValue(annotation.toQString())); } void ModelNode::removeGlobalAnnotation() { - view()->rootModelNode().removeAuxiliaryData(globalAnnotationProperty); + m_model->rootModelNode().removeAuxiliaryData(globalAnnotationProperty); } GlobalAnnotationStatus ModelNode::globalStatus() const { GlobalAnnotationStatus result; - ModelNode root = view()->rootModelNode(); + ModelNode root = m_model->rootModelNode(); auto data = root.auxiliaryData(globalAnnotationStatus); @@ -1184,19 +1184,19 @@ GlobalAnnotationStatus ModelNode::globalStatus() const bool ModelNode::hasGlobalStatus() const { - return view()->rootModelNode().hasAuxiliaryData(globalAnnotationStatus); + return m_model->rootModelNode().hasAuxiliaryData(globalAnnotationStatus); } void ModelNode::setGlobalStatus(const GlobalAnnotationStatus &status) { - view()->rootModelNode().setAuxiliaryData(globalAnnotationStatus, - QVariant::fromValue(status.toQString())); + m_model->rootModelNode().setAuxiliaryData(globalAnnotationStatus, + QVariant::fromValue(status.toQString())); } void ModelNode::removeGlobalStatus() { if (hasGlobalStatus()) { - view()->rootModelNode().removeAuxiliaryData(globalAnnotationStatus); + m_model->rootModelNode().removeAuxiliaryData(globalAnnotationStatus); } } @@ -1230,108 +1230,6 @@ void ModelNode::setLocked(bool value) } } -bool ModelNode::isThisOrAncestorLocked(const ModelNode &node) -{ - if (!node.isValid()) - return false; - - if (node.locked()) - return true; - - if (node.isRootNode() || !node.hasParentProperty()) - return false; - - return isThisOrAncestorLocked(node.parentProperty().parentModelNode()); -} - -/*! - * \brief The lowest common ancestor node for node1 and node2. If one of the nodes (Node A) is - * the ancestor of the other node, the return value is Node A and not the parent of Node A. - * \param node1 First node - * \param node2 Second node - * \param depthOfLCA Depth of the return value - * \param depthOfNode1 Depth of node1. Use this parameter for optimization - * \param depthOfNode2 Depth of node2. Use this parameter for optimization - */ -static ModelNode lowestCommonAncestor(const ModelNode &node1, - const ModelNode &node2, - int &depthOfLCA, - const int &depthOfNode1 = -1, - const int &depthOfNode2 = -1) -{ - Q_ASSERT(node1.isValid() && node2.isValid()); - - auto depthOfNode = [](const ModelNode &node) -> int { - int depth = 0; - ModelNode parentNode = node; - while (!parentNode.isRootNode()) { - depth++; - parentNode = parentNode.parentProperty().parentModelNode(); - } - return depth; - }; - - if (node1 == node2) { - depthOfLCA = (depthOfNode1 < 0) ? ((depthOfNode2 < 0) ? depthOfNode(node1) : depthOfNode2) - : depthOfNode1; - return node1; - } - - if (node1.isRootNode()) { - depthOfLCA = 0; - return node1; - } - - if (node2.isRootNode()) { - depthOfLCA = 0; - return node2; - } - - ModelNode nodeLower = node1; - ModelNode nodeHigher = node2; - int depthLower = (depthOfNode1 < 0) ? depthOfNode(nodeLower) : depthOfNode1; - int depthHigher = (depthOfNode2 < 0) ? depthOfNode(nodeHigher) : depthOfNode2; - - if (depthLower > depthHigher) { - std::swap(depthLower, depthHigher); - std::swap(nodeLower, nodeHigher); - } - - int depthDiff = depthHigher - depthLower; - while (depthDiff--) - nodeHigher = nodeHigher.parentProperty().parentModelNode(); - - while (nodeLower != nodeHigher) { - nodeLower = nodeLower.parentProperty().parentModelNode(); - nodeHigher = nodeHigher.parentProperty().parentModelNode(); - --depthLower; - } - - depthOfLCA = depthLower; - return nodeLower; -} - -/*! - * \brief The lowest common node containing all nodes. If one of the nodes (Node A) is - * the ancestor of the other nodes, the return value is Node A and not the parent of Node A. - */ -ModelNode ModelNode::lowestCommonAncestor(const QList &nodes) -{ - if (nodes.isEmpty()) - return {}; - - ModelNode accumulatedNode = nodes.first(); - int accumulatedNodeDepth = -1; - for (const ModelNode &node : Utils::span(nodes).subspan(1)) { - accumulatedNode = QmlDesigner::lowestCommonAncestor(accumulatedNode, - node, - accumulatedNodeDepth, - accumulatedNodeDepth); - } - - return accumulatedNode; -} - void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList) { if (!isValid()) diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.cpp b/src/plugins/qmldesigner/designercore/model/modelutils.cpp index e1fe3f3cadc..cb3f482289f 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelutils.cpp @@ -4,6 +4,7 @@ #include "modelutils.h" #include +#include #include #include #include @@ -162,4 +163,114 @@ QList allModelNodesWithId(AbstractView *view) [&](const ModelNode &node) { return node.hasId(); }); } +bool isThisOrAncestorLocked(const ModelNode &node) +{ + if (!node.isValid()) + return false; + + if (node.locked()) + return true; + + if (node.isRootNode() || !node.hasParentProperty()) + return false; + + return isThisOrAncestorLocked(node.parentProperty().parentModelNode()); +} + +/*! + * \brief The lowest common ancestor node for node1 and node2. If one of the nodes (Node A) is + * the ancestor of the other node, the return value is Node A and not the parent of Node A. + * \param node1 First node + * \param node2 Second node + * \param depthOfLCA Depth of the return value + * \param depthOfNode1 Depth of node1. Use this parameter for optimization + * \param depthOfNode2 Depth of node2. Use this parameter for optimization + */ +namespace { +ModelNode lowestCommonAncestor(const ModelNode &node1, + const ModelNode &node2, + int &depthOfLCA, + const int &depthOfNode1 = -1, + const int &depthOfNode2 = -1) +{ + auto depthOfNode = [](const ModelNode &node) -> int { + int depth = 0; + ModelNode parentNode = node; + while (parentNode && !parentNode.isRootNode()) { + depth++; + parentNode = parentNode.parentProperty().parentModelNode(); + } + + return depth; + }; + + if (node1 == node2) { + depthOfLCA = (depthOfNode1 < 0) ? ((depthOfNode2 < 0) ? depthOfNode(node1) : depthOfNode2) + : depthOfNode1; + return node1; + } + + if (node1.model() != node2.model()) { + depthOfLCA = -1; + return {}; + } + + if (node1.isRootNode()) { + depthOfLCA = 0; + return node1; + } + + if (node2.isRootNode()) { + depthOfLCA = 0; + return node2; + } + + ModelNode nodeLower = node1; + ModelNode nodeHigher = node2; + int depthLower = (depthOfNode1 < 0) ? depthOfNode(nodeLower) : depthOfNode1; + int depthHigher = (depthOfNode2 < 0) ? depthOfNode(nodeHigher) : depthOfNode2; + + if (depthLower > depthHigher) { + std::swap(depthLower, depthHigher); + std::swap(nodeLower, nodeHigher); + } + + int depthDiff = depthHigher - depthLower; + while (depthDiff--) + nodeHigher = nodeHigher.parentProperty().parentModelNode(); + + while (nodeLower != nodeHigher) { + nodeLower = nodeLower.parentProperty().parentModelNode(); + nodeHigher = nodeHigher.parentProperty().parentModelNode(); + --depthLower; + } + + depthOfLCA = depthLower; + return nodeLower; +} +} // namespace + +/*! + * \brief The lowest common node containing all nodes. If one of the nodes (Node A) is + * the ancestor of the other nodes, the return value is Node A and not the parent of Node A. + */ +ModelNode lowestCommonAncestor(Utils::span nodes) +{ + if (nodes.empty()) + return {}; + + ModelNode accumulatedNode = nodes.front(); + int accumulatedNodeDepth = -1; + for (const ModelNode &node : nodes.subspan(1)) { + accumulatedNode = lowestCommonAncestor(accumulatedNode, + node, + accumulatedNodeDepth, + accumulatedNodeDepth); + if (!accumulatedNode) + return {}; + } + + return accumulatedNode; +} + } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.h b/src/plugins/qmldesigner/designercore/model/modelutils.h index ee6d3e41303..3f8ddeab56b 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.h +++ b/src/plugins/qmldesigner/designercore/model/modelutils.h @@ -38,4 +38,7 @@ QMLDESIGNERCORE_EXPORT QList pruneChildren(const QList &no QMLDESIGNERCORE_EXPORT QList allModelNodesWithId(AbstractView *view); +QMLDESIGNERCORE_EXPORT bool isThisOrAncestorLocked(const ModelNode &node); +QMLDESIGNERCORE_EXPORT ModelNode lowestCommonAncestor(Utils::span nodes); + } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index ea344d3e62b..68b6426b8fa 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -1029,6 +1029,11 @@ void RewriterView::moveToComponent(const ModelNode &modelNode) const QList nodes = modelNode.allSubModelNodesAndThisNode(); QSet directPaths; + // Always add QtQuick import + QString quickImport = model()->qtQuickItemMetaInfo().requiredImportString(); + if (!quickImport.isEmpty()) + directPaths.insert(quickImport); + for (const ModelNode &partialNode : nodes) { QString importStr = partialNode.metaInfo().requiredImportString(); if (importStr.size()) diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index dd4fe3b2555..def00f395d3 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -19,6 +19,7 @@ #include "rewriterview.h" #include "signalhandlerproperty.h" #include "variantproperty.h" + #include #include #include @@ -295,23 +296,6 @@ bool isListElementType(const QmlDesigner::TypeName &type) return type == "ListElement" || type == "QtQuick.ListElement" || type == "Qt.ListElement"; } -bool isComponentType(const QmlDesigner::TypeName &type) -{ - return type == "Component" || type == "Qt.Component" || type == "QtQuick.Component" - || type == "QtQml.Component" || type == ".QQmlComponent" || type == "QQmlComponent" - || type == "QML.Component" || type == "QtQml.Base.Component"; -} - -bool isCustomParserType(const QmlDesigner::TypeName &type) -{ - return type == "QtQuick.VisualItemModel" || type == "Qt.VisualItemModel" - || type == "QtQuick.VisualDataModel" || type == "Qt.VisualDataModel" - || type == "QtQuick.ListModel" || type == "Qt.ListModel" - || type == "QtQml.Models.ListModel" || type == "QtQuick.XmlListModel" - || type == "Qt.XmlListModel"; -} - - bool isPropertyChangesType(const QmlDesigner::TypeName &type) { return type == "PropertyChanges" || type == "QtQuick.PropertyChanges" || type == "Qt.PropertyChanges"; @@ -323,9 +307,10 @@ bool isConnectionsType(const QmlDesigner::TypeName &type) || type == "QtQml.Connections" || type == "QtQml.Base.Connections"; } -bool propertyIsComponentType(const QmlDesigner::NodeAbstractProperty &property, const QmlDesigner::TypeName &type, QmlDesigner::Model *model) +bool propertyHasImplicitComponentType(const QmlDesigner::NodeAbstractProperty &property, + const QmlDesigner::NodeMetaInfo &type) { - if (model->metaInfo(type).isQmlComponent() && !isComponentType(type)) + if (type.isQmlComponent()) return false; //If the type is already a subclass of Component keep it return property.parentModelNode().isValid() @@ -426,6 +411,11 @@ bool equals(const QVariant &a, const QVariant &b) return false; } +bool usesCustomParserButIsNotPropertyChange(const QmlDesigner::NodeMetaInfo &nodeMetaInfo) +{ + return nodeMetaInfo.usesCustomParser() && !nodeMetaInfo.isQtQuickPropertyChanges(); +} + } // anonymous namespace namespace QmlDesigner { @@ -454,21 +444,20 @@ public: void enterScope(AST::Node *node) { m_scopeBuilder.push(node); } - void leaveScope() - { m_scopeBuilder.pop(); } + void leaveScope() { m_scopeBuilder.pop(); } - NodeMetaInfo lookup(AST::UiQualifiedId *astTypeNode) + std::tuple lookup(AST::UiQualifiedId *astTypeNode) { TypeName fullTypeName; for (AST::UiQualifiedId *iter = astTypeNode; iter; iter = iter->next) - if (!iter->name.isEmpty()) - fullTypeName += iter->name.toUtf8() + '.'; - - if (fullTypeName.endsWith('.')) - fullTypeName.chop(1); + if (!iter->name.isEmpty()) { + fullTypeName += iter->name.toUtf8(); + if (iter->next) + fullTypeName += '.'; + } NodeMetaInfo metaInfo = m_model->metaInfo(fullTypeName); - return metaInfo; + return {metaInfo, fullTypeName}; } bool lookupProperty(const QString &propertyPrefix, @@ -828,15 +817,6 @@ void TextToModelMerger::setupImports(const Document::Ptr &doc, namespace { -bool skipByMetaInfo(QStringView moduleName, const QStringList &skipModuleNames) -{ - return std::any_of(skipModuleNames.begin(), - skipModuleNames.end(), - [&](const QString &skipModuleName) { - return moduleName.contains(skipModuleName); - }); -} - class StartsWith : public QStringView { public: @@ -876,7 +856,7 @@ constexpr auto skipModules = std::make_tuple(EndsWith(u".impl"), EndsWith(u".private"), EndsWith(u".Private"), Equals(u"QtQuick.Particles"), - Equals(u"QtQuick.Dialogs"), + StartsWith(u"QtQuick.Dialogs"), Equals(u"QtQuick.Controls.Styles"), Equals(u"QtNfc"), Equals(u"Qt.WebSockets"), @@ -886,7 +866,51 @@ constexpr auto skipModules = std::make_tuple(EndsWith(u".impl"), Equals(u"QtWinExtras"), Equals(u"QtPurchasing"), Equals(u"QtBluetooth"), - Equals(u"Enginio")); + Equals(u"Enginio"), + StartsWith(u"Qt.labs."), + StartsWith(u"Qt.test.controls"), + StartsWith(u"QmlTime"), + StartsWith(u"Qt.labs."), + StartsWith(u"Qt.test.controls"), + StartsWith(u"Qt3D."), + StartsWith(u"Qt5Compat.GraphicalEffects"), + StartsWith(u"QtCanvas3D"), + StartsWith(u"QtCore"), + StartsWith(u"QtDataVisualization"), + StartsWith(u"QtGamepad"), + StartsWith(u"QtOpcUa"), + StartsWith(u"QtPositioning"), + Equals(u"QtQuick.Controls.Basic"), + Equals(u"QtQuick.Controls.Fusion"), + Equals(u"QtQuick.Controls.Imagine"), + Equals(u"QtQuick.Controls.Material"), + Equals(u"QtQuick.Controls.NativeStyle"), + Equals(u"QtQuick.Controls.Universal"), + Equals(u"QtQuick.Controls.Windows"), + StartsWith(u"QtQuick.LocalStorage"), + StartsWith(u"QtQuick.NativeStyle"), + StartsWith(u"QtQuick.Pdf"), + StartsWith(u"QtQuick.Scene2D"), + StartsWith(u"QtQuick.Scene3D"), + StartsWith(u"QtQuick.Shapes"), + StartsWith(u"QtQuick.Studio.EventSimulator"), + StartsWith(u"QtQuick.Studio.EventSystem"), + StartsWith(u"QtQuick.Templates"), + StartsWith(u"QtQuick.VirtualKeyboard"), + StartsWith(u"QtQuick.tooling"), + StartsWith( + u"QtQuick3D MateriablacklistImportslEditor"), + StartsWith(u"QtQuick3D.ParticleEffects"), + StartsWith(u"QtRemoteObjects"), + StartsWith(u"QtRemoveObjects"), + StartsWith(u"QtScxml"), + StartsWith(u"QtSensors"), + StartsWith(u"QtTest"), + StartsWith(u"QtTextToSpeech"), + StartsWith(u"QtVncServer"), + StartsWith(u"QtWebEngine"), + StartsWith(u"QtWebSockets"), + StartsWith(u"QtWebView")); bool skipModule(QStringView moduleName) { @@ -894,11 +918,6 @@ bool skipModule(QStringView moduleName) skipModules); } -bool skipModule(QStringView moduleName, const QStringList &skipModuleNames) -{ - return skipModule(moduleName) || skipByMetaInfo(moduleName, skipModuleNames); -} - void collectPossibleFileImports(const QString &checkPath, QSet usedImportsSet, QList &possibleImports) @@ -975,12 +994,8 @@ void TextToModelMerger::setupPossibleImports() auto &externalDependencies = m_rewriterView->externalDependencies(); if (externalDependencies.isQt6Project()) { - const auto skipModuleNames = m_rewriterView->model() - ->metaInfo() - .itemLibraryInfo() - ->blacklistImports(); ModuleScanner moduleScanner{[&](QStringView moduleName) { - return skipModule(moduleName, skipModuleNames); + return skipModule(moduleName); }, VersionScanning::No, m_rewriterView->externalDependencies()}; @@ -1214,21 +1229,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, m_rewriterView->positionStorage()->setNodeOffset(modelNode, astObjectType->identifierToken.offset); - NodeMetaInfo info = context->lookup(astObjectType); + auto [info, typeName] = context->lookup(astObjectType); if (!info.isValid()) { - qWarning() << "Skipping node with unknown type" << toString(astObjectType) << info.typeName(); + qWarning() << "Skipping node with unknown type" << toString(astObjectType); return; } int majorVersion = info.majorVersion(); int minorVersion = info.minorVersion(); - TypeName typeName = info.typeName(); - PropertyName defaultPropertyName = info.defaultPropertyName(); - - if (defaultPropertyName.isEmpty()) //fallback and use the meta system of the model - defaultPropertyName = modelNode.metaInfo().defaultPropertyName(); - if (modelNode.isRootNode() && !m_rewriterView->allowComponentRoot() && info.isQmlComponent()) { for (AST::UiObjectMemberList *iter = astInitializer->members; iter; iter = iter->next) { if (auto def = AST::cast(iter->member)) { @@ -1238,16 +1247,16 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, } } - bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model()); + bool isImplicitComponent = modelNode.hasParentProperty() + && propertyHasImplicitComponentType(modelNode.parentProperty(), info); - if (modelNode.type() - != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then. + if (modelNode.metaInfo() + != info //If there is no valid parentProperty //the node has just been created. The type is correct then. || modelNode.majorVersion() != majorVersion || modelNode.minorVersion() != minorVersion || modelNode.behaviorPropertyName() != onTokenProperty) { const bool isRootNode = m_rewriterView->rootModelNode() == modelNode; - differenceHandler.typeDiffers(isRootNode, modelNode, typeName, - majorVersion, minorVersion, - astNode, context); + differenceHandler.typeDiffers( + isRootNode, modelNode, info, typeName, majorVersion, minorVersion, astNode, context); if (!modelNode.isValid()) return; @@ -1259,9 +1268,9 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, } } - if (isComponentType(typeName) || isImplicitComponent) + if (info.isQmlComponent() || isImplicitComponent) setupComponentDelayed(modelNode, differenceHandler.isAmender()); - else if (isCustomParserType(typeName)) + else if (info.usesCustomParser()) setupCustomParserNodeDelayed(modelNode, differenceHandler.isAmender()); else if (!modelNode.nodeSource().isEmpty() || modelNode.nodeSourceType() != ModelNode::NodeWithoutSource) clearImplicitComponentDelayed(modelNode, differenceHandler.isAmender()); @@ -1388,8 +1397,13 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, } } + PropertyName defaultPropertyName = info.defaultPropertyName(); + + if (defaultPropertyName.isEmpty()) //fallback and use the meta system of the model + defaultPropertyName = modelNode.metaInfo().defaultPropertyName(); + if (!defaultPropertyItems.isEmpty()) { - if (isComponentType(modelNode.type())) + if (modelNode.metaInfo().isQmlComponent()) setupComponentDelayed(modelNode, differenceHandler.isAmender()); if (defaultPropertyName.isEmpty()) { qWarning() << "No default property for node type" << modelNode.type() << ", ignoring child items."; @@ -1575,22 +1589,28 @@ void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty, const TypeName &dynamicPropertyType, DifferenceHandler &differenceHandler) { - NodeMetaInfo info = context->lookup(binding->qualifiedTypeNameId); + auto [info, typeName] = context->lookup(binding->qualifiedTypeNameId); if (!info.isValid()) { qWarning() << "SNP" << "Skipping node with unknown type" << toString(binding->qualifiedTypeNameId); return; } - TypeName typeName = info.typeName(); - int majorVersion = info.majorVersion(); - int minorVersion = info.minorVersion(); + + int majorVersion = -1; + int minorVersion = -1; + if constexpr (!useProjectStorage()) { + typeName = info.typeName(); + majorVersion = info.majorVersion(); + minorVersion = info.minorVersion(); + } if (modelProperty.isNodeProperty() && dynamicPropertyType == modelProperty.dynamicTypeName()) { ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode(); syncNode(nodePropertyNode, binding, context, differenceHandler); } else { differenceHandler.shouldBeNodeProperty(modelProperty, + info, typeName, majorVersion, minorVersion, @@ -1728,7 +1748,8 @@ void TextToModelMerger::syncNodeListProperty(NodeListProperty &modelListProperty } } -ModelNode TextToModelMerger::createModelNode(const TypeName &typeName, +ModelNode TextToModelMerger::createModelNode(const NodeMetaInfo &nodeMetaInfo, + const TypeName &typeName, int majorVersion, int minorVersion, bool isImplicitComponent, @@ -1748,13 +1769,17 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName, AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode); - if (isCustomParserType(typeName)) + bool useCustomParser = usesCustomParserButIsNotPropertyChange(nodeMetaInfo); + + if (useCustomParser) { nodeSource = textAt(context->doc(), - astObjectType->identifierToken, - astNode->lastSourceLocation()); + astObjectType->identifierToken, + astNode->lastSourceLocation()); + } + bool isQmlComponent = nodeMetaInfo.isQmlComponent(); - if (isComponentType(typeName) || isImplicitComponent) { + if (isQmlComponent || isImplicitComponent) { QString componentSource = extractComponentFromQml(textAt(context->doc(), astObjectType->identifierToken, astNode->lastSourceLocation())); @@ -1765,9 +1790,9 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName, ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource; - if (isComponentType(typeName) || isImplicitComponent) + if (isQmlComponent || isImplicitComponent) nodeSourceType = ModelNode::NodeWithComponentSource; - else if (isCustomParserType(typeName)) + else if (useCustomParser) nodeSourceType = ModelNode::NodeWithCustomParserSource; ModelNode newNode = m_rewriterView->createModelNode(typeName, @@ -1892,6 +1917,7 @@ void ModelValidator::shouldBeVariantProperty([[maybe_unused]] AbstractProperty & } void ModelValidator::shouldBeNodeProperty([[maybe_unused]] AbstractProperty &modelProperty, + const NodeMetaInfo &, const TypeName & /*typeName*/, int /*majorVersion*/, int /*minorVersion*/, @@ -1918,10 +1944,11 @@ ModelNode ModelValidator::listPropertyMissingModelNode(NodeListProperty &/*model } void ModelValidator::typeDiffers(bool /*isRootNode*/, - [[maybe_unused]] ModelNode &modelNode, - [[maybe_unused]] const TypeName &typeName, - [[maybe_unused]] int majorVersion, - [[maybe_unused]] int minorVersion, + ModelNode &modelNode, + const NodeMetaInfo &, + const TypeName &typeName, + int majorVersion, + int minorVersion, QmlJS::AST::UiObjectMember * /*astNode*/, ReadingContext * /*context*/) { @@ -2050,6 +2077,7 @@ void ModelAmender::shouldBeVariantProperty(AbstractProperty &modelProperty, cons } void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, @@ -2060,15 +2088,17 @@ void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty, ModelNode theNode = modelProperty.parentModelNode(); NodeProperty newNodeProperty = theNode.nodeProperty(modelProperty.name()); - const bool propertyTakesComponent = propertyIsComponentType(newNodeProperty, typeName, theNode.model()); + const bool propertyTakesComponent = propertyHasImplicitComponentType(newNodeProperty, + nodeMetaInfo); - const ModelNode &newNode = m_merger->createModelNode(typeName, - majorVersion, - minorVersion, - propertyTakesComponent, - astNode, - context, - *this); + const ModelNode &newNode = m_merger->createModelNode(nodeMetaInfo, + typeName, + majorVersion, + minorVersion, + propertyTakesComponent, + astNode, + context, + *this); if (dynamicPropertyType.isEmpty()) newNodeProperty.setModelNode(newNode); @@ -2102,31 +2132,30 @@ ModelNode ModelAmender::listPropertyMissingModelNode(NodeListProperty &modelProp if (!astObjectType || !astInitializer) return ModelNode(); - NodeMetaInfo info = context->lookup(astObjectType); + auto [info, typeName] = context->lookup(astObjectType); if (!info.isValid()) { qWarning() << "Skipping node with unknown type" << toString(astObjectType); return {}; } - TypeName typeName = info.typeName(); - int majorVersion = info.majorVersion(); - int minorVersion = info.minorVersion(); - const bool propertyTakesComponent = propertyIsComponentType(modelProperty, typeName, m_merger->view()->model()); + int majorVersion = -1; + int minorVersion = -1; + if constexpr (!useProjectStorage()) { + typeName = info.typeName(); + majorVersion = info.majorVersion(); + minorVersion = info.minorVersion(); + } + const bool propertyTakesComponent = propertyHasImplicitComponentType(modelProperty, info); - const ModelNode &newNode = m_merger->createModelNode(typeName, - majorVersion, - minorVersion, - propertyTakesComponent, - arrayMember, - context, - *this); - + const ModelNode &newNode = m_merger->createModelNode( + info, typeName, majorVersion, minorVersion, propertyTakesComponent, arrayMember, context, *this); if (propertyTakesComponent) m_merger->setupComponentDelayed(newNode, true); - if (modelProperty.isDefaultProperty() || isComponentType(modelProperty.parentModelNode().type())) { //In the default property case we do some magic + if (modelProperty.isDefaultProperty() + || modelProperty.parentModelNode().metaInfo().isQmlComponent()) { //In the default property case we do some magic if (modelProperty.isNodeListProperty()) { modelProperty.reparentHere(newNode); } else { //The default property could a NodeProperty implicitly (delegate:) @@ -2141,13 +2170,16 @@ ModelNode ModelAmender::listPropertyMissingModelNode(NodeListProperty &modelProp void ModelAmender::typeDiffers(bool isRootNode, ModelNode &modelNode, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, AST::UiObjectMember *astNode, ReadingContext *context) { - const bool propertyTakesComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model()); + const bool propertyTakesComponent = modelNode.hasParentProperty() + && propertyHasImplicitComponentType(modelNode.parentProperty(), + nodeMetaInfo); if (isRootNode) { modelNode.view()->changeRootNodeType(typeName, majorVersion, minorVersion); @@ -2155,13 +2187,14 @@ void ModelAmender::typeDiffers(bool isRootNode, NodeAbstractProperty parentProperty = modelNode.parentProperty(); int nodeIndex = -1; if (parentProperty.isNodeListProperty()) { - nodeIndex = parentProperty.toNodeListProperty().toModelNodeList().indexOf(modelNode); + nodeIndex = parentProperty.toNodeListProperty().indexOf(modelNode); Q_ASSERT(nodeIndex >= 0); } removeModelNode(modelNode); - const ModelNode &newNode = m_merger->createModelNode(typeName, + const ModelNode &newNode = m_merger->createModelNode(nodeMetaInfo, + typeName, majorVersion, minorVersion, propertyTakesComponent, @@ -2170,7 +2203,7 @@ void ModelAmender::typeDiffers(bool isRootNode, *this); parentProperty.reparentHere(newNode); if (parentProperty.isNodeListProperty()) { - int currentIndex = parentProperty.toNodeListProperty().toModelNodeList().indexOf(newNode); + int currentIndex = parentProperty.toNodeListProperty().indexOf(newNode); if (nodeIndex != currentIndex) parentProperty.toNodeListProperty().slide(currentIndex, nodeIndex); } @@ -2367,7 +2400,7 @@ void TextToModelMerger::setupCustomParserNode(const ModelNode &node) void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchronous) { - Q_ASSERT(isCustomParserType(node.type())); + Q_ASSERT(usesCustomParserButIsNotPropertyChange(node.metaInfo())); if (synchronous) { setupCustomParserNode(node); @@ -2379,7 +2412,7 @@ void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool void TextToModelMerger::clearImplicitComponentDelayed(const ModelNode &node, bool synchronous) { - Q_ASSERT(!isComponentType(node.type())); + Q_ASSERT(!usesCustomParserButIsNotPropertyChange(node.metaInfo())); if (synchronous) { clearImplicitComponent(node); diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h index 50b3a423678..f5119060405 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h @@ -95,7 +95,8 @@ public: const QList arrayMembers, ReadingContext *context, DifferenceHandler &differenceHandler); - ModelNode createModelNode(const TypeName &typeName, + ModelNode createModelNode(const QmlDesigner::NodeMetaInfo &nodeMetaInfo, + const TypeName &typeName, int majorVersion, int minorVersion, bool isImplicitComponent, @@ -183,23 +184,27 @@ public: virtual void variantValuesDiffer(VariantProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName) = 0; virtual void shouldBeVariantProperty(AbstractProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName) = 0; virtual void shouldBeNodeProperty(AbstractProperty &modelProperty, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, QmlJS::AST::UiObjectMember *astNode, const TypeName &dynamicPropertyType, - ReadingContext *context) = 0; + ReadingContext *context) + = 0; virtual void modelNodeAbsentFromQml(ModelNode &modelNode) = 0; virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, ReadingContext *context, QmlJS::AST::UiObjectMember *arrayMember) = 0; virtual void typeDiffers(bool isRootNode, ModelNode &modelNode, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, QmlJS::AST::UiObjectMember *astNode, - ReadingContext *context) = 0; + ReadingContext *context) + = 0; virtual void propertyAbsentFromQml(AbstractProperty &modelProperty) = 0; virtual void idsDiffer(ModelNode &modelNode, const QString &qmlId) = 0; virtual bool isAmender() const = 0; @@ -238,6 +243,7 @@ public: void variantValuesDiffer(VariantProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName) override; void shouldBeVariantProperty(AbstractProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName) override; void shouldBeNodeProperty(AbstractProperty &modelProperty, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, @@ -251,6 +257,7 @@ public: QmlJS::AST::UiObjectMember *arrayMember) override; void typeDiffers(bool isRootNode, ModelNode &modelNode, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, @@ -291,6 +298,7 @@ public: void variantValuesDiffer(VariantProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicType) override; void shouldBeVariantProperty(AbstractProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName) override; void shouldBeNodeProperty(AbstractProperty &modelProperty, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, @@ -304,6 +312,7 @@ public: QmlJS::AST::UiObjectMember *arrayMember) override; void typeDiffers(bool isRootNode, ModelNode &modelNode, + const NodeMetaInfo &nodeMetaInfo, const TypeName &typeName, int majorVersion, int minorVersion, diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 1158572e5e0..cc4de52663e 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -52,6 +53,7 @@ public: void synchronize(Storage::Synchronization::SynchronizationPackage package) override { + TypeIds deletedTypeIds; Sqlite::withImmediateTransaction(database, [&] { AliasPropertyDeclarations insertedAliasPropertyDeclarations; AliasPropertyDeclarations updatedAliasPropertyDeclarations; @@ -60,7 +62,6 @@ public: PropertyDeclarations relinkablePropertyDeclarations; Prototypes relinkablePrototypes; Prototypes relinkableExtensions; - TypeIds deletedTypeIds; TypeIds updatedTypeIds; updatedTypeIds.reserve(package.types.size()); @@ -109,6 +110,8 @@ public: commonTypeCache_.resetTypeIds(); }); + + callRefreshMetaInfoCallback(deletedTypeIds); } void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override @@ -120,6 +123,17 @@ public: }); } + void addRefreshCallback(std::function *callback) override + { + m_refreshCallbacks.push_back(callback); + } + + void removeRefreshCallback(std::function *callback) override + { + m_refreshCallbacks.erase( + std::find(m_refreshCallbacks.begin(), m_refreshCallbacks.end(), callback)); + } + ModuleId moduleId(Utils::SmallStringView moduleName) const override { return moduleCache.id(moduleName); @@ -545,6 +559,12 @@ public: return selectPropertyEditorPathIdStatement.template valueWithTransaction(typeId); } + Storage::Imports fetchDocumentImports() const + { + return selectAllDocumentImportForSourceIdStatement + .template valuesWithTransaction(); + } + private: class ModuleStorageAdapter { @@ -602,6 +622,12 @@ private: return selectAllModulesStatement.template valuesWithTransaction(); } + void callRefreshMetaInfoCallback(const TypeIds &deletedTypeIds) + { + for (auto *callback : m_refreshCallbacks) + (*callback)(deletedTypeIds); + } + class AliasPropertyDeclaration { public: @@ -1557,32 +1583,31 @@ private: PropertyCompare{}); } - void insertDocumentImport(const Storage::Import &import, - Storage::Synchronization::ImportKind importKind, - ModuleId sourceModuleId, - ModuleExportedImportId moduleExportedImportId) + ImportId insertDocumentImport(const Storage::Import &import, + Storage::Synchronization::ImportKind importKind, + ModuleId sourceModuleId, + ImportId parentImportId) { if (import.version.minor) { - insertDocumentImportWithVersionStatement.write(import.sourceId, - import.moduleId, - sourceModuleId, - importKind, - import.version.major.value, - import.version.minor.value, - moduleExportedImportId); + return insertDocumentImportWithVersionStatement + .template value(import.sourceId, + import.moduleId, + sourceModuleId, + importKind, + import.version.major.value, + import.version.minor.value, + parentImportId); } else if (import.version.major) { - insertDocumentImportWithMajorVersionStatement.write(import.sourceId, - import.moduleId, - sourceModuleId, - importKind, - import.version.major.value, - moduleExportedImportId); + return insertDocumentImportWithMajorVersionStatement + .template value(import.sourceId, + import.moduleId, + sourceModuleId, + importKind, + import.version.major.value, + parentImportId); } else { - insertDocumentImportWithoutVersionStatement.write(import.sourceId, - import.moduleId, - sourceModuleId, - importKind, - moduleExportedImportId); + return insertDocumentImportWithoutVersionStatement.template value( + import.sourceId, import.moduleId, sourceModuleId, importKind, parentImportId); } } @@ -1618,11 +1643,8 @@ private: }; auto insert = [&](const Storage::Import &import) { - insertDocumentImport(import, importKind, import.moduleId, ModuleExportedImportId{}); - auto callback = [&](ModuleId exportedModuleId, - int majorVersion, - int minorVersion, - ModuleExportedImportId moduleExportedImportId) { + auto importId = insertDocumentImport(import, importKind, import.moduleId, ImportId{}); + auto callback = [&](ModuleId exportedModuleId, int majorVersion, int minorVersion) { Storage::Import additionImport{exportedModuleId, Storage::Version{majorVersion, minorVersion}, import.sourceId}; @@ -1631,10 +1653,7 @@ private: ? Storage::Synchronization::ImportKind::ModuleExportedImport : Storage::Synchronization::ImportKind::ModuleExportedModuleDependency; - insertDocumentImport(additionImport, - exportedImportKind, - import.moduleId, - moduleExportedImportId); + insertDocumentImport(additionImport, exportedImportKind, import.moduleId, importId); }; selectModuleExportedImportsForModuleIdStatement.readCallback(callback, @@ -1649,6 +1668,7 @@ private: auto remove = [&](const Storage::Synchronization::ImportView &view) { deleteDocumentImportStatement.write(view.importId); + deleteDocumentImportsWithParentImportIdStatement.write(view.sourceId, view.importId); }; Sqlite::insertUpdateDelete(range, imports, compareKey, insert, update, remove); @@ -2680,21 +2700,21 @@ private: Sqlite::StrictColumnType::Integer); auto &minorVersionColumn = table.addColumn("minorVersion", Sqlite::StrictColumnType::Integer); - auto &moduleExportedModuleIdColumn = table.addColumn("moduleExportedModuleId", - Sqlite::StrictColumnType::Integer); + auto &parentImportIdColumn = table.addColumn("parentImportId", + Sqlite::StrictColumnType::Integer); table.addUniqueIndex({sourceIdColumn, moduleIdColumn, kindColumn, sourceModuleIdColumn, - moduleExportedModuleIdColumn}, + parentImportIdColumn}, "majorVersion IS NULL AND minorVersion IS NULL"); table.addUniqueIndex({sourceIdColumn, moduleIdColumn, kindColumn, sourceModuleIdColumn, majorVersionColumn, - moduleExportedModuleIdColumn}, + parentImportIdColumn}, "majorVersion IS NOT NULL AND minorVersion IS NULL"); table.addUniqueIndex({sourceIdColumn, moduleIdColumn, @@ -2702,7 +2722,7 @@ private: sourceModuleIdColumn, majorVersionColumn, minorVersionColumn, - moduleExportedModuleIdColumn}, + parentImportIdColumn}, "majorVersion IS NOT NULL AND minorVersion IS NOT NULL"); table.initialize(database); @@ -2767,6 +2787,7 @@ public: Initializer initializer; mutable ModuleCache moduleCache{ModuleStorageAdapter{*this}}; Storage::Info::CommonTypeCache commonTypeCache_{*this}; + std::vector *> m_refreshCallbacks; ReadWriteStatement<1, 3> upsertTypeStatement{ "INSERT INTO types(sourceId, name, traits) VALUES(?1, ?2, ?3) ON CONFLICT DO " "UPDATE SET traits=excluded.traits WHERE traits IS NOT " @@ -3043,25 +3064,32 @@ public: "SELECT typeId FROM exportedTypeNames WHERE moduleId IN carray(?1, ?2, 'int32') AND " "name=?3", database}; + mutable ReadStatement<4> selectAllDocumentImportForSourceIdStatement{ + "SELECT moduleId, majorVersion, minorVersion, sourceId " + "FROM documentImports ", + database}; mutable ReadStatement<5, 2> selectDocumentImportForSourceIdStatement{ "SELECT importId, sourceId, moduleId, majorVersion, minorVersion " "FROM documentImports WHERE sourceId IN carray(?1) AND kind=?2 ORDER BY sourceId, " "moduleId, majorVersion, minorVersion", database}; - WriteStatement<5> insertDocumentImportWithoutVersionStatement{ + ReadWriteStatement<1, 5> insertDocumentImportWithoutVersionStatement{ "INSERT INTO documentImports(sourceId, moduleId, sourceModuleId, kind, " - "moduleExportedModuleId) VALUES (?1, ?2, ?3, ?4, ?5)", + "parentImportId) VALUES (?1, ?2, ?3, ?4, ?5) RETURNING importId", database}; - WriteStatement<6> insertDocumentImportWithMajorVersionStatement{ + ReadWriteStatement<1, 6> insertDocumentImportWithMajorVersionStatement{ "INSERT INTO documentImports(sourceId, moduleId, sourceModuleId, kind, majorVersion, " - "moduleExportedModuleId) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + "parentImportId) VALUES (?1, ?2, ?3, ?4, ?5, ?6) RETURNING importId", database}; - WriteStatement<7> insertDocumentImportWithVersionStatement{ + ReadWriteStatement<1, 7> insertDocumentImportWithVersionStatement{ "INSERT INTO documentImports(sourceId, moduleId, sourceModuleId, kind, majorVersion, " - "minorVersion, moduleExportedModuleId) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + "minorVersion, parentImportId) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) RETURNING " + "importId", database}; WriteStatement<1> deleteDocumentImportStatement{"DELETE FROM documentImports WHERE importId=?1", database}; + WriteStatement<2> deleteDocumentImportsWithParentImportIdStatement{ + "DELETE FROM documentImports WHERE sourceId=?1 AND parentImportId=?2", database}; WriteStatement<1> deleteDocumentImportsWithSourceIdsStatement{ "DELETE FROM documentImports WHERE sourceId IN carray(?1)", database}; ReadStatement<1, 2> selectPropertyDeclarationIdPrototypeChainDownStatement{ @@ -3307,7 +3335,7 @@ public: database}; WriteStatement<1> deleteModuleExportedImportStatement{ "DELETE FROM moduleExportedImports WHERE moduleExportedImportId=?1", database}; - mutable ReadStatement<4, 3> selectModuleExportedImportsForModuleIdStatement{ + mutable ReadStatement<3, 3> selectModuleExportedImportsForModuleIdStatement{ "WITH RECURSIVE " " imports(moduleId, majorVersion, minorVersion, moduleExportedImportId) AS ( " " SELECT exportedModuleId, " @@ -3321,8 +3349,7 @@ public: " iif(mei.isAutoVersion=1, i.minorVersion, mei.minorVersion), " " mei.moduleExportedImportId " " FROM moduleExportedImports AS mei JOIN imports AS i USING(moduleId)) " - "SELECT DISTINCT moduleId, ifnull(majorVersion, -1), ifnull(minorVersion, -1), " - " moduleExportedImportId " + "SELECT DISTINCT moduleId, ifnull(majorVersion, -1), ifnull(minorVersion, -1) " "FROM imports", database}; mutable ReadStatement<1, 1> selectPropertyDeclarationIdsForTypeStatement{ diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h index 0d290c22f8c..bf73a3c724c 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h @@ -49,7 +49,8 @@ enum class TypeTraits : int { IsEnum = 1 << 8, IsFileComponent = 1 << 9, IsProjectComponent = 1 << 10, - IsInProjectModule = 1 << 11 + IsInProjectModule = 1 << 11, + UsesCustomParser = 1 << 12 }; constexpr TypeTraits operator|(TypeTraits first, TypeTraits second) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h index 49ae0637936..e6962602b38 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h @@ -27,6 +27,9 @@ public: virtual void synchronize(Storage::Synchronization::SynchronizationPackage package) = 0; virtual void synchronizeDocumentImports(const Storage::Imports imports, SourceId sourceId) = 0; + virtual void addRefreshCallback(std::function *callback) = 0; + virtual void removeRefreshCallback(std::function *callback) = 0; + virtual ModuleId moduleId(::Utils::SmallStringView name) const = 0; virtual std::optional propertyDeclaration(PropertyDeclarationId propertyDeclarationId) const = 0; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp index 624b4c47c9c..3759736cfaf 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp @@ -227,8 +227,10 @@ void ProjectStorageUpdater::updateQmlTypes(const QStringList &qmlTypesPaths, package, notUpdatedSourceIds); - if (state == FileState::Changed) + if (state == FileState::Changed) { package.projectDatas.push_back(std::move(projectData)); + package.updatedProjectSourceIds.push_back(sourceId); + } } } diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp index 2c1adfba1ee..41f568e66d2 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp @@ -80,7 +80,7 @@ void addImports(Storage::Imports &imports, imports.emplace_back(qmlCppModuleId, Storage::Version{}, sourceId); } -Storage::TypeTraits createTypeTraits(QQmlJSScope::AccessSemantics accessSematics) +Storage::TypeTraits createAccessTypeTraits(QQmlJSScope::AccessSemantics accessSematics) { switch (accessSematics) { case QQmlJSScope::AccessSemantics::Reference: @@ -96,6 +96,16 @@ Storage::TypeTraits createTypeTraits(QQmlJSScope::AccessSemantics accessSematics return Storage::TypeTraits::None; } +Storage::TypeTraits createTypeTraits(QQmlJSScope::AccessSemantics accessSematics, bool hasCustomParser) +{ + auto typeTrait = createAccessTypeTraits(accessSematics); + + if (hasCustomParser) + typeTrait = typeTrait | Storage::TypeTraits::UsesCustomParser; + + return typeTrait; +} + Storage::Version createVersion(QTypeRevision qmlVersion) { return Storage::Version{qmlVersion.majorVersion(), qmlVersion.minorVersion()}; @@ -413,7 +423,7 @@ void addType(Storage::Synchronization::Types &types, Utils::SmallStringView{typeName}, Storage::Synchronization::ImportedType{TypeNameString{component.baseTypeName()}}, Storage::Synchronization::ImportedType{TypeNameString{component.extensionTypeName()}}, - createTypeTraits(component.accessSemantics()), + createTypeTraits(component.accessSemantics(), component.hasCustomParser()), sourceId, createExports(exports, typeName, storage, cppModuleId), createProperties(component.ownProperties(), enumerationTypes, componentNameWithoutNamespace), diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h index 68327ee7cb8..424638a64d9 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h @@ -3,6 +3,7 @@ #pragma once +#include "projectstorage.h" #include "projectstorageexceptions.h" #include "projectstorageids.h" #include "sourcepath.h" @@ -15,6 +16,7 @@ #include #include +#include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/designmodecontext.cpp b/src/plugins/qmldesigner/designmodecontext.cpp index 13a03bc10b1..43fb0d9d756 100644 --- a/src/plugins/qmldesigner/designmodecontext.cpp +++ b/src/plugins/qmldesigner/designmodecontext.cpp @@ -6,7 +6,6 @@ #include "collectionwidget.h" #include "designmodewidget.h" #include "edit3dwidget.h" -#include "effectmakerwidget.h" #include "formeditorwidget.h" #include "materialbrowserwidget.h" #include "navigatorwidget.h" @@ -99,18 +98,6 @@ void TextEditorContext::contextHelp(const HelpCallback &callback) const qobject_cast(m_widget)->contextHelp(callback); } -EffectMakerContext::EffectMakerContext(QWidget *widget) - : IContext(widget) -{ - setWidget(widget); - setContext(Core::Context(Constants::C_QMLEFFECTMAKER, Constants::C_QT_QUICK_TOOLS_MENU)); -} - -void EffectMakerContext::contextHelp(const HelpCallback &callback) const -{ - qobject_cast(m_widget)->contextHelp(callback); -} - CollectionEditorContext::CollectionEditorContext(QWidget *widget) : IContext(widget) { diff --git a/src/plugins/qmldesigner/designmodecontext.h b/src/plugins/qmldesigner/designmodecontext.h index 72c0a195239..12f0113d977 100644 --- a/src/plugins/qmldesigner/designmodecontext.h +++ b/src/plugins/qmldesigner/designmodecontext.h @@ -74,15 +74,6 @@ public: void contextHelp(const Core::IContext::HelpCallback &callback) const override; }; -class EffectMakerContext : public Core::IContext -{ - Q_OBJECT - -public: - EffectMakerContext(QWidget *widget); - void contextHelp(const Core::IContext::HelpCallback &callback) const override; -}; - class CollectionEditorContext : public Core::IContext { Q_OBJECT diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index 822b23fc9a7..fe9fafde5c4 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -34,6 +34,8 @@ #include #include +#include + #include #include #include @@ -95,7 +97,7 @@ DesignModeWidget::DesignModeWidget() , m_crumbleBar(new CrumbleBar(this)) { setAcceptDrops(true); - if (Utils::StyleHelper::isQDSTheme()) + if (Utils::StyleHelper::isQDSTheme() || Core::ICore::isQtDesignStudio()) qApp->setStyle(QmlDesignerBasePlugin::style()); } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 60e1ded8474..4ca8b1ea923 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -52,7 +52,7 @@ const char EDIT3D_EDIT_SHOW_GRID[] = "QmlDesigner.Editor3D.ToggleGrid"; const char EDIT3D_EDIT_SELECT_BACKGROUND_COLOR[] = "QmlDesigner.Editor3D.SelectBackgroundColor"; const char EDIT3D_EDIT_SELECT_GRID_COLOR[] = "QmlDesigner.Editor3D.SelectGridColor"; const char EDIT3D_EDIT_RESET_BACKGROUND_COLOR[] = "QmlDesigner.Editor3D.ResetBackgroundColor"; -const char EDIT3D_EDIT_SYNC_BACKGROUND_COLOR[] = "QmlDesigner.Editor3D.SyncBackgroundColor"; +const char EDIT3D_EDIT_SYNC_ENV_BACKGROUND[] = "QmlDesigner.Editor3D.SyncEnvBackground"; const char EDIT3D_EDIT_SHOW_SELECTION_BOX[] = "QmlDesigner.Editor3D.ToggleSelectionBox"; const char EDIT3D_EDIT_SHOW_ICON_GIZMO[] = "QmlDesigner.Editor3D.ToggleIconGizmo"; const char EDIT3D_EDIT_SHOW_CAMERA_FRUSTUM[] = "QmlDesigner.Editor3D.ToggleCameraFrustum"; @@ -78,7 +78,6 @@ const char QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY[] = "import_options"; const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene"; const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports"; const char MATERIAL_LIB_ID[] = "__materialLibrary__"; -const char COLLECTION_LIB_ID[] = "__collectionLibrary__"; const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index 74a7804b9d4..8536b332401 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -267,7 +267,7 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e return false; d = new QmlDesignerPluginPrivate; d->timer.start(); - if (QmlProjectManager::QmlProject::isQtDesignStudio()) + if (Core::ICore::isQtDesignStudio()) GenerateResource::generateMenuEntry(this); const QString fontPath @@ -292,7 +292,7 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e Core::AsynchronousMessageBox::warning(composedTitle, description.toString()); }); - if (QmlProjectManager::QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { d->toolBar = ToolBar::create(); d->statusBar = ToolBar::createStatusBar(); } @@ -327,7 +327,7 @@ void QmlDesignerPlugin::extensionsInitialized() ExtensionSystem::IPlugin::ShutdownFlag QmlDesignerPlugin::aboutToShutdown() { - if (QmlProjectManager::QmlProject::isQtDesignStudio()) + if (Core::ICore::isQtDesignStudio()) emitUsageStatistics("qdsShutdownCount"); return SynchronousShutdown; @@ -630,7 +630,7 @@ void QmlDesignerPlugin::enforceDelayedInitialize() d->viewManager.registerFormEditorTool(std::make_unique(d->externalDependencies)); d->viewManager.registerFormEditorTool(std::make_unique()); - if (QmlProjectManager::QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { d->mainWidget.initialize(); emitUsageStatistics("StandaloneMode"); diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index f5556e4d948..557cf96c8e5 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -471,16 +471,15 @@ void SettingsPageWidget::setSettings(const DesignerSettings &settings) m_askBeforeDeletingAssetCheckBox->setChecked(settings.value( DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool()); - const bool standaloneMode = QmlProjectManager::QmlProject::isQtDesignStudio(); #ifdef QT_DEBUG const auto showDebugSettings = true; #else const auto showDebugSettings = settings.value(DesignerSettingsKey::SHOW_DEBUG_SETTINGS).toBool(); #endif - const bool showAdvancedFeatures = !standaloneMode || showDebugSettings; + const bool showAdvancedFeatures = !Core::ICore::isQtDesignStudio() || showDebugSettings; m_emulationGroupBox->setVisible(showAdvancedFeatures); m_debugGroupBox->setVisible(showAdvancedFeatures); - m_featureTimelineEditorCheckBox->setVisible(standaloneMode); + m_featureTimelineEditorCheckBox->setVisible(Core::ICore::isQtDesignStudio()); m_smoothRendering->setChecked(settings.value(DesignerSettingsKey::SMOOTH_RENDERING).toBool()); m_alwaysAutoFormatUICheckBox->setChecked( diff --git a/src/plugins/qmldesigner/studioplugin/CMakeLists.txt b/src/plugins/qmldesigner/studioplugin/CMakeLists.txt deleted file mode 100644 index 3c04dfd6243..00000000000 --- a/src/plugins/qmldesigner/studioplugin/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_qtc_plugin(StudioPlugin - PLUGIN_CLASS StudioPlugin - CONDITION TARGET QmlDesigner - DEPENDS Core QmlDesigner Utils ProjectExplorer - SOURCES - studioplugin.cpp - studioplugin.h - studioplugin.qrc - PLUGIN_PATH ${QmlDesignerPluginInstallPrefix} -) diff --git a/src/plugins/qmldesigner/studioplugin/studioplugin.cpp b/src/plugins/qmldesigner/studioplugin/studioplugin.cpp deleted file mode 100644 index 9b4f67f36bc..00000000000 --- a/src/plugins/qmldesigner/studioplugin/studioplugin.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "studioplugin.h" - -namespace QmlDesigner { - -StudioPlugin::StudioPlugin() -{ -} - -QString StudioPlugin::pluginName() const -{ - return QLatin1String("StudioPlugin"); -} - -QString StudioPlugin::metaInfo() const -{ - return QLatin1String(":/studioplugin/studioplugin.metainfo"); -} - -} diff --git a/src/plugins/qmldesigner/studioplugin/studioplugin.h b/src/plugins/qmldesigner/studioplugin/studioplugin.h deleted file mode 100644 index 416b6df310a..00000000000 --- a/src/plugins/qmldesigner/studioplugin/studioplugin.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -QT_FORWARD_DECLARE_CLASS(QAction) - -namespace QmlDesigner { - -class StudioPlugin : public QObject, QmlDesigner::IWidgetPlugin -{ - Q_OBJECT - - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "studioplugin.json") - - Q_DISABLE_COPY(StudioPlugin) - Q_INTERFACES(QmlDesigner::IWidgetPlugin) - -public: - StudioPlugin(); - ~StudioPlugin() override = default; - - QString metaInfo() const override; - QString pluginName() const override; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/studioplugin/studioplugin.json b/src/plugins/qmldesigner/studioplugin/studioplugin.json deleted file mode 100644 index 1370c2048dc..00000000000 --- a/src/plugins/qmldesigner/studioplugin/studioplugin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Vendor" : "The Qt Company Ltd", - "Category" : "Qt Quick", - "Description" : "Plugin for Qt Studio", - "Url" : "http://www.qt.io" -} diff --git a/src/plugins/qmldesigner/studioplugin/studioplugin.metainfo b/src/plugins/qmldesigner/studioplugin/studioplugin.metainfo deleted file mode 100644 index 2998ea2f67b..00000000000 --- a/src/plugins/qmldesigner/studioplugin/studioplugin.metainfo +++ /dev/null @@ -1,61 +0,0 @@ -MetaInfo { - - Imports { - blacklistImports: [ - "Qt.labs.", - "Qt3D.", - "QtCanvas3D", - "QtGamepad", - "QtPositioning", - "QtQuick.LocalStorage", - "QtQuick.Scene2D", - "QtQuick.Scene3D", - "QtQuick.VirtualKeyboard", - "QtScxml", - "QtTest", - "QtWebEngine", - "QtWebSockets", - "QtQuick.Dialogs.", - "QtSensors", - "QtRemoveObjects", - "QtQuick.tooling", - "QtWebView", - "QtCore", - "QtQuick.Controls.Basic", - "QtQuick.Controls.Fusion", - "QtQuick.Controls.Imagine", - "QtQuick.Controls.Universal", - "QtQuick.Controls.Material", - "QtQuick.Controls.NativeStyle", - "QtQuick.Controls.Windows", - "QtQuick.NativeStyle", - "QtRemoteObjects", - "Qt5Compat.GraphicalEffects", - "QtQuick.Templates", - "QtQuick.Shapes", - "QtQuick.Studio.EventSystem", - "QtQuick.Studio.EventSimulator", - "QtQuick.Pdf", - "QmlTime", - "Qt.test.controls", - "QtOpcUa", - "QtVncServer", - "QtTextToSpeech", - "QtQuick3D MaterialEditor", - "QtDataVisualization", - "QtQuick3D.ParticleEffects" - ] - - showTagsForImports: [ - "QtQuick.Controls", - "QtQuick.Layouts", - "QtQuick.Studio.Effects", - "QtQuick.Studio.Components", - "QtQuick.Studio.MultiText", - "QtQuick.Studio.LogicHelper", - "Qt.SafeRenderer", - "QtQuick3D", - "FlowView" - ] - } -} diff --git a/src/plugins/qmldesigner/studioplugin/studioplugin.qrc b/src/plugins/qmldesigner/studioplugin/studioplugin.qrc deleted file mode 100644 index 7196699789f..00000000000 --- a/src/plugins/qmldesigner/studioplugin/studioplugin.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - studioplugin.metainfo - - diff --git a/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp b/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp index 2cfe5aa9741..b2c55c5b61d 100644 --- a/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp +++ b/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp @@ -3,8 +3,11 @@ #include "studioquickwidget.h" +#include + #include #include +#include #include QQmlEngine *s_engine = nullptr; @@ -46,6 +49,14 @@ void StudioQuickWidget::setResizeMode(QQuickWidget::ResizeMode mode) void StudioQuickWidget::setSource(const QUrl &url) { m_quickWidget->setSource(url); + + if (rootObject()) { + const auto windows = rootObject()->findChildren(); + + for (auto window : windows) { + window->setTransientParent(Core::ICore::dialogParent()->windowHandle()); + } + } } void StudioQuickWidget::refresh() {} diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.cpp b/src/plugins/qmldesignerbase/studio/studiostyle.cpp index 59f19e9f5c7..d1891ca88db 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.cpp +++ b/src/plugins/qmldesignerbase/studio/studiostyle.cpp @@ -594,10 +594,7 @@ void StudioStyle::drawComplexControl( } } break; case CC_ComboBox: { - if (QWidget *parentWidget = widget->parentWidget()) { - QBrush bgColor = parentWidget->palette().brush(parentWidget->backgroundRole()); - painter->fillRect(option->rect, bgColor); - } + painter->fillRect(option->rect, standardPalette().brush(QPalette::ColorRole::Base)); Super::drawComplexControl(control, option, painter, widget); } break; @@ -1136,6 +1133,13 @@ QPalette StudioStyle::standardPalette() const return d->stdPalette; } +void StudioStyle::polish(QWidget *widget) +{ + if (widget && widget->property("_q_custom_style_skipolish").toBool()) + return; + Super::polish(widget); +} + void StudioStyle::drawQmlEditorIcon( PrimitiveElement element, const QStyleOption *option, diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.h b/src/plugins/qmldesignerbase/studio/studiostyle.h index 63250a007de..0912d3071fd 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.h +++ b/src/plugins/qmldesignerbase/studio/studiostyle.h @@ -58,6 +58,8 @@ public: QPalette standardPalette() const override; + void polish(QWidget *widget) override; + private: void drawQmlEditorIcon(PrimitiveElement element, const QStyleOption *option, diff --git a/src/plugins/qmljseditor/qmljsoutline.cpp b/src/plugins/qmljseditor/qmljsoutline.cpp index 291907179d9..89d2dd68320 100644 --- a/src/plugins/qmljseditor/qmljsoutline.cpp +++ b/src/plugins/qmljseditor/qmljsoutline.cpp @@ -144,10 +144,15 @@ void QmlJSOutlineWidget::setEditor(QmlJSEditorWidget *editor) connect(m_editor, &QmlJSEditorWidget::outlineModelIndexChanged, this, &QmlJSOutlineWidget::updateSelectionInTree); - connect(m_editor->qmlJsEditorDocument()->outlineModel(), &QmlOutlineModel::updated, this, [this] () { - m_treeView->expandAll(); - m_editor->updateOutlineIndexNow(); - }); + connect(m_editor->qmlJsEditorDocument()->outlineModel(), + &QmlOutlineModel::updated, + this, + [treeView = QPointer(m_treeView), editor = QPointer(m_editor)]() { + if (treeView) + treeView->expandAll(); + if (editor) + editor->updateOutlineIndexNow(); + }); } QList QmlJSOutlineWidget::filterMenuActions() const diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp index c4c5a621f65..3d75f4c9e45 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp +++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp @@ -272,7 +272,7 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) if (childNode->name().contains("files", Qt::CaseInsensitive)) { PropsPair propsPair; FileProps fileProps; - const QString childNodeName = childNode->name().toLower(); + const QString childNodeName = childNode->name().toLower().remove("qds."); const QmlJS::SimpleReaderNode::Property childNodeFilter = childNode->property("filter"); const QmlJS::SimpleReaderNode::Property childNodeDirectory = childNode->property( "directory"); diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.cpp index 6ac7d9098dd..aa733b68af4 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.cpp @@ -2,9 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cmakegeneratordialog.h" -#include "cmakegeneratordialogtreemodel.h" -#include "generatecmakelistsconstants.h" #include "../qmlprojectmanagertr.h" +#include "cmakegeneratordialogtreemodel.h" #include #include @@ -21,10 +20,10 @@ using namespace Utils; namespace QmlProjectManager { namespace GenerateCmake { -CmakeGeneratorDialog::CmakeGeneratorDialog(const FilePath &rootDir, const FilePaths &files) - : QDialog(), - m_rootDir(rootDir), - m_files(files) +CmakeGeneratorDialog::CmakeGeneratorDialog(const FilePath &rootDir, + const FilePaths &files, + const FilePaths invalidFiles) + : QDialog(), m_rootDir(rootDir), m_files(files), m_invalidFiles(invalidFiles) { setWindowTitle(Tr::tr("Select Files to Generate")); @@ -106,6 +105,8 @@ FilePaths CmakeGeneratorDialog::getFilePaths() const QString FILE_CREATE_NOTIFICATION = Tr::tr("File %1 will be created.\n"); const QString FILE_OVERWRITE_NOTIFICATION = Tr::tr("File %1 will be overwritten.\n"); +const QString FILE_INVALID_NOTIFICATION = Tr::tr( + "File %1 contains invalid characters and will be skipped.\n"); void CmakeGeneratorDialog::refreshNotificationText() { @@ -119,6 +120,11 @@ void CmakeGeneratorDialog::refreshNotificationText() QList nodes = m_model->items(); + for (const auto &file : m_invalidFiles) { + cursor.insertImage(iformat); + cursor.insertText(QString(FILE_INVALID_NOTIFICATION).arg(file.displayName())); + } + for (CheckableFileTreeItem *node : nodes) { if (!m_files.contains(node->toFilePath())) continue; diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.h b/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.h index 4c302b67368..fb15ec4de7c 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.h +++ b/src/plugins/qmlprojectmanager/cmakegen/cmakegeneratordialog.h @@ -22,7 +22,9 @@ class CmakeGeneratorDialog : public QDialog Q_OBJECT public: - CmakeGeneratorDialog(const Utils::FilePath &rootDir, const Utils::FilePaths &files); + CmakeGeneratorDialog(const Utils::FilePath &rootDir, + const Utils::FilePaths &files, + const Utils::FilePaths invalidFiles); Utils::FilePaths getFilePaths(); public slots: @@ -40,6 +42,7 @@ private: QVariant m_warningIcon; Utils::FilePath m_rootDir; Utils::FilePaths m_files; + Utils::FilePaths m_invalidFiles; }; } //GenerateCmake diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp index 9603562dc68..cee9eb247e6 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp @@ -142,7 +142,7 @@ void onGenerateCmakeLists() for (const GeneratableFile &file: cmakeGen.fileQueue().queuedFiles()) allFiles.append(file.filePath); - CmakeGeneratorDialog dialog(rootDir, allFiles); + CmakeGeneratorDialog dialog(rootDir, allFiles, cmakeGen.invalidFileNames()); if (dialog.exec()) { FilePaths confirmedFiles = dialog.getFilePaths(); cmakeGen.filterFileQueue(confirmedFiles); @@ -334,6 +334,11 @@ bool CmakeFileGenerator::execute() return m_fileQueue.writeQueuedFiles(); } +FilePaths CmakeFileGenerator::invalidFileNames() const +{ + return m_invalidFileNames; +} + const char DO_NOT_EDIT_FILE_COMMENT[] = "### This file is automatically generated by Qt Design Studio.\n### Do not change\n\n"; const char ADD_SUBDIR[] = "add_subdirectory(%1)\n"; @@ -491,8 +496,7 @@ QStringList CmakeFileGenerator::getDirectoryResources(const FilePath &dir) FilePaths allFiles = dir.dirEntries(FILES_ONLY); for (FilePath &file : allFiles) { - if (!file.fileName().endsWith(".qml", Qt::CaseInsensitive) && - includeFile(file)) { + if (!file.fileName().endsWith(".qml", Qt::CaseInsensitive) && includeFile(file)) { moduleFiles.append(file.fileName()); } } @@ -519,10 +523,20 @@ QStringList CmakeFileGenerator::getDirectoryTreeQmls(const FilePath &dir) return qmlFileList; } +static void appendWidthQuotes(QStringList &list, const QString &string) +{ + if (string.contains(' ')) + list.append("\"" + string + "\""); + else + list.append(string); +} + QStringList CmakeFileGenerator::getDirectoryTreeResources(const FilePath &dir) { QStringList resourceFileList; + //for (const auto &string : getDirectoryResources(dir)) + // appendWidthQuotes(resourceFileList, string); resourceFileList.append(getDirectoryResources(dir)); FilePaths subDirsList = dir.dirEntries(DIRS_ONLY); @@ -531,9 +545,8 @@ QStringList CmakeFileGenerator::getDirectoryTreeResources(const FilePath &dir) continue; QStringList subDirResources = getDirectoryTreeResources(subDir); for (QString &resource : subDirResources) { - resourceFileList.append(subDir.fileName().append('/').append(resource)); + appendWidthQuotes(resourceFileList, subDir.fileName().append('/').append(resource)); } - } return resourceFileList; @@ -556,6 +569,18 @@ bool CmakeFileGenerator::isDirBlacklisted(const FilePath &dir) return (!dir.fileName().compare(DIRNAME_DESIGNER)); } +bool CmakeFileGenerator::validFileName(const Utils::FilePath &filePath) +{ + QStringList invalidChars = {"!", "\"", "£", "$", "%", "!", "^", "&", "*", "(", ")", "=", "+", + "'", ",", ";", ":", "#", "~", "{", "{", "[", "]", "<", ">", "?"}; + const QString baseName = filePath.baseName(); + for (const auto &c : invalidChars) { + if (baseName.contains(c)) + return false; + } + return true; +} + bool CmakeFileGenerator::includeFile(const FilePath &filePath) { if (m_checkFileIsInProject) { @@ -564,7 +589,12 @@ bool CmakeFileGenerator::includeFile(const FilePath &filePath) return false; } - return !isFileBlacklisted(filePath.fileName()); + if (validFileName(filePath)) + return !isFileBlacklisted(filePath.fileName()); + else + m_invalidFileNames.append(filePath); + + return false; } bool CmakeFileGenerator::generateEntryPointFiles(const FilePath &dir) diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.h b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.h index 6afa9cda05a..db025daa275 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.h +++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.h @@ -45,6 +45,7 @@ public: const FileQueue fileQueue() const; void filterFileQueue(const Utils::FilePaths &keepFiles); bool execute(); + Utils::FilePaths invalidFileNames() const; private: void generateMainCmake(const Utils::FilePath &rootDir); @@ -63,12 +64,15 @@ private: bool isFileBlacklisted(const QString &fileName); bool isDirBlacklisted(const Utils::FilePath &dir); bool includeFile(const Utils::FilePath &filePath); + bool validFileName(const Utils::FilePath &filePath); private: FileQueue m_fileQueue; QStringList m_resourceFileLocations; QStringList m_moduleNames; bool m_checkFileIsInProject; + + Utils::FilePaths m_invalidFileNames; }; } //GenerateCmake diff --git a/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl b/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl index 423ed1a921a..4cd900a31ff 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl +++ b/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl @@ -1,11 +1,19 @@ -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.21.1) + +option(LINK_INSIGHT "Link Qt Insight Tracker library" ON) +option(BUILD_QDS_COMPONENTS "Build design studio components" ON) project(%1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -find_package(Qt6 COMPONENTS Gui Qml Quick) +find_package(Qt6 6.2 REQUIRED COMPONENTS Core Gui Qml Quick) + +if (Qt6_VERSION VERSION_GREATER_EQUAL 6.3) + qt_standard_project_setup() +endif() + qt_add_executable(%1 src/main.cpp) qt_add_resources(%1 "configuration" @@ -20,5 +28,20 @@ target_link_libraries(%1 PRIVATE Qt${QT_VERSION_MAJOR}::Qml ) +if (BUILD_QDS_COMPONENTS) + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) +endif() + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) +if (LINK_INSIGHT) + include(${CMAKE_CURRENT_SOURCE_DIR}/insight) +endif () + +include(GNUInstallDirs) +install(TARGETS CppExampleApp + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index 7ed655b01be..360595e8fa4 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -53,7 +53,7 @@ QmlProject::QmlProject(const Utils::FilePath &fileName) setNeedsBuildConfigurations(false); setBuildSystemCreator([](Target *t) { return new QmlBuildSystem(t); }); - if (QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { if (allowOnlySingleProject()) { EditorManager::closeAllDocuments(); ProjectManager::closeAllProjects(); @@ -113,7 +113,7 @@ Project::RestoreResult QmlProject::fromMap(const Store &map, QString *errorMessa // FIXME: are there any other way? // What if it's not a Design Studio project? What should we do then? - if (QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { int preferedVersion = preferedQtTarget(activeTarget()); setKitWithVersion(preferedVersion, kits); @@ -207,13 +207,6 @@ Tasks QmlProject::projectIssues(const Kit *k) const return result; } -bool QmlProject::isQtDesignStudio() -{ - QtcSettings *settings = Core::ICore::settings(); - const Key qdsStandaloneEntry = "QML/Designer/StandAloneMode"; - return settings->value(qdsStandaloneEntry, false).toBool(); -} - bool QmlProject::isQtDesignStudioStartedFromQtC() { return qEnvironmentVariableIsSet(Constants::enviromentLaunchedQDS); @@ -226,7 +219,7 @@ DeploymentKnowledge QmlProject::deploymentKnowledge() const bool QmlProject::isEditModePreferred() const { - return !isQtDesignStudio(); + return !Core::ICore::isQtDesignStudio(); } int QmlProject::preferedQtTarget(Target *target) diff --git a/src/plugins/qmlprojectmanager/qmlproject.h b/src/plugins/qmlprojectmanager/qmlproject.h index f7855f48ee1..b716d442d05 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.h +++ b/src/plugins/qmlprojectmanager/qmlproject.h @@ -17,7 +17,6 @@ class QMLPROJECTMANAGER_EXPORT QmlProject : public ProjectExplorer::Project public: explicit QmlProject(const Utils::FilePath &filename); - static bool isQtDesignStudio(); static bool isQtDesignStudioStartedFromQtC(); bool isEditModePreferred() const override; diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp index 7c12e312b10..24fd05a5540 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp @@ -266,7 +266,7 @@ void QmlProjectPlugin::initialize() Utils::FileIconProvider::registerIconOverlayForSuffix(":/qmlproject/images/qmlproject.png", "qmlproject"); - if (QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { Core::ActionContainer *menu = Core::ActionManager::actionContainer( ProjectExplorer::Constants::M_FILECONTEXT); QAction *mainfileAction = new QAction(Tr::tr("Set as Main .qml File"), this); @@ -355,7 +355,7 @@ void QmlProjectPlugin::initialize() } GenerateCmake::generateMenuEntry(this); - if (QmlProject::isQtDesignStudio()) + if (Core::ICore::isQtDesignStudio()) GenerateCmake::CmakeProjectConverter::generateMenuEntry(this); } diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index 2bd1318e85d..18e61462281 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -125,7 +126,7 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id) qmlMainFile.setTarget(target); connect(&qmlMainFile, &BaseAspect::changed, this, &RunConfiguration::update); - if (QmlProject::isQtDesignStudio()) + if (Core::ICore::isQtDesignStudio()) setupQtVersionAspect(); else qtversion.setVisible(false); @@ -210,13 +211,17 @@ FilePath QmlProjectRunConfiguration::qmlRuntimeFilePath() const if (!qmlRuntime.isEmpty()) return qmlRuntime; } + auto hasDeployStep = [this]() { + return target()->activeDeployConfiguration() && + !target()->activeDeployConfiguration()->stepList()->isEmpty(); + }; // The Qt version might know, but we need to make sure // that the device can reach it. if (QtVersion *version = QtKitAspect::qtVersion(kit)) { // look for puppet as qmlruntime only in QtStudio Qt versions if (version->features().contains("QtStudio") && - version->qtVersion().majorVersion() > 5) { + version->qtVersion().majorVersion() > 5 && !hasDeployStep()) { auto [workingDirectoryPath, puppetPath] = QmlDesigner::QmlPuppetPaths::qmlPuppetPaths( target(), QmlDesigner::QmlDesignerBasePlugin::settings()); @@ -237,6 +242,9 @@ FilePath QmlProjectRunConfiguration::qmlRuntimeFilePath() const void QmlProjectRunConfiguration::setupQtVersionAspect() { + if (!Core::ICore::isQtDesignStudio()) + return; + qtversion.setSettingsKey("QmlProjectManager.kit"); qtversion.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); qtversion.setLabelText(Tr::tr("Qt Version:")); diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index 829b761d108..e01215e3d5c 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -548,7 +548,7 @@ void StudioWelcomePlugin::extensionsInitialized() Core::ModeManager::activateMode(m_welcomeMode->id()); // Enable QDS new project dialog and QDS wizards - if (QmlProjectManager::QmlProject::isQtDesignStudio()) { + if (Core::ICore::isQtDesignStudio()) { ProjectExplorer::JsonWizardFactory::clearWizardPaths(); ProjectExplorer::JsonWizardFactory::addWizardPath( Core::ICore::resourcePath("qmldesigner/studio_templates")); diff --git a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml index 7cd8dd7f4a3..a44a2a75d2d 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml @@ -27,7 +27,7 @@ Item { property color backgroundGradientColorStart: "#222222" property color backgroundGradientColorEnd: "#999999" property color gridColor: "#cccccc" - property bool syncBackgroundColor: false + property bool syncEnvBackground: false enum SelectionMode { Item, Group } enum TransformMode { Move, Rotate, Scale } @@ -58,7 +58,7 @@ Item { onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) onShowGridChanged: _generalHelper.storeToolState(sceneId, "showGrid", showGrid); - onSyncBackgroundColorChanged: _generalHelper.storeToolState(sceneId, "syncBackgroundColor", syncBackgroundColor); + onSyncEnvBackgroundChanged: _generalHelper.storeToolState(sceneId, "syncEnvBackground", syncEnvBackground); onShowSelectionBoxChanged: _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox); onShowIconGizmoChanged: _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo); onShowCameraFrustumChanged: _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum); @@ -136,10 +136,7 @@ Item { } } - if (syncBackgroundColor) - updateBackgroundColors([_generalHelper.sceneEnvironmentColor(sceneId)]); - else - updateBackgroundColors(_generalHelper.bgColor); + updateEnvBackground(); notifyActiveSceneChange(); } @@ -206,6 +203,31 @@ Item { } } + function updateEnvBackground() { + updateBackgroundColors(_generalHelper.bgColor); + + if (!editView) + return; + + if (syncEnvBackground) { + let bgMode = _generalHelper.sceneEnvironmentBgMode(sceneId); + if ((!_generalHelper.sceneEnvironmentLightProbe(sceneId) && bgMode === SceneEnvironment.SkyBox) + || (!_generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId) && bgMode === SceneEnvironment.SkyBoxCubeMap)) { + editView.sceneEnv.backgroundMode = SceneEnvironment.Color; + } else { + editView.sceneEnv.backgroundMode = bgMode; + } + editView.sceneEnv.lightProbe = _generalHelper.sceneEnvironmentLightProbe(sceneId); + editView.sceneEnv.skyBoxCubeMap = _generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId); + editView.sceneEnv.clearColor = _generalHelper.sceneEnvironmentColor(sceneId); + } else { + editView.sceneEnv.backgroundMode = SceneEnvironment.Transparent; + editView.sceneEnv.lightProbe = null; + editView.sceneEnv.skyBoxCubeMap = null; + editView.sceneEnv.clearColor = "transparent"; + } + } + // If resetToDefault is true, tool states not specifically set to anything will be reset to // their default state. function updateToolStates(toolStates, resetToDefault) @@ -220,15 +242,12 @@ Item { else if (resetToDefault) showGrid = true; - if ("syncBackgroundColor" in toolStates) { - syncBackgroundColor = toolStates.syncBackgroundColor; - if (syncBackgroundColor) - updateBackgroundColors([_generalHelper.sceneEnvironmentColor(sceneId)]); - else - updateBackgroundColors(_generalHelper.bgColor); + if ("syncEnvBackground" in toolStates) { + syncEnvBackground = toolStates.syncEnvBackground; + updateEnvBackground(); } else if (resetToDefault) { - syncBackgroundColor = false; - updateBackgroundColors(_generalHelper.bgColor); + syncEnvBackground = false; + updateEnvBackground(); } if ("showSelectionBox" in toolStates) @@ -281,7 +300,7 @@ Item { { _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) _generalHelper.storeToolState(sceneId, "showGrid", showGrid) - _generalHelper.storeToolState(sceneId, "syncBackgroundColor", syncBackgroundColor) + _generalHelper.storeToolState(sceneId, "syncEnvBackground", syncEnvBackground) _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox) _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo) _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum) @@ -697,6 +716,7 @@ Item { } } } + function onHiddenStateChanged(node) { for (var i = 0; i < cameraGizmos.length; ++i) { @@ -727,6 +747,16 @@ Item { } } } + + function onUpdateDragTooltip() + { + gizmoLabel.updateLabel(); + rotateGizmoLabel.updateLabel(); + } + + function onSceneEnvDataChanged() { + updateEnvBackground(); + } } Node { @@ -834,6 +864,7 @@ Item { else viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); } + onCurrentAngleChanged: rotateGizmoLabel.updateLabel() } LightGizmo { @@ -1012,6 +1043,27 @@ Item { visible: targetNode.dragging z: 3 + function updateLabel() + { + // This is skipped during application shutdown, as calling QQuickText::setText() + // during application shutdown can crash the application. + if (!gizmoLabel.visible || !viewRoot.selectedNode || shuttingDown) + return; + var targetProperty; + if (gizmoLabel.targetNode === moveGizmo) + gizmoLabelText.text = _generalHelper.snapPositionDragTooltip(viewRoot.selectedNode.position); + else + gizmoLabelText.text = _generalHelper.snapScaleDragTooltip(viewRoot.selectedNode.scale); + } + + Connections { + target: viewRoot.selectedNode + function onPositionChanged() { gizmoLabel.updateLabel() } + function onScaleChanged() { gizmoLabel.updateLabel() } + } + + onVisibleChanged: gizmoLabel.updateLabel() + Rectangle { color: "white" x: -width / 2 @@ -1021,25 +1073,6 @@ Item { border.width: 1 Text { id: gizmoLabelText - text: { - // This is skipped during application shutdown, as calling QQuickText::setText() - // during application shutdown can crash the application. - if (shuttingDown) - return text; - var l = Qt.locale(); - var targetProperty; - if (viewRoot.selectedNode) { - if (gizmoLabel.targetNode === moveGizmo) - targetProperty = viewRoot.selectedNode.position; - else - targetProperty = viewRoot.selectedNode.scale; - return qsTr("x:") + Number(targetProperty.x).toLocaleString(l, 'f', 1) - + qsTr(" y:") + Number(targetProperty.y).toLocaleString(l, 'f', 1) - + qsTr(" z:") + Number(targetProperty.z).toLocaleString(l, 'f', 1); - } else { - return ""; - } - } anchors.centerIn: parent } } @@ -1057,21 +1090,19 @@ Item { parent: rotateGizmo.view3D z: 3 + function updateLabel() { + // This is skipped during application shutdown, as calling QQuickText::setText() + // during application shutdown can crash the application. + if (!rotateGizmoLabel.visible || !rotateGizmo.targetNode || shuttingDown) + return; + var degrees = rotateGizmo.currentAngle * (180 / Math.PI); + rotateGizmoLabelText.text = _generalHelper.snapRotationDragTooltip(degrees); + } + + onVisibleChanged: rotateGizmoLabel.updateLabel() + Text { id: rotateGizmoLabelText - text: { - // This is skipped during application shutdown, as calling QQuickText::setText() - // during application shutdown can crash the application. - if (shuttingDown) - return text; - var l = Qt.locale(); - if (rotateGizmo.targetNode) { - var degrees = rotateGizmo.currentAngle * (180 / Math.PI); - return Number(degrees).toLocaleString(l, 'f', 1); - } else { - return ""; - } - } anchors.centerIn: parent } } @@ -1135,4 +1166,30 @@ Item { visible: viewRoot.fps > 0 } } + + Keys.onPressed: (event) => { + switch (event.key) { + case Qt.Key_Control: + case Qt.Key_Shift: + gizmoLabel.updateLabel(); + rotateGizmoLabel.updateLabel(); + break; + default: + break; + } + event.accepted = false; + } + + Keys.onReleased: (event) => { + switch (event.key) { + case Qt.Key_Control: + case Qt.Key_Shift: + gizmoLabel.updateLabel(); + rotateGizmoLabel.updateLabel(); + break; + default: + break; + } + event.accepted = false; + } } diff --git a/src/tools/qml2puppet/mockfiles/qt6/HelperGrid.qml b/src/tools/qml2puppet/mockfiles/qt6/HelperGrid.qml index 2a7f921ffe2..e189d064014 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/HelperGrid.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/HelperGrid.qml @@ -13,9 +13,8 @@ Node { property bool orthoMode: false property double distance: 500 - readonly property int minGridStep: 50 - readonly property int maxGridStep: 32 * minGridStep - readonly property int gridArea: minGridStep * 512 + readonly property int maxGridStep: 32 * _generalHelper.minGridStep + readonly property int gridArea: _generalHelper.minGridStep * 512 // Step of the main lines of the grid, between those is always one subdiv line property int gridStep: 100 @@ -32,12 +31,13 @@ Node { return Math.atan(step / distance) } - onDistanceChanged: { + function calcStep() + { if (distance === 0) return // Calculate new grid step - let newStep = gridStep + let newStep = _generalHelper.minGridStep let gridRad = calcRad(newStep) while (gridRad < minGridRad && newStep < maxGridStep) { newStep *= 2 @@ -45,16 +45,13 @@ Node { newStep = maxGridStep gridRad = calcRad(newStep) } - while (gridRad > minGridRad * 2 && newStep > minGridStep) { - newStep /= 2 - if (newStep < minGridStep) - newStep = minGridStep - gridRad = calcRad(newStep) - } gridStep = newStep subGridMaterial.generalAlpha = Math.min(1, 2 * (1 - (minGridRad / gridRad))) } + onMaxGridStepChanged: calcStep() + onDistanceChanged: calcStep() + // Note: Only one instance of HelperGrid is supported, as the geometry names are fixed Model { // Main grid lines @@ -74,6 +71,7 @@ Node { orthoMode: grid.orthoMode } ] + opacity: 0.99 } Model { // Subdivision lines @@ -93,6 +91,7 @@ Node { orthoMode: grid.orthoMode } ] + opacity: 0.99 } Model { // Z Axis @@ -111,6 +110,7 @@ Node { orthoMode: grid.orthoMode } ] + opacity: 0.99 } Model { // X Axis readonly property bool _edit3dLocked: true // Make this non-pickable @@ -129,5 +129,6 @@ Node { orthoMode: grid.orthoMode } ] + opacity: 0.99 } } diff --git a/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml index e3f585de545..0eafeeb816e 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml @@ -15,6 +15,7 @@ View3D { property alias sceneHelpers: sceneHelpers property alias perspectiveCamera: scenePerspectiveCamera property alias orthoCamera: sceneOrthoCamera + property alias sceneEnv: sceneEnv property vector3d cameraLookAt // Measuring the distance from camera to lookAt plus the distance of lookAt from grid plane diff --git a/src/tools/qml2puppet/mockfiles/shaders/gridmaterial.frag b/src/tools/qml2puppet/mockfiles/shaders/gridmaterial.frag index 6ec50859028..7d785bc5312 100644 --- a/src/tools/qml2puppet/mockfiles/shaders/gridmaterial.frag +++ b/src/tools/qml2puppet/mockfiles/shaders/gridmaterial.frag @@ -14,9 +14,20 @@ void MAIN() float cosAngle = dot(normalize(camDir), normalize(camLevel)); float angleDepth = density * pow(cosAngle, 8); float alpha = generalAlpha * clamp((1.0 - ((angleDepth * depth - alphaStartDepth) / (alphaEndDepth - alphaStartDepth))), 0, 1); - if (alpha > 0.01) - FRAGCOLOR = vec4(color.x * alpha, color.y * alpha, color.z * alpha, alpha); - else + + // Force additional alpha when approaching the far clip of edit camera + if (depth > 90000.0) + alpha *= clamp((100000.0 - depth) / 10000.0, 0, 1); + + if (alpha > 0.01) { + vec2 uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0)); + vec4 sc = texture(SCREEN_TEXTURE, uv); + if (sc.a == 0.0) + FRAGCOLOR = vec4(color.xyz * alpha, alpha); + else + FRAGCOLOR = vec4((color.xyz * alpha + sc.xyz * (1.0 - alpha)) * alpha, alpha); + } else { discard; + } } } diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp index 47f176b4219..f1b4b795e47 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp @@ -57,6 +57,10 @@ GeneralHelper::GeneralHelper() m_toolStateUpdateTimer.setSingleShot(true); QObject::connect(&m_toolStateUpdateTimer, &QTimer::timeout, this, &GeneralHelper::handlePendingToolStateUpdate); + + QList defaultBg; + defaultBg.append(QColor()); + m_bgColor = QVariant::fromValue(defaultBg); } void GeneralHelper::requestOverlayUpdate() @@ -540,19 +544,62 @@ void GeneralHelper::storeToolState(const QString &sceneId, const QString &tool, } } -void GeneralHelper::setSceneEnvironmentColor(const QString &sceneId, const QColor &color) +void GeneralHelper::setSceneEnvironmentData(const QString &sceneId, + QQuick3DSceneEnvironment *env) { - m_sceneEnvironmentColor[sceneId] = color; + if (env) { + SceneEnvData &data = m_sceneEnvironmentData[sceneId]; + data.backgroundMode = env->backgroundMode(); + data.clearColor = env->clearColor(); + + if (data.lightProbe) + disconnect(data.lightProbe, &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged); + data.lightProbe = env->lightProbe(); + if (env->lightProbe()) + connect(env->lightProbe(), &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged, Qt::DirectConnection); + + if (data.skyBoxCubeMap) + disconnect(data.skyBoxCubeMap, &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged); + data.skyBoxCubeMap = env->skyBoxCubeMap(); + if (env->skyBoxCubeMap()) + connect(env->skyBoxCubeMap(), &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged, Qt::DirectConnection); + + emit sceneEnvDataChanged(); + } +} + +QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes GeneralHelper::sceneEnvironmentBgMode( + const QString &sceneId) const +{ + return m_sceneEnvironmentData[sceneId].backgroundMode; } QColor GeneralHelper::sceneEnvironmentColor(const QString &sceneId) const { - return m_sceneEnvironmentColor[sceneId]; + return m_sceneEnvironmentData[sceneId].clearColor; } -void GeneralHelper::clearSceneEnvironmentColors() +QQuick3DTexture *GeneralHelper::sceneEnvironmentLightProbe(const QString &sceneId) const { - m_sceneEnvironmentColor.clear(); + return m_sceneEnvironmentData[sceneId].lightProbe.data(); +} + +QQuick3DCubeMapTexture *GeneralHelper::sceneEnvironmentSkyBoxCubeMap(const QString &sceneId) const +{ + return m_sceneEnvironmentData[sceneId].skyBoxCubeMap.data(); +} + +void GeneralHelper::clearSceneEnvironmentData() +{ + for (const SceneEnvData &data : std::as_const(m_sceneEnvironmentData)) { + if (data.lightProbe) + disconnect(data.lightProbe, &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged); + if (data.skyBoxCubeMap) + disconnect(data.skyBoxCubeMap, &QObject::destroyed, this, &GeneralHelper::sceneEnvDataChanged); + } + + m_sceneEnvironmentData.clear(); + emit sceneEnvDataChanged(); } void GeneralHelper::initToolStates(const QString &sceneId, const QVariantMap &toolStates) @@ -918,7 +965,7 @@ double GeneralHelper::adjustRotationForSnap(double newAngle) static double adjustScaler(double newScale, double increment) { double absScale = qAbs(newScale); - double comp1 = 1. + double(int((absScale / increment) - (1. / increment))) * increment; + double comp1 = 1. + double(int(int(absScale / increment) - (1. / increment))) * increment; double comp2 = comp1 + increment; double retVal = absScale - comp1 > comp2 - absScale ? comp2 : comp1; if (newScale < 0) @@ -931,7 +978,7 @@ double GeneralHelper::adjustScalerForSnap(double newScale) bool snapScale = m_snapScale; double increment = m_snapScaleInterval; - if (qFuzzyIsNull(newScale) || !queryKeyboardForSnapping(snapScale, increment)) + if (!queryKeyboardForSnapping(snapScale, increment)) return newScale; return adjustScaler(newScale, increment); @@ -954,6 +1001,56 @@ QVector3D GeneralHelper::adjustScaleForSnap(const QVector3D &newScale) return adjScale; } +void GeneralHelper::setSnapPositionInterval(double interval) +{ + if (m_snapPositionInterval != interval) { + m_snapPositionInterval = interval; + emit minGridStepChanged(); + } +} + +QString GeneralHelper::formatVectorDragTooltip(const QVector3D &vec, const QString &suffix) const +{ + return QObject::tr("x:%L1 y:%L2 z:%L3%L4") + .arg(vec.x(), 0, 'f', 1).arg(vec.y(), 0, 'f', 1) + .arg(vec.z(), 0, 'f', 1).arg(suffix); +} + +QString GeneralHelper::formatSnapStr(bool snapEnabled, double increment, const QString &suffix) const +{ + double inc = increment; + QString snapStr; + if (queryKeyboardForSnapping(snapEnabled, inc)) { + int precision = 0; + // We can have fractional snap if shift is pressed, so adjust precision in those cases + if (qRound(inc * 10.) != qRound(inc) * 10) + precision = 1; + snapStr = QObject::tr(" (Snap: %1%2)").arg(inc, 0, 'f', precision).arg(suffix); + } + return snapStr; +} + +QString GeneralHelper::snapPositionDragTooltip(const QVector3D &pos) const +{ + return formatVectorDragTooltip(pos, formatSnapStr(m_snapPosition, m_snapPositionInterval, {})); +} + +QString GeneralHelper::snapRotationDragTooltip(double angle) const +{ + return tr("%L1%L2").arg(angle, 0, 'f', 1).arg(formatSnapStr(m_snapRotation, m_snapRotationInterval, {})); +} + +QString GeneralHelper::snapScaleDragTooltip(const QVector3D &scale) const +{ + return formatVectorDragTooltip(scale, formatSnapStr(m_snapScale, m_snapScaleInterval * 100., tr("%"))); +} + +double GeneralHelper::minGridStep() const +{ + // Minimum grid step is a multiple of snap interval, as the last step is divided to subgrid + return 2. * m_snapPositionInterval; +} + void GeneralHelper::setBgColor(const QVariant &colors) { if (m_bgColor != colors) { diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h index a80ab516ae2..9e54250201a 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h @@ -15,7 +15,10 @@ #include #include #include +#include #include +#include +#include QT_BEGIN_NAMESPACE class QQuick3DCamera; @@ -33,6 +36,7 @@ class GeneralHelper : public QObject Q_OBJECT Q_PROPERTY(bool isMacOS READ isMacOS CONSTANT) Q_PROPERTY(QVariant bgColor READ bgColor NOTIFY bgColorChanged FINAL) + Q_PROPERTY(double minGridStep READ minGridStep NOTIFY minGridStepChanged FINAL) public: GeneralHelper(); @@ -91,9 +95,13 @@ public: Q_INVOKABLE void scaleMultiSelection(bool commit); Q_INVOKABLE void rotateMultiSelection(bool commit); - void setSceneEnvironmentColor(const QString &sceneId, const QColor &color); + void setSceneEnvironmentData(const QString &sceneId, QQuick3DSceneEnvironment *env); + Q_INVOKABLE QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes sceneEnvironmentBgMode( + const QString &sceneId) const; Q_INVOKABLE QColor sceneEnvironmentColor(const QString &sceneId) const; - void clearSceneEnvironmentColors(); + Q_INVOKABLE QQuick3DTexture *sceneEnvironmentLightProbe(const QString &sceneId) const; + Q_INVOKABLE QQuick3DCubeMapTexture *sceneEnvironmentSkyBoxCubeMap(const QString &sceneId) const; + void clearSceneEnvironmentData(); bool isMacOS() const; @@ -114,10 +122,16 @@ public: void setSnapPosition(bool enable) { m_snapPosition = enable; } void setSnapRotation(bool enable) { m_snapRotation = enable; } void setSnapScale(bool enable) { m_snapScale = enable; } - void setSnapPositionInterval(double interval) { m_snapPositionInterval = interval; } + void setSnapPositionInterval(double interval); void setSnapRotationInterval(double interval) { m_snapRotationInterval = interval; } void setSnapScaleInterval(double interval) { m_snapScaleInterval = interval / 100.; } + Q_INVOKABLE QString snapPositionDragTooltip(const QVector3D &pos) const; + Q_INVOKABLE QString snapRotationDragTooltip(double angle) const; + Q_INVOKABLE QString snapScaleDragTooltip(const QVector3D &scale) const; + + double minGridStep() const; + void setBgColor(const QVariant &colors); QVariant bgColor() const { return m_bgColor; } @@ -128,19 +142,32 @@ signals: void lockedStateChanged(QQuick3DNode *node); void rotationBlocksChanged(); void bgColorChanged(); + void minGridStepChanged(); + void updateDragTooltip(); + void sceneEnvDataChanged(); + private: void handlePendingToolStateUpdate(); QVector3D pivotScenePosition(QQuick3DNode *node) const; bool getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds, QVector3D &maxBounds); + QString formatVectorDragTooltip(const QVector3D &vec, const QString &suffix) const; + QString formatSnapStr(bool snapEnabled, double increment, const QString &suffix) const; QTimer m_overlayUpdateTimer; QTimer m_toolStateUpdateTimer; QHash m_toolStates; QHash m_toolStatesPending; - QHash m_sceneEnvironmentColor; QSet m_rotationBlockedNodes; + struct SceneEnvData { + QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes backgroundMode; + QColor clearColor; + QPointer lightProbe; + QPointer skyBoxCubeMap; + }; + QHash m_sceneEnvironmentData; + struct MultiSelData { QVector3D startScenePos; QVector3D startScale; diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 663adcf3e17..bff0732d0c1 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -344,22 +344,33 @@ void Qt5InformationNodeInstanceServer::updateSnapSettings( #ifdef QUICK3D_MODULE auto helper = qobject_cast(m_3dHelper); if (helper) { + bool changed = false; for (const auto &container : valueChanges) { - if (container.name() == "snapPos3d") + if (container.name() == "snapPos3d") { helper->setSnapPosition(container.value().toBool()); - else if (container.name() == "snapPosInt3d") + changed = true; + } else if (container.name() == "snapPosInt3d") { helper->setSnapPositionInterval(container.value().toDouble()); - else if (container.name() == "snapRot3d") + changed = true; + } else if (container.name() == "snapRot3d") { helper->setSnapRotation(container.value().toBool()); - else if (container.name() == "snapRotInt3d") + changed = true; + } else if (container.name() == "snapRotInt3d") { helper->setSnapRotationInterval(container.value().toDouble()); - else if (container.name() == "snapScale3d") + changed = true; + } else if (container.name() == "snapScale3d") { helper->setSnapScale(container.value().toBool()); - else if (container.name() == "snapScaleInt3d") + changed = true; + } else if (container.name() == "snapScaleInt3d") { helper->setSnapScaleInterval(container.value().toDouble()); - else if (container.name() == "snapAbs3d") + changed = true; + } else if (container.name() == "snapAbs3d") { helper->setSnapAbsolute(container.value().toBool()); + changed = true; + } } + if (changed) + emit helper->updateDragTooltip(); } #endif } @@ -374,10 +385,9 @@ void Qt5InformationNodeInstanceServer::updateColorSettings( QQmlProperty gridProp(m_editView3DData.rootItem, "gridColor", context()); gridProp.write(container.value()); } else if (container.name() == "edit3dBgColor") { - QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateBackgroundColors", - Q_ARG(QVariant, container.value())); if (auto helper = qobject_cast(m_3dHelper)) helper->setBgColor(container.value()); + QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateEnvBackground"); } } } @@ -533,9 +543,66 @@ void Qt5InformationNodeInstanceServer::handleParticleSystemSelected(QQuick3DPart } }); - const auto anim = animations(); - for (auto a : anim) - a->restart(); + if (m_targetParticleSystem) { + auto checkAncestor = [](QObject *checkObj, QObject *ancestor) -> bool { + QObject *parent = checkObj->parent(); + while (parent) { + if (parent == ancestor) + return true; + parent = parent->parent(); + } + return false; + }; + auto isAnimContainer = [](QObject *o) -> bool { + return ServerNodeInstance::isSubclassOf(o, "QQuickParallelAnimation") + || ServerNodeInstance::isSubclassOf(o, "QQuickSequentialAnimation"); + }; + + const QVector anims = animations(); + QSet containers; + for (auto a : anims) { + // Stop all animations by default. We only want to run animations related to currently + // active particle system and nothing else. + a->stop(); + + // Timeline animations are controlled by timeline controls, so exclude those + if (ServerNodeInstance::isSubclassOf(a, "QQuickTimelineAnimation")) + continue; + + if (ServerNodeInstance::isSubclassOf(a, "QQuickPropertyAnimation") + || ServerNodeInstance::isSubclassOf(a, "QQuickPropertyAction")) { + QObject *target = a->property("target").value(); + if (target != m_targetParticleSystem + && !checkAncestor(target, m_targetParticleSystem) + && !checkAncestor(m_targetParticleSystem, target)) { + continue; + } + } else { + continue; + } + + QObject *animParent = a->parent(); + bool isContained = isAnimContainer(animParent); + if (isContained) { + // We only want to start the toplevel container animations + while (isContained) { + if (isAnimContainer(animParent->parent())) { + animParent = animParent->parent(); + isContained = true; + } else { + containers.insert(qobject_cast(animParent)); + isContained = false; + } + } + } else { + a->restart(); + } + } + + // Activate necessary container animations + for (auto container : std::as_const(containers)) + container->restart(); + } } static QString baseProperty(const QString &property) @@ -662,10 +729,6 @@ Qt5InformationNodeInstanceServer::propertyToPropertyValueTriples( if (variant.typeId() == QVariant::Vector3D) { auto vector3d = variant.value(); - - if (vector3d.isNull()) - return result; - const PropertyName dot = propertyName.isEmpty() ? "" : "."; propTriple.instance = instance; propTriple.propertyName = propertyName + dot + PropertyName("x"); @@ -998,7 +1061,7 @@ void Qt5InformationNodeInstanceServer::resolveSceneRoots() ++it; } - updateSceneEnvColorsToHelper(); + updateSceneEnvToHelper(); if (updateActiveScene) { m_active3DView = findView3DForSceneRoot(m_active3DScene); @@ -1844,7 +1907,7 @@ void Qt5InformationNodeInstanceServer::setup3DEditView( m_editView3DSetupDone = true; - updateSceneEnvColorsToHelper(); + updateSceneEnvToHelper(); if (toolStates.contains({})) { // Update tool state to an existing no-scene state before updating the active scene to @@ -2164,15 +2227,15 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm render3DEditView(2); } -void Qt5InformationNodeInstanceServer::setSceneEnvironmentColor( - [[maybe_unused]] const PropertyValueContainer &container) +void Qt5InformationNodeInstanceServer::setSceneEnvironmentData( + [[maybe_unused]] qint32 instanceId) { #ifdef QUICK3D_MODULE auto helper = qobject_cast(m_3dHelper); - if (!helper || !hasInstanceForId(container.instanceId()) || !m_active3DView) + if (!helper || !hasInstanceForId(instanceId) || !m_active3DView) return; - ServerNodeInstance sceneEnvInstance = instanceForId(container.instanceId()); + ServerNodeInstance sceneEnvInstance = instanceForId(instanceId); if (!sceneEnvInstance.isSubclassOf("QQuick3DSceneEnvironment")) return; @@ -2187,17 +2250,14 @@ void Qt5InformationNodeInstanceServer::setSceneEnvironmentColor( ServerNodeInstance activeSceneInstance = active3DSceneInstance(); const QString sceneId = activeSceneInstance.id(); - QColor color = container.value().value(); - helper->setSceneEnvironmentColor(sceneId, color); + helper->setSceneEnvironmentData(sceneId, activeEnv); + QVariantMap toolStates = helper->getToolStates(sceneId); - if (toolStates.contains("syncBackgroundColor")) { - bool sync = toolStates["syncBackgroundColor"].toBool(); - if (sync) { - QList colors{color}; - QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateBackgroundColors", - Q_ARG(QVariant, QVariant::fromValue(colors))); - } + if (toolStates.contains("syncEnvBackground")) { + bool sync = toolStates["syncEnvBackground"].toBool(); + if (sync) + QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateEnvBackground"); } #endif } @@ -2240,15 +2300,15 @@ QVariantList Qt5InformationNodeInstanceServer::alignCameraList() const return cameras; } -void Qt5InformationNodeInstanceServer::updateSceneEnvColorsToHelper() +void Qt5InformationNodeInstanceServer::updateSceneEnvToHelper() { #ifdef QUICK3D_MODULE - // Update stored scene environment colors for all scenes + // Update stored scene environment backgrounds for all scenes auto helper = qobject_cast(m_3dHelper); if (!helper) return; - helper->clearSceneEnvironmentColors(); + helper->clearSceneEnvironmentData(); const auto sceneRoots = m_3DSceneMap.uniqueKeys(); for (QObject *sceneRoot : sceneRoots) { @@ -2260,32 +2320,36 @@ void Qt5InformationNodeInstanceServer::updateSceneEnvColorsToHelper() if (!env) continue; - QColor clearColor = env->clearColor(); - if (clearColor.isValid() && helper) { - ServerNodeInstance sceneInstance; - if (hasInstanceForObject(sceneRoot)) - sceneInstance = instanceForObject(sceneRoot); - else if (hasInstanceForObject(view3D)) - sceneInstance = instanceForObject(view3D); + ServerNodeInstance sceneInstance; + if (hasInstanceForObject(sceneRoot)) + sceneInstance = instanceForObject(sceneRoot); + else if (hasInstanceForObject(view3D)) + sceneInstance = instanceForObject(view3D); - const QString sceneId = sceneInstance.id(); + const QString sceneId = sceneInstance.id(); - helper->setSceneEnvironmentColor(sceneId, clearColor); - } + helper->setSceneEnvironmentData(sceneId, env); } #endif } +bool Qt5InformationNodeInstanceServer::isSceneEnvironmentBgProperty(const PropertyName &name) const +{ + return name == "backgroundMode" || name == "clearColor" + || name == "lightProbe" || name == "skyBoxCubeMap"; +} + void Qt5InformationNodeInstanceServer::changePropertyValues(const ChangeValuesCommand &command) { bool hasDynamicProperties = false; const QVector values = command.valueChanges(); + QSet sceneEnvs; for (const PropertyValueContainer &container : values) { if (!container.isReflected()) { hasDynamicProperties |= container.isDynamic(); - if (container.name() == "clearColor") - setSceneEnvironmentColor(container); + if (isSceneEnvironmentBgProperty(container.name())) + sceneEnvs.insert(container.instanceId()); setInstancePropertyVariant(container); } @@ -2294,6 +2358,9 @@ void Qt5InformationNodeInstanceServer::changePropertyValues(const ChangeValuesCo if (hasDynamicProperties) refreshBindings(); + for (const qint32 id : std::as_const(sceneEnvs)) + setSceneEnvironmentData(id); + startRenderTimer(); render3DEditView(); @@ -2387,8 +2454,8 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c case View3DActionType::ShowCameraFrustum: updatedToolState.insert("showCameraFrustum", command.isEnabled()); break; - case View3DActionType::SyncBackgroundColor: - updatedToolState.insert("syncBackgroundColor", command.isEnabled()); + case View3DActionType::SyncEnvBackground: + updatedToolState.insert("syncEnvBackground", command.isEnabled()); break; #ifdef QUICK3D_PARTICLES_MODULE case View3DActionType::ShowParticleEmitter: @@ -2462,6 +2529,16 @@ void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxilia void Qt5InformationNodeInstanceServer::changePropertyBindings(const ChangeBindingsCommand &command) { Qt5NodeInstanceServer::changePropertyBindings(command); + + QSet sceneEnvs; + for (const PropertyBindingContainer &container : std::as_const(command.bindingChanges)) { + if (isSceneEnvironmentBgProperty(container.name())) + sceneEnvs.insert(container.instanceId()); + } + + for (const qint32 id : std::as_const(sceneEnvs)) + setSceneEnvironmentData(id); + render3DEditView(); } @@ -2502,15 +2579,18 @@ void Qt5InformationNodeInstanceServer::changeState(const ChangeStateCommand &com void Qt5InformationNodeInstanceServer::removeProperties(const RemovePropertiesCommand &command) { const QVector props = command.properties(); + QSet sceneEnvs; + for (const PropertyAbstractContainer &container : props) { - if (container.name() == "clearColor") { - setSceneEnvironmentColor(PropertyValueContainer(container.instanceId(), - container.name(), {}, {})); - } + if (isSceneEnvironmentBgProperty(container.name())) + sceneEnvs.insert(container.instanceId()); } Qt5NodeInstanceServer::removeProperties(command); + for (const qint32 id : std::as_const(sceneEnvs)) + setSceneEnvironmentData(id); + render3DEditView(); } diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index c774bc8f33f..3ac044cbace 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -136,9 +136,10 @@ private: void resetParticleSystem(); void handleParticleSystemDeselected(); #endif - void setSceneEnvironmentColor(const PropertyValueContainer &container); + void setSceneEnvironmentData(qint32 instanceId); QVariantList alignCameraList() const; - void updateSceneEnvColorsToHelper(); + void updateSceneEnvToHelper(); + bool isSceneEnvironmentBgProperty(const PropertyName &name) const; RenderViewData m_editView3DData; RenderViewData m_modelNode3DImageViewData; diff --git a/tests/auto/algorithm/tst_algorithm.cpp b/tests/auto/algorithm/tst_algorithm.cpp index bca8a9dac0d..774ec637256 100644 --- a/tests/auto/algorithm/tst_algorithm.cpp +++ b/tests/auto/algorithm/tst_algorithm.cpp @@ -402,6 +402,24 @@ void tst_Algorithm::transform() const QHash expected({{1, 2}, {2, 3}, {3, 4}, {4, 5}}); QCOMPARE(trans, expected); } + { + // std::vector -> std::vector appending container + const std::vector v({1, 2, 3, 4}); + const auto trans = Utils::transform>(v, [](int i) -> std::vector { + return {i, i * 2}; + }); + const std::vector expected{1, 2, 2, 4, 3, 6, 4, 8}; + QCOMPARE(trans, expected); + } + { + // QList -> QList appending container + const QList v({1, 2, 3, 4}); + const auto trans = Utils::transform>(v, [](int i) -> QList { + return {i, i * 2}; + }); + const QList expected{1, 2, 2, 4, 3, 6, 4, 8}; + QCOMPARE(trans, expected); + } } void tst_Algorithm::sort() diff --git a/tests/auto/qml/connectioneditor/tst_connectioneditor.cpp b/tests/auto/qml/connectioneditor/tst_connectioneditor.cpp index ecb8983271c..06b8f142653 100644 --- a/tests/auto/qml/connectioneditor/tst_connectioneditor.cpp +++ b/tests/auto/qml/connectioneditor/tst_connectioneditor.cpp @@ -104,10 +104,10 @@ void tst_ConnectionEditor::invalidSyntax() = "{someItem.complexCall(blah)}"; //valid QML bit not valid for ConnectionEditor QString result = ConnectionEditorEvaluator::getDisplayStringForType(statement1); - QCOMPARE(result, ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME); + QCOMPARE(result, ConnectionEditorStatements::CUSTOM_DISPLAY_NAME); result = ConnectionEditorEvaluator::getDisplayStringForType(statement2); - QCOMPARE(result, ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME); + QCOMPARE(result, ConnectionEditorStatements::CUSTOM_DISPLAY_NAME); auto resultHandler = ConnectionEditorEvaluator::parseStatement(statement1); auto parsedStatement = ConnectionEditorStatements::okStatement(resultHandler); @@ -168,14 +168,14 @@ void tst_ConnectionEditor::displayStrings_data() QTest::newRow("Custom function call assignment") << "{someItem.color = item.functionCall()}" - << ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME; + << ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; QTest::newRow("Custom function call with argument") << "{someItem.color = item.functionCall(\"test\")}" - << ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME; + << ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; QTest::newRow("Custom function call with argument 2") - << "{item.functionCall(\"test\")}" << ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME; + << "{item.functionCall(\"test\")}" << ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; } void tst_ConnectionEditor::parseAssignment() diff --git a/tests/unit/tests/matchers/CMakeLists.txt b/tests/unit/tests/matchers/CMakeLists.txt index f050cb83c85..32a7dad99c3 100644 --- a/tests/unit/tests/matchers/CMakeLists.txt +++ b/tests/unit/tests/matchers/CMakeLists.txt @@ -7,6 +7,7 @@ add_qtc_library(TestMatchers OBJECT SOURCES info_exportedtypenames-matcher.h import-matcher.h + strippedstring-matcher.h unittest-matchers.h version-matcher.h qvariant-matcher.h diff --git a/tests/unit/tests/matchers/strippedstring-matcher.h b/tests/unit/tests/matchers/strippedstring-matcher.h new file mode 100644 index 00000000000..3c491cba8f8 --- /dev/null +++ b/tests/unit/tests/matchers/strippedstring-matcher.h @@ -0,0 +1,81 @@ +// 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 + +#include + +#include + +namespace Internal { +class StippedStringEqMatcher +{ +public: + explicit StippedStringEqMatcher(QString content) + : m_content(strip(std::move(content))) + {} + + bool MatchAndExplain(const QString &s, testing::MatchResultListener *listener) const + { + auto strippedContent = strip(s); + bool isEqual = m_content == strippedContent; + + if (!isEqual) { + auto [found1, found2] = std::mismatch(strippedContent.begin(), + strippedContent.end(), + m_content.begin(), + m_content.end()); + if (found1 != strippedContent.end() && found2 != m_content.end()) { + *listener << "\ncurrent value mismatch start: \n" + << QStringView{found1, strippedContent.end()}; + *listener << "\nexpected value mismatch start: \n" + << QStringView{found2, m_content.end()}; + } + } + + return isEqual; + } + + static QString strip(QString s) + { + s.replace('\n', ' '); + + auto newEnd = std::unique(s.begin(), s.end(), [](QChar first, QChar second) { + return first.isSpace() && second.isSpace(); + }); + + s.erase(newEnd, s.end()); + + static QRegularExpression regex1{R"xx((\W)\s)xx"}; + s.replace(regex1, R"xx(\1)xx"); + + static QRegularExpression regex2{R"xx(\s(\W))xx"}; + s.replace(regex2, R"xx(\1)xx"); + + return s.trimmed(); + } + + void DescribeTo(::std::ostream *os) const + { + *os << "has stripped content " << testing::PrintToString(m_content); + } + + void DescribeNegationTo(::std::ostream *os) const + { + *os << "doesn't have stripped content " << testing::PrintToString(m_content); + } + +private: + const QString m_content; +}; + +} // namespace Internal + +inline auto StrippedStringEq(const QStringView &content) +{ + return ::testing::PolymorphicMatcher(Internal::StippedStringEqMatcher(content.toString())); +} diff --git a/tests/unit/tests/mocks/CMakeLists.txt b/tests/unit/tests/mocks/CMakeLists.txt index 5323f09e407..bd34e15c68c 100644 --- a/tests/unit/tests/mocks/CMakeLists.txt +++ b/tests/unit/tests/mocks/CMakeLists.txt @@ -2,6 +2,7 @@ add_qtc_library(TestMocks OBJECT EXCLUDE_FROM_INSTALL PROPERTIES SKIP_AUTOGEN ON PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} + INCLUDES ../../../../src/plugins/qmldesigner/components/componentcore DEPENDS Qt::Core Qt::Widgets Googletest Sqlite TestDesignerCore SOURCES @@ -20,6 +21,7 @@ add_qtc_library(TestMocks OBJECT mocktimer.h mocktimestampprovider.h modelresourcemanagementmock.h + propertycomponentgeneratormock.h projectstoragemock.cpp projectstoragemock.h projectstoragepathwatchermock.h diff --git a/tests/unit/tests/mocks/abstractviewmock.h b/tests/unit/tests/mocks/abstractviewmock.h index ed97e7e443c..d7eab46acce 100644 --- a/tests/unit/tests/mocks/abstractviewmock.h +++ b/tests/unit/tests/mocks/abstractviewmock.h @@ -56,4 +56,5 @@ public: AbstractView::PropertyChangeFlags propertyChange), (override)); MOCK_METHOD(void, nodeAboutToBeRemoved, (const QmlDesigner::ModelNode &removedNode), (override)); + MOCK_METHOD(void, refreshMetaInfos, (const QmlDesigner::TypeIds &), (override)); }; diff --git a/tests/unit/tests/mocks/projectstoragemock.cpp b/tests/unit/tests/mocks/projectstoragemock.cpp index 105b8c73469..ac829d21a3f 100644 --- a/tests/unit/tests/mocks/projectstoragemock.cpp +++ b/tests/unit/tests/mocks/projectstoragemock.cpp @@ -112,6 +112,26 @@ QmlDesigner::ImportId ProjectStorageMock::createImportId(QmlDesigner::ModuleId m return importId; } +void ProjectStorageMock::addExportedTypeName(QmlDesigner::TypeId typeId, + QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName) +{ + ON_CALL(*this, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); + ON_CALL(*this, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName))) + .WillByDefault(Return(typeId)); + exportedTypeName[typeId].emplace_back(moduleId, typeName); +} + +void ProjectStorageMock::removeExportedTypeName(QmlDesigner::TypeId typeId, + QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName) +{ + ON_CALL(*this, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(TypeId{})); + ON_CALL(*this, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName))) + .WillByDefault(Return(TypeId{})); + exportedTypeName.erase(typeId); +} + PropertyDeclarationId ProjectStorageMock::createProperty(TypeId typeId, Utils::SmallString name, PropertyDeclarationTraits traits, @@ -139,6 +159,24 @@ PropertyDeclarationId ProjectStorageMock::createProperty(TypeId typeId, return propertyId; } +void ProjectStorageMock::removeProperty(QmlDesigner::TypeId typeId, Utils::SmallString name) +{ + auto propertyId = propertyDeclarationId(typeId, name); + + ON_CALL(*this, propertyDeclarationId(Eq(typeId), Eq(name))) + .WillByDefault(Return(PropertyDeclarationId{})); + ON_CALL(*this, propertyName(Eq(propertyId))) + .WillByDefault(Return(std::optional{})); + + ON_CALL(*this, propertyDeclaration(Eq(propertyId))) + .WillByDefault(Return(std::optional{})); + + ON_CALL(*this, propertyDeclarationIds(Eq(typeId))) + .WillByDefault(Return(QVarLengthArray{})); + ON_CALL(*this, localPropertyDeclarationIds(Eq(typeId))) + .WillByDefault(Return(QVarLengthArray{})); +} + QmlDesigner::PropertyDeclarationId ProjectStorageMock::createProperty( QmlDesigner::TypeId typeId, Utils::SmallString name, QmlDesigner::TypeId propertyTypeId) { @@ -159,6 +197,12 @@ void ProjectStorageMock::createFunction(QmlDesigner::TypeId typeId, Utils::Small ON_CALL(*this, functionDeclarationNames(Eq(typeId))).WillByDefault(Return(functionNames)); } +void ProjectStorageMock::setPropertyEditorPathId(QmlDesigner::TypeId typeId, + QmlDesigner::SourceId sourceId) +{ + ON_CALL(*this, propertyEditorPathId(Eq(typeId))).WillByDefault(Return(sourceId)); +} + namespace { void addBaseProperties(TypeId typeId, TypeIds baseTypeIds, ProjectStorageMock &storage) { @@ -171,6 +215,14 @@ void addBaseProperties(TypeId typeId, TypeIds baseTypeIds, ProjectStorageMock &s } } } + +void setType(TypeId typeId, ModuleId moduleId, Utils::SmallStringView typeName, ProjectStorageMock &storage) +{ + ON_CALL(storage, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); + ON_CALL(storage, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName))) + .WillByDefault(Return(typeId)); +} + } // namespace TypeId ProjectStorageMock::createType(ModuleId moduleId, @@ -189,9 +241,12 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId, static TypeId typeId; incrementBasicId(typeId); - ON_CALL(*this, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); - ON_CALL(*this, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName))) - .WillByDefault(Return(typeId)); + setType(typeId, moduleId, typeName, *this); + + addBaseProperties(typeId, baseTypeIds, *this); + + addExportedTypeName(typeId, moduleId, typeName); + PropertyDeclarationId defaultPropertyDeclarationId; if (defaultPropertyName.size()) { if (!defaultPropertyTypeId) { @@ -205,8 +260,7 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId, } ON_CALL(*this, type(Eq(typeId))) - .WillByDefault( - Return(Storage::Info::Type{defaultPropertyDeclarationId, sourceId, typeTraits})); + .WillByDefault(Return(Storage::Info::Type{defaultPropertyDeclarationId, sourceId, typeTraits})); ON_CALL(*this, isBasedOn(Eq(typeId), Eq(typeId))).WillByDefault(Return(true)); @@ -220,11 +274,20 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId, ON_CALL(*this, prototypeAndSelfIds(Eq(typeId))).WillByDefault(Return(selfAndPrototypes)); ON_CALL(*this, prototypeIds(Eq(typeId))).WillByDefault(Return(baseTypeIds)); - addBaseProperties(typeId, baseTypeIds, *this); - return typeId; } +void ProjectStorageMock::removeType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName) +{ + auto oldTypeId = typeId(moduleId, typeName); + + setType(TypeId{}, moduleId, typeName, *this); + + removeExportedTypeName(oldTypeId, moduleId, typeName); + + ON_CALL(*this, type(Eq(oldTypeId))).WillByDefault(Return(std::optional{})); +} + QmlDesigner::TypeId ProjectStorageMock::createType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, QmlDesigner::Storage::TypeTraits typeTraits, @@ -266,6 +329,13 @@ QmlDesigner::TypeId ProjectStorageMock::createValue(QmlDesigner::ModuleId module return createType(moduleId, typeName, Storage::TypeTraits::Value, baseTypeIds); } +ProjectStorageMock::ProjectStorageMock() +{ + ON_CALL(*this, exportedTypeNames(_)).WillByDefault([&](TypeId id) { + return exportedTypeName[id]; + }); +} + void ProjectStorageMock::setupQtQuick() { setupIsBasedOn(*this); @@ -280,6 +350,7 @@ void ProjectStorageMock::setupQtQuick() auto intId = createValue(qmlModuleId, "int"); createValue(qmlNativeModuleId, "uint"); auto doubleId = createValue(qmlModuleId, "double"); + addExportedTypeName(doubleId, qmlModuleId, "real"); createValue(qmlNativeModuleId, "float"); createValue(qmlModuleId, "date"); auto stringId = createValue(qmlModuleId, "string"); @@ -307,7 +378,7 @@ void ProjectStorageMock::setupQtQuick() createProperty(fontValueTypeId, "overline", boolId); createProperty(fontValueTypeId, "pointSize", doubleId); createProperty(fontValueTypeId, "pixelSize", intId); - createValue(qtQuickModuleId, "font", {fontValueTypeId}); + auto fontId = createValue(qtQuickModuleId, "font", {fontValueTypeId}); auto itemId = createObject(qtQuickModuleId, "Item", @@ -315,6 +386,8 @@ void ProjectStorageMock::setupQtQuick() PropertyDeclarationTraits::IsList, qtObjectId, {qtObjectId}); + createProperty(itemId, "x", doubleId); + createProperty(itemId, "font", fontId); auto inputDeviceId = createObject(qtQuickModuleId, "InputDevice", {qtObjectId}); createProperty(inputDeviceId, "seatName", stringId); diff --git a/tests/unit/tests/mocks/projectstoragemock.h b/tests/unit/tests/mocks/projectstoragemock.h index 61788b46db8..e55841f4786 100644 --- a/tests/unit/tests/mocks/projectstoragemock.h +++ b/tests/unit/tests/mocks/projectstoragemock.h @@ -9,12 +9,14 @@ #include #include +#include #include #include class ProjectStorageMock : public QmlDesigner::ProjectStorageInterface { public: + ProjectStorageMock(); virtual ~ProjectStorageMock() = default; void setupQtQuick(); @@ -40,15 +42,24 @@ public: QmlDesigner::SourceId sourceId, QmlDesigner::Storage::Version version = QmlDesigner::Storage::Version{}); - QmlDesigner::TypeId createType( - QmlDesigner::ModuleId moduleId, - Utils::SmallStringView typeName, - Utils::SmallStringView defaultPropertyName, - QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits, - QmlDesigner::TypeId defaultPropertyTypeId, - QmlDesigner::Storage::TypeTraits typeTraits, - QmlDesigner::TypeIds baseTypeIds = {}, - QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{}); + void addExportedTypeName(QmlDesigner::TypeId typeId, + QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName); + + void removeExportedTypeName(QmlDesigner::TypeId typeId, + QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName); + + QmlDesigner::TypeId createType(QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName, + Utils::SmallStringView defaultPropertyName, + QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits, + QmlDesigner::TypeId defaultPropertyTypeId, + QmlDesigner::Storage::TypeTraits typeTraits, + QmlDesigner::TypeIds baseTypeIds = {}, + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{}); + + void removeType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName); QmlDesigner::TypeId createType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, @@ -83,8 +94,11 @@ public: Utils::SmallString name, QmlDesigner::TypeId propertyTypeId); + void removeProperty(QmlDesigner::TypeId typeId, Utils::SmallString name); + void createSignal(QmlDesigner::TypeId typeId, Utils::SmallString name); void createFunction(QmlDesigner::TypeId typeId, Utils::SmallString name); + void setPropertyEditorPathId(QmlDesigner::TypeId typeId, QmlDesigner::SourceId sourceId); MOCK_METHOD(void, synchronize, @@ -95,6 +109,15 @@ public: (const QmlDesigner::Storage::Imports imports, QmlDesigner::SourceId sourceId), (override)); + MOCK_METHOD(void, + addRefreshCallback, + (std::function * callback), + (override)); + MOCK_METHOD(void, + removeRefreshCallback, + (std::function * callback), + (override)); + MOCK_METHOD(QmlDesigner::ModuleId, moduleId, (::Utils::SmallStringView), (const, override)); MOCK_METHOD(std::optional, @@ -277,6 +300,7 @@ public: (const, override)); QmlDesigner::Storage::Info::CommonTypeCache typeCache{*this}; + std::map exportedTypeName; }; class ProjectStorageMockWithQtQtuick : public ProjectStorageMock diff --git a/tests/unit/tests/mocks/propertycomponentgeneratormock.h b/tests/unit/tests/mocks/propertycomponentgeneratormock.h new file mode 100644 index 00000000000..043b64b464e --- /dev/null +++ b/tests/unit/tests/mocks/propertycomponentgeneratormock.h @@ -0,0 +1,22 @@ +// 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 "../utils/googletest.h" + +#include + +class PropertyComponentGeneratorMock : public QmlDesigner::PropertyComponentGeneratorInterface +{ + using Property = QmlDesigner::PropertyComponentGeneratorInterface::Property; + +public: + virtual ~PropertyComponentGeneratorMock() = default; + MOCK_METHOD(Property, + create, + (const QmlDesigner::PropertyMetaInfo &), + + (const, override)); + MOCK_METHOD(QStringList, imports, (), (const, override)); +}; diff --git a/tests/unit/tests/printers/gtest-qt-printing.cpp b/tests/unit/tests/printers/gtest-qt-printing.cpp index 532aef525f5..f432d87398c 100644 --- a/tests/unit/tests/printers/gtest-qt-printing.cpp +++ b/tests/unit/tests/printers/gtest-qt-printing.cpp @@ -33,6 +33,11 @@ std::ostream &operator<<(std::ostream &out, const QString &text) return out << text.toUtf8(); } +std::ostream &operator<<(std::ostream &out, QStringView text) +{ + return out << text.toString(); +} + std::ostream &operator<<(std::ostream &out, const QVariant &variant) { QString output; diff --git a/tests/unit/tests/printers/gtest-qt-printing.h b/tests/unit/tests/printers/gtest-qt-printing.h index c84697c17b0..ceec63817cc 100644 --- a/tests/unit/tests/printers/gtest-qt-printing.h +++ b/tests/unit/tests/printers/gtest-qt-printing.h @@ -11,12 +11,14 @@ QT_BEGIN_NAMESPACE class QVariant; class QString; +class QStringView; class QTextCharFormat; class QImage; class QIcon; std::ostream &operator<<(std::ostream &out, const QVariant &QVariant); std::ostream &operator<<(std::ostream &out, const QString &text); +std::ostream &operator<<(std::ostream &out, QStringView text); std::ostream &operator<<(std::ostream &out, const QByteArray &byteArray); std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format); std::ostream &operator<<(std::ostream &out, const QImage &image); diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index 9a10b6d7449..0847caa3230 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -65,6 +65,7 @@ add_qtc_library(TestDesignerCore OBJECT include/metainfo.h include/metainforeader.h include/modelnode.h + include/module.h include/nodeabstractproperty.h include/nodelistproperty.h include/nodemetainfo.h diff --git a/tests/unit/tests/unittests/CMakeLists.txt b/tests/unit/tests/unittests/CMakeLists.txt index 5de9ef141c8..7088bedcc0a 100644 --- a/tests/unit/tests/unittests/CMakeLists.txt +++ b/tests/unit/tests/unittests/CMakeLists.txt @@ -38,6 +38,7 @@ function(unittest_copy_data_folder) ) endfunction(unittest_copy_data_folder) +add_subdirectory(componentcore) add_subdirectory(listmodeleditor) add_subdirectory(imagecache) add_subdirectory(metainfo) diff --git a/tests/unit/tests/unittests/componentcore/CMakeLists.txt b/tests/unit/tests/unittests/componentcore/CMakeLists.txt new file mode 100644 index 00000000000..913fe21bea4 --- /dev/null +++ b/tests/unit/tests/unittests/componentcore/CMakeLists.txt @@ -0,0 +1,15 @@ +extend_qtc_test(unittest + SOURCES + propertycomponentgenerator-test.cpp + propertyeditorcomponentgenerator-test.cpp +) + +extend_qtc_test(unittest + SOURCES_PREFIX ../../../../../src/plugins/qmldesigner/components/componentcore + SOURCES + propertyeditorcomponentgenerator.cpp propertyeditorcomponentgenerator.h + propertycomponentgenerator.cpp propertycomponentgenerator.h + propertycomponentgeneratorinterface.h +) + + diff --git a/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp b/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp new file mode 100644 index 00000000000..10e4cc32525 --- /dev/null +++ b/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp @@ -0,0 +1,374 @@ +// 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 +#include + +#include + +using namespace Qt::StringLiterals; + +namespace QmlDesigner { + +std::ostream &operator<<(std::ostream &out, const PropertyComponentGenerator::BasicProperty &property) +{ + return out << "BasicProperty(" << property.propertyName << ", " << property.component << ")"; +} + +std::ostream &operator<<(std::ostream &out, const PropertyComponentGenerator::ComplexProperty &property) +{ + return out << "ComplexPropertiesFromTemplates(" << property.propertyName << ", " + << property.component << ")"; +} + +} // namespace QmlDesigner + +namespace { + +using BasicProperty = QmlDesigner::PropertyComponentGenerator::BasicProperty; +using ComplexProperty = QmlDesigner::PropertyComponentGenerator::ComplexProperty; + +template +auto IsBasicProperty(const Matcher &matcher) +{ + return VariantWith(Field(&BasicProperty::component, matcher)); +} + +template +auto IsComplexProperty(const Matcher &matcher) +{ + return VariantWith(Field(&ComplexProperty::component, matcher)); +} + +constexpr Utils::SmallStringView sourcesPath = UNITTEST_DIR + "/../../../../share/qtcreator/qmldesigner/propertyEditorQmlSources"; + +class PropertyComponentGenerator : public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + simpleReaderNode = createTemplateConfiguration(QString{sourcesPath}); + } + + static void TearDownTestSuite() { simpleReaderNode.reset(); } + + static QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath) + { + QmlJS::SimpleReader reader; + const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml"; + auto templateConfiguration = reader.readFile(fileName); + + if (!templateConfiguration) + qWarning().nospace() << "template definitions:" << reader.errors(); + + return templateConfiguration; + } + + template + static Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name) + { + if (auto property = node->property(name)) { + const auto &value = property.value; + if (value.type() == QVariant::List) { + auto list = value.toList(); + if (list.size()) + return list.front().value(); + } else { + return property.value.value(); + } + } + + return {}; + } + + static QString getSource(const QString &name) + { + const auto &nodes = simpleReaderNode->children(); + for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) { + if (auto typeName = getProperty(node.get(), "typeNames"); typeName != name) + continue; + + auto sourcePath = getProperty(node.get(), "sourceFile"); + + return getContent(QString{sourcesPath} + "/PropertyTemplates/" + sourcePath); + } + + return {}; + } + + static QString getContent(const QString &path) + { + QFile file{path}; + + if (file.open(QIODevice::ReadOnly)) + return QString::fromUtf8(file.readAll()); + + return {}; + } + + static QString getExpectedContent(const QString &typeName, + const QString &propertyName, + const QString &binding) + { + auto source = getSource(typeName); + + return source.arg(propertyName, binding); + } + + QmlDesigner::NodeMetaInfo createTypeWithProperties(Utils::SmallStringView name, + QmlDesigner::TypeId propertyTypeId) + { + auto typeId = projectStorageMock.createValue(qmlModuleId, name); + projectStorageMock.createProperty(typeId, "sub1", propertyTypeId); + projectStorageMock.createProperty(typeId, "sub2", propertyTypeId); + + return {typeId, &projectStorageMock}; + } + + QmlDesigner::NodeMetaInfo createType(Utils::SmallStringView name) + { + auto typeId = projectStorageMock.createValue(qmlModuleId, name); + + return {typeId, &projectStorageMock}; + } + + QmlDesigner::PropertyMetaInfo createProperty(QmlDesigner::TypeId typeId, + Utils::SmallString name, + QmlDesigner::Storage::PropertyDeclarationTraits traits, + QmlDesigner::TypeId propertyTypeId) + { + auto propertyId = projectStorageMock.createProperty(typeId, name, traits, propertyTypeId); + + return {propertyId, &projectStorageMock}; + } + + QString toString(const QmlDesigner::PropertyComponentGenerator::Property &propertyComponent, + const QString &baseName, + const QString &propertyName) + { + auto text = std::visit( + [](const auto &p) -> QString { + if constexpr (!std::is_same_v, std::monostate>) + return p.component; + else + return {}; + }, + propertyComponent); + + text.replace(propertyName, baseName + "."_L1 + propertyName); + text.replace("backendValues."_L1 + baseName + "."_L1 + propertyName, + "backendValues."_L1 + baseName + "_"_L1 + propertyName); + + return text; + } + +protected: + inline static QSharedPointer simpleReaderNode; + NiceMock viewMock; + NiceMock pathCacheMock{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCacheMock.sourceId}; + NiceMock resourceManagementMock; + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, + "Item", + -1, + -1, + nullptr, + std::make_unique( + resourceManagementMock)}; + QmlDesigner::PropertyComponentGenerator generator{QString{sourcesPath}, &model}; + QmlDesigner::NodeMetaInfo itemMetaInfo = model.qtQuickItemMetaInfo(); + QmlDesigner::ModuleId qmlModuleId = projectStorageMock.createModule("QML"); +}; + +TEST_F(PropertyComponentGenerator, + basic_property_is_returned_for_property_with_a_template_with_no_separate_section) +{ + QString expected = getExpectedContent("real", "x", "x"); + auto xProperty = itemMetaInfo.property("x"); + + auto propertyComponent = generator.create(xProperty); + + ASSERT_THAT(propertyComponent, IsBasicProperty(StrippedStringEq(expected))); +} + +TEST_F(PropertyComponentGenerator, + complex_property_is_returned_for_property_with_a_template_with_a_separate_section) +{ + QString expected = getExpectedContent("font", "font", "font"); + auto fontProperty = itemMetaInfo.property("font"); + + auto propertyComponent = generator.create(fontProperty); + + ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expected))); +} + +TEST_F(PropertyComponentGenerator, + complex_property_is_returned_for_property_without_a_template_and_subproperties) +{ + auto stringId = projectStorageMock.createValue(qmlModuleId, "string"); + auto fooNodeInfo = createTypeWithProperties("Foo", stringId); + auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1"); + auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2"); + auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id()); + QString expectedText = QStringLiteral( + R"xy( + Section { + caption: foo - Foo + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 8 + rightPadding: 0 + expanded: false + level: 1 + SectionLayout { + )xy"); + expectedText += sub1Text; + expectedText += sub2Text; + expectedText += "}}"; + + auto propertyComponent = generator.create(fooProperty); + + ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expectedText))); +} + +TEST_F(PropertyComponentGenerator, + pointer_readonly_complex_property_is_returned_for_property_without_a_template_and_subproperties) +{ + auto stringId = projectStorageMock.createValue(qmlModuleId, "string"); + auto fooNodeInfo = createTypeWithProperties("Foo", stringId); + auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1"); + auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2"); + auto fooProperty = createProperty(itemMetaInfo.id(), + "foo", + QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer + | QmlDesigner::Storage::PropertyDeclarationTraits::IsReadOnly, + fooNodeInfo.id()); + QString expectedText = QStringLiteral( + R"xy( + Section { + caption: foo - Foo + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 8 + rightPadding: 0 + expanded: false + level: 1 + SectionLayout { + )xy"); + expectedText += sub1Text; + expectedText += sub2Text; + expectedText += "}}"; + + auto propertyComponent = generator.create(fooProperty); + + ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expectedText))); +} + +TEST_F(PropertyComponentGenerator, basic_property_without_template_is_returning_monostate) +{ + auto fooNodeInfo = createType("Foo"); + auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id()); + + auto propertyComponent = generator.create(fooProperty); + + ASSERT_THAT(propertyComponent, VariantWith(std::monostate{})); +} + +TEST_F(PropertyComponentGenerator, + complex_property_without_templates_and_writeable_is_returning_monostate) +{ + auto barTypeId = projectStorageMock.createValue(qmlModuleId, "bar"); + auto fooNodeInfo = createTypeWithProperties("Foo", barTypeId); + auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id()); + + auto propertyComponent = generator.create(fooProperty); + + ASSERT_THAT(propertyComponent, VariantWith(std::monostate{})); +} + +TEST_F(PropertyComponentGenerator, + pointer_writeable_complex_property_without_templates_and_with_only_subproperties_without_templates_is_returning_monostate) +{ + auto stringId = projectStorageMock.createValue(qmlModuleId, "string"); + auto fooNodeInfo = createTypeWithProperties("Foo", stringId); + auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1"); + auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2"); + auto fooProperty = createProperty(itemMetaInfo.id(), + "foo", + QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer, + fooNodeInfo.id()); + + auto propertyComponent = generator.create(fooProperty); + + ASSERT_THAT(propertyComponent, VariantWith(std::monostate{})); +} + +TEST_F(PropertyComponentGenerator, get_imports) +{ + auto imports = generator.imports(); + + ASSERT_THAT(imports, + ElementsAre(Eq("import HelperWidgets 2.0"), + Eq("import QtQuick 2.15"), + Eq("import QtQuick.Layouts 1.15"), + Eq("import StudioTheme 1.0 as StudioTheme"))); +} + +TEST_F(PropertyComponentGenerator, set_model_to_null_removes_creates_only_monostates) +{ + QString expected = getExpectedContent("real", "x", "x"); + auto xProperty = itemMetaInfo.property("x"); + + generator.setModel(nullptr); + + ASSERT_THAT(generator.create(xProperty), VariantWith(std::monostate{})); +} + +TEST_F(PropertyComponentGenerator, set_model_fromn_null_updates_internal_state) +{ + generator.setModel(nullptr); + QString expected = getExpectedContent("real", "x", "x"); + auto xProperty = itemMetaInfo.property("x"); + + generator.setModel(&model); + + ASSERT_THAT(generator.create(xProperty), IsBasicProperty(StrippedStringEq(expected))); +} + +TEST_F(PropertyComponentGenerator, after_refresh_meta_infos_type_was_deleted) +{ + auto xProperty = itemMetaInfo.property("x"); + auto doubleMetaInfo = model.doubleMetaInfo(); + projectStorageMock.removeExportedTypeName(doubleMetaInfo.id(), + projectStorageMock.createModule("QML"), + "real"); + + generator.refreshMetaInfos({doubleMetaInfo.id()}); + + ASSERT_THAT(generator.create(xProperty), VariantWith(std::monostate{})); +} + +TEST_F(PropertyComponentGenerator, after_refresh_meta_infos_type_was_added) +{ + QString expected = getExpectedContent("real", "x", "x"); + auto xProperty = itemMetaInfo.property("x"); + auto doubleMetaInfo = model.doubleMetaInfo(); + projectStorageMock.removeExportedTypeName(doubleMetaInfo.id(), + projectStorageMock.createModule("QML"), + "real"); + generator.refreshMetaInfos({doubleMetaInfo.id()}); + projectStorageMock.addExportedTypeName(doubleMetaInfo.id(), + projectStorageMock.createModule("QML"), + "real"); + + generator.refreshMetaInfos({}); + + ASSERT_THAT(generator.create(xProperty), IsBasicProperty(StrippedStringEq(expected))); +} + +} // namespace diff --git a/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp b/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp new file mode 100644 index 00000000000..3b9a8bbfe25 --- /dev/null +++ b/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp @@ -0,0 +1,487 @@ +// 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 { + +using BasicProperty = QmlDesigner::PropertyComponentGenerator::BasicProperty; +using ComplexProperty = QmlDesigner::PropertyComponentGenerator::ComplexProperty; +using QmlDesigner::PropertyMetaInfo; + +class PropertyEditorComponentGenerator : public ::testing::Test +{ +protected: + QmlDesigner::NodeMetaInfo createType(Utils::SmallStringView name, + QmlDesigner::TypeIds baseTypeIds = {}) + { + auto typeId = projectStorageMock.createValue(qtQuickModuleId, name, baseTypeIds); + + return {typeId, &projectStorageMock}; + } + + QmlDesigner::PropertyMetaInfo createProperty(QmlDesigner::TypeId typeId, + Utils::SmallString name, + QmlDesigner::Storage::PropertyDeclarationTraits traits, + QmlDesigner::TypeId propertyTypeId) + { + auto propertyId = projectStorageMock.createProperty(typeId, name, traits, propertyTypeId); + + return {propertyId, &projectStorageMock}; + } + + void setImports(const QStringList &imports) + { + ON_CALL(propertyGeneratorMock, imports()).WillByDefault(Return(imports)); + } + + void addBasicProperty(const PropertyMetaInfo &property, + Utils::SmallStringView name, + const QString &componentContent) + { + ON_CALL(propertyGeneratorMock, create(property)) + .WillByDefault(Return(BasicProperty{name, componentContent})); + } + + void addComplexProperty(const PropertyMetaInfo &property, + Utils::SmallStringView name, + const QString &componentContent) + { + ON_CALL(propertyGeneratorMock, create(property)) + .WillByDefault(Return(ComplexProperty{name, componentContent})); + } + + QmlDesigner::PropertyMetaInfo createBasicProperty(QmlDesigner::TypeId typeId, + Utils::SmallString name, + QmlDesigner::Storage::PropertyDeclarationTraits traits, + QmlDesigner::TypeId propertyTypeId, + const QString &componentContent) + { + auto propertyInfo = createProperty(typeId, name, traits, propertyTypeId); + addBasicProperty(propertyInfo, name, componentContent); + + return propertyInfo; + } + + QmlDesigner::PropertyMetaInfo createComplexProperty( + QmlDesigner::TypeId typeId, + Utils::SmallString name, + QmlDesigner::Storage::PropertyDeclarationTraits traits, + QmlDesigner::TypeId propertyTypeId, + const QString &componentContent) + { + auto propertyInfo = createProperty(typeId, name, traits, propertyTypeId); + addComplexProperty(propertyInfo, name, componentContent); + + return propertyInfo; + } + +protected: + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId::create(10); + NiceMock projectStorageMock{sourceId}; + NiceMock propertyGeneratorMock; + QmlDesigner::PropertyEditorComponentGenerator generator{propertyGeneratorMock}; + QmlDesigner::ModuleId qtQuickModuleId = projectStorageMock.createModule("QtQuick"); + QmlDesigner::NodeMetaInfo fooTypeInfo = createType("Foo"); + QmlDesigner::TypeId dummyTypeId = projectStorageMock.commonTypeCache().builtinTypeId(); +}; + +TEST_F(PropertyEditorComponentGenerator, no_properties_and_no_imports) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + } + } + })xy"}; + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, properties_without_component_are_not_shows) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + } + } + })xy"}; + createProperty(fooTypeInfo.id(), "x", {}, dummyTypeId); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, show_component_button_for_a_component_node) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + ComponentButton {} + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + } + } + })xy"}; + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), true); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, imports) +{ + QString expectedText{ + R"xy( + import QtQtuick + import Studio 2.1 + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + } + } + })xy"}; + setImports({"import QtQtuick", "import Studio 2.1"}); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, basic_property) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout { + Double{} + } + } + } + } + })xy"}; + createBasicProperty(fooTypeInfo.id(), "value", {}, dummyTypeId, "Double{}"); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, basic_properties_with_base_type) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout { + SuperDouble{} + Double{} + } + } + } + } + })xy"}; + createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}"); + auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()}); + createBasicProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperDouble{}"); + + auto text = generator.create(superFooInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, + only_handle_basic_properties_for_types_without_specifics_or_panes) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout { + SuperDouble{} + } + } + } + } + })xy"}; + createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}"); + auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()}); + createBasicProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperDouble{}"); + projectStorageMock.setPropertyEditorPathId(fooTypeInfo.id(), sourceId); + + auto text = generator.create(superFooInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, order_basic_property) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout { + AnotherX{} + AndY{} + SomeZ{} + } + } + } + } + })xy"}; + createBasicProperty(fooTypeInfo.id(), "z", {}, dummyTypeId, "SomeZ{}"); + createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "AnotherX{}"); + createBasicProperty(fooTypeInfo.id(), "y", {}, dummyTypeId, "AndY{}"); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, complex_property) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Complex{} + } + } + })xy"}; + createComplexProperty(fooTypeInfo.id(), "value", {}, dummyTypeId, "Complex{}"); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, complex_properties_with_base_type) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Complex{} + SuperComplex{} + } + } + })xy"}; + createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Complex{}"); + auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()}); + createComplexProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperComplex{}"); + + auto text = generator.create(superFooInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, + only_handle_complex_properties_for_types_without_specifics_or_panes) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + SuperComplex{} + } + } + })xy"}; + createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Complex{}"); + auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()}); + createComplexProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperComplex{}"); + projectStorageMock.setPropertyEditorPathId(fooTypeInfo.id(), sourceId); + + auto text = generator.create(superFooInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, ordered_complex_property) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Anchors{} + Delegate{} + ComplexFont{} + } + } + })xy"}; + createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Delegate{}"); + createComplexProperty(fooTypeInfo.id(), "font", {}, dummyTypeId, "ComplexFont{}"); + createComplexProperty(fooTypeInfo.id(), "anchors", {}, dummyTypeId, "Anchors{}"); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} + +TEST_F(PropertyEditorComponentGenerator, basic_is_placed_before_complex_components) +{ + QString expectedText{ + R"xy( + Column { + width: parent.width + Section { + caption: "Exposed Custom Properties" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout { + Double{} + } + } + Font{} + } + } + })xy"}; + createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}"); + createComplexProperty(fooTypeInfo.id(), "font", {}, dummyTypeId, "Font{}"); + + auto text = generator.create(fooTypeInfo.selfAndPrototypes(), false); + + ASSERT_THAT(text, StrippedStringEq(expectedText)); +} +} // namespace diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp index c1f3b4f8d7c..c21d3259365 100644 --- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp @@ -409,7 +409,7 @@ TEST_F(NodeMetaInfo, get_invalid_property_if_not_exists) { auto metaInfo = model.qtQuickItemMetaInfo(); - auto property = metaInfo.property("x"); + auto property = metaInfo.property("foo"); ASSERT_THAT(property, PropertyId(IsFalse())); } diff --git a/tests/unit/tests/unittests/model/model-test.cpp b/tests/unit/tests/unittests/model/model-test.cpp index 52cc04564a8..71f6462c7cc 100644 --- a/tests/unit/tests/unittests/model/model-test.cpp +++ b/tests/unit/tests/unittests/model/model-test.cpp @@ -869,4 +869,75 @@ TEST_F(Model, meta_info_of_not_existing_type_is_invalid) ASSERT_THAT(meta_info, IsFalse()); } +TEST_F(Model, module_is_valid) +{ + auto module = model.module("QML"); + + ASSERT_THAT(module, IsTrue()); +} + +TEST_F(Model, module_returns_always_the_same) +{ + auto oldModule = model.module("QML"); + + auto module = model.module("QML"); + + ASSERT_THAT(module, oldModule); +} + +TEST_F(Model, get_meta_info_by_module) +{ + auto module = model.module("QML"); + + auto metaInfo = model.metaInfo(module, "QtObject"); + + ASSERT_THAT(metaInfo, model.qmlQtObjectMetaInfo()); +} + +TEST_F(Model, get_invalid_meta_info_by_module_for_wrong_name) +{ + auto module = model.module("QML"); + + auto metaInfo = model.metaInfo(module, "Object"); + + ASSERT_THAT(metaInfo, IsFalse()); +} + +TEST_F(Model, get_invalid_meta_info_by_module_for_wrong_module) +{ + auto module = model.module("Qml"); + + auto metaInfo = model.metaInfo(module, "Object"); + + ASSERT_THAT(metaInfo, IsFalse()); +} + +TEST_F(Model, add_refresh_callback_to_project_storage) +{ + EXPECT_CALL(projectStorageMock, addRefreshCallback(_)); + + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; +} + +TEST_F(Model, remove_refresh_callback_from_project_storage) +{ + EXPECT_CALL(projectStorageMock, removeRefreshCallback(_)).Times(2); // there is a model in the fixture + + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; +} + +TEST_F(Model, refresh_callback_is_calling_abstract_view) +{ + const QmlDesigner::TypeIds typeIds = {QmlDesigner::TypeId::create(3), + QmlDesigner::TypeId::create(1)}; + std::function *callback = nullptr; + ON_CALL(projectStorageMock, addRefreshCallback(_)).WillByDefault([&](auto *c) { callback = c; }); + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; + model.attachView(&viewMock); + + EXPECT_CALL(viewMock, refreshMetaInfos(typeIds)); + + (*callback)(typeIds); +} + } // namespace diff --git a/tests/unit/tests/unittests/model/modelutils-test.cpp b/tests/unit/tests/unittests/model/modelutils-test.cpp index 4418d36ac83..8d69e69aba8 100644 --- a/tests/unit/tests/unittests/model/modelutils-test.cpp +++ b/tests/unit/tests/unittests/model/modelutils-test.cpp @@ -3,13 +3,16 @@ #include +#include #include #include - #include #include +#include namespace { +using QmlDesigner::ModelNode; +using QmlDesigner::ModelNodes; class ModelUtils : public ::testing::Test { @@ -106,4 +109,59 @@ TEST_F(ModelUtils, component_file_path_for_non_file_component_node_is_empty) ASSERT_THAT(path, IsEmpty()); } +TEST_F(ModelUtils, find_lowest_common_ancestor) +{ + auto child1 = model.createModelNode("Item"); + auto child2 = model.createModelNode("Item"); + model.rootModelNode().defaultNodeAbstractProperty().reparentHere(child1); + model.rootModelNode().defaultNodeAbstractProperty().reparentHere(child2); + ModelNodes nodes{child1, child2}; + + auto commonAncestor = QmlDesigner::ModelUtils::lowestCommonAncestor(nodes); + + ASSERT_THAT(commonAncestor, model.rootModelNode()); +} + +TEST_F(ModelUtils, lowest_common_ancestor_return_invalid_node_if_argument_is_invalid) +{ + auto child1 = model.createModelNode("Item"); + auto child2 = ModelNode{}; + model.rootModelNode().defaultNodeAbstractProperty().reparentHere(child1); + ModelNodes nodes{child1, child2}; + + auto commonAncestor = QmlDesigner::ModelUtils::lowestCommonAncestor(nodes); + + ASSERT_THAT(commonAncestor, Not(IsValid())); +} + +TEST_F(ModelUtils, find_lowest_common_ancestor_when_one_of_the_nodes_is_parent) +{ + auto parentNode = model.createModelNode("Item"); + auto childNode = model.createModelNode("Item"); + parentNode.defaultNodeAbstractProperty().reparentHere(childNode); + model.rootModelNode().defaultNodeAbstractProperty().reparentHere(parentNode); + ModelNodes nodes{childNode, parentNode}; + + auto commonAncestor = QmlDesigner::ModelUtils::lowestCommonAncestor(nodes); + + ASSERT_THAT(commonAncestor, parentNode); +} + +TEST_F(ModelUtils, lowest_common_ancestor_for_uncle_and_nephew_should_return_the_grandFather) +{ + auto grandFatherNode = model.createModelNode("Item"); + auto fatherNode = model.createModelNode("Item"); + auto uncleNode = model.createModelNode("Item"); + auto nephewNode = model.createModelNode("Item"); + fatherNode.defaultNodeAbstractProperty().reparentHere(nephewNode); + grandFatherNode.defaultNodeAbstractProperty().reparentHere(fatherNode); + grandFatherNode.defaultNodeAbstractProperty().reparentHere(uncleNode); + model.rootModelNode().defaultNodeAbstractProperty().reparentHere(grandFatherNode); + ModelNodes nodes{uncleNode, nephewNode}; + + auto commonAncestor = QmlDesigner::ModelUtils::lowestCommonAncestor(nodes); + + ASSERT_THAT(commonAncestor, grandFatherNode); +} + } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index e666493e60c..f6ea7a015a7 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -5332,6 +5332,19 @@ TEST_F(ProjectStorage, module_exported_import) UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } +TEST_F(ProjectStorage, module_exported_import_deletes_indirect_imports_too) +{ + auto package{createModuleExportedImportSynchronizationPackage()}; + storage.synchronize(package); + SynchronizationPackage packageWhichDeletesImports; + packageWhichDeletesImports.updatedSourceIds = package.updatedSourceIds; + storage.synchronize(packageWhichDeletesImports); + + auto imports = storage.fetchDocumentImports(); + + ASSERT_THAT(imports, IsEmpty()); +} + TEST_F(ProjectStorage, module_exported_import_with_different_versions) { auto package{createModuleExportedImportSynchronizationPackage()}; @@ -7192,4 +7205,16 @@ TEST_F(ProjectStorage, synchronize_property_editor_with_non_existing_type_name) ASSERT_THAT(storage.propertyEditorPathId(fetchTypeId(sourceId4, "Item4D")), IsFalse()); } +TEST_F(ProjectStorage, call_refresh_callback_after_synchronization) +{ + auto package{createSimpleSynchronizationPackage()}; + MockFunction callbackMock; + auto callback = callbackMock.AsStdFunction(); + storage.addRefreshCallback(&callback); + + EXPECT_CALL(callbackMock, Call(_)); + + storage.synchronize(package); +} + } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp index 2b7c4b83648..c167e3eb8b5 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp @@ -1164,28 +1164,27 @@ TEST_F(ProjectStorageUpdater, update_qml_types_files_is_empty) TEST_F(ProjectStorageUpdater, update_qml_types_files) { EXPECT_CALL(projectStorageMock, - synchronize( - AllOf(Field(&SynchronizationPackage::imports, - UnorderedElementsAre(import4, import5)), - Field(&SynchronizationPackage::types, - UnorderedElementsAre(objectType, itemType)), - Field(&SynchronizationPackage::updatedSourceIds, - UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId)), - Field(&SynchronizationPackage::fileStatuses, - UnorderedElementsAre(IsFileStatus(qmltypesPathSourceId, 1, 21), - IsFileStatus(qmltypes2PathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedFileStatusSourceIds, - UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId)), - Field(&SynchronizationPackage::projectDatas, - UnorderedElementsAre(IsProjectData(qmltypesPathSourceId, - qmltypesPathSourceId, - builtinCppNativeModuleId, - FileType::QmlTypes), - IsProjectData(qmltypes2PathSourceId, - qmltypes2PathSourceId, - builtinCppNativeModuleId, - FileType::QmlTypes))), - Field(&SynchronizationPackage::updatedProjectSourceIds, IsEmpty())))); + synchronize(AllOf( + Field(&SynchronizationPackage::imports, UnorderedElementsAre(import4, import5)), + Field(&SynchronizationPackage::types, UnorderedElementsAre(objectType, itemType)), + Field(&SynchronizationPackage::updatedSourceIds, + UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId)), + Field(&SynchronizationPackage::fileStatuses, + UnorderedElementsAre(IsFileStatus(qmltypesPathSourceId, 1, 21), + IsFileStatus(qmltypes2PathSourceId, 1, 21))), + Field(&SynchronizationPackage::updatedFileStatusSourceIds, + UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId)), + Field(&SynchronizationPackage::projectDatas, + UnorderedElementsAre(IsProjectData(qmltypesPathSourceId, + qmltypesPathSourceId, + builtinCppNativeModuleId, + FileType::QmlTypes), + IsProjectData(qmltypes2PathSourceId, + qmltypes2PathSourceId, + builtinCppNativeModuleId, + FileType::QmlTypes))), + Field(&SynchronizationPackage::updatedProjectSourceIds, + UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId))))); updater.update({}, {"/path/example.qmltypes", "/path/example2.qmltypes"}, {}); } @@ -1209,7 +1208,8 @@ TEST_F(ProjectStorageUpdater, dont_update_qml_types_files_if_unchanged) qmltypesPathSourceId, builtinCppNativeModuleId, FileType::QmlTypes))), - Field(&SynchronizationPackage::updatedProjectSourceIds, IsEmpty())))); + Field(&SynchronizationPackage::updatedProjectSourceIds, + UnorderedElementsAre(qmltypesPathSourceId))))); updater.update({}, {"/path/example.qmltypes", "/path/example2.qmltypes"}, {}); } diff --git a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp index 68138232b20..ba4b4865b86 100644 --- a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp @@ -53,6 +53,17 @@ MATCHER_P5(IsType, && type.traits == traits && type.sourceId == sourceId; } +MATCHER_P(HasFlag, flag, std::string(negation ? "hasn't " : "has ") + PrintToString(flag)) +{ + return bool(arg & flag); +} + +template +auto IsTypeTrait(const Matcher &matcher) +{ + return Field(&Synchronization::Type::traits, matcher); +} + MATCHER_P3(IsPropertyDeclaration, name, typeName, @@ -740,4 +751,76 @@ TEST_F(QmlTypesParser, alias_enumeration_is_referenced_by_qualified_name) QmlDesigner::Storage::PropertyDeclarationTraits::None))))); } +TEST_F(QmlTypesParser, access_type_is_reference) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + accessSemantics: "reference"}})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Reference))); +} + +TEST_F(QmlTypesParser, access_type_is_value) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + accessSemantics: "value"}})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Value))); +} + +TEST_F(QmlTypesParser, access_type_is_sequence) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + accessSemantics: "sequence"}})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Sequence))); +} + +TEST_F(QmlTypesParser, access_type_is_none) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + accessSemantics: "none"}})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::None))); +} + +TEST_F(QmlTypesParser, uses_custom_parser) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + hasCustomParser: true }})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(HasFlag(Storage::TypeTraits::UsesCustomParser)))); +} + +TEST_F(QmlTypesParser, uses_no_custom_parser) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + hasCustomParser: false }})"}; + + parser.parse(source, imports, types, projectData); + + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Not(HasFlag(Storage::TypeTraits::UsesCustomParser))))); +} + } // namespace diff --git a/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp b/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp index 4131ec48a56..a1ad263cf7c 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp +++ b/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp @@ -10,7 +10,7 @@ namespace { constexpr QLatin1String localTestDataDir{UNITTEST_DIR "/qmlprojectmanager/data"}; -class DataSet : public testing::TestWithParam +class QmlProjectConverter : public testing::TestWithParam { public: void setDataSource(const QString &dataSetName) @@ -64,10 +64,12 @@ private: }; INSTANTIATE_TEST_SUITE_P(QmlProjectItem, - DataSet, - ::testing::Values(QString("test-set-1"), QString("test-set-2"))); + QmlProjectConverter, + ::testing::Values(QString("test-set-1"), + QString("test-set-2"), + QString("test-set-3"))); -TEST_P(DataSet, qml_project_to_json) +TEST_P(QmlProjectConverter, qml_project_to_json) { // GIVEN setDataSource(GetParam()); @@ -82,7 +84,7 @@ TEST_P(DataSet, qml_project_to_json) ASSERT_THAT(convertedContent, Eq(targetContent)); } -TEST_P(DataSet, json_to_qml_project) +TEST_P(QmlProjectConverter, json_to_qml_project) { // GIVEN setDataSource(GetParam()); diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md index 0edd94edd93..bffd46b79ee 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md @@ -25,6 +25,12 @@ Test functions iterate over the "test-set-*" folders and run the tests by using * **purpose**: testing fileselectors * **origin**: file selectors example from playground +### test-set-3 + +* **purpose**: testing `QDS.` prefixes +* **origin**: copy of test-set-1 + + ## File Filters test data Test data contains an example project folders that file filters will be initialized and tested. diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml new file mode 100644 index 00000000000..dbd6e4a91ac --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml @@ -0,0 +1,98 @@ +// prop: json-converted +// prop: auto-generated + +import QmlProject + +Project { + mainFile: "content/App.qml" + mainUiFile: "content/Screen01.ui.qml" + targetDirectory: "/opt/UntitledProject13" + widgetApp: true + importPaths: [ "imports","asset_imports" ] + + qdsVersion: "4.0" + quickVersion: "6.2" + qt6Project: true + qtForMCUs: true + + multilanguageSupport: true + primaryLanguage: "en" + supportedLanguages: [ "en" ] + + Environment { + QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1" + QT_AUTO_SCREEN_SCALE_FACTOR: "1" + QT_ENABLE_HIGHDPI_SCALING: "0" + QT_LOGGING_RULES: "qt.qml.connections=false" + QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" + } + + ShaderTool { + args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12" + files: [ "content/shaders/*" ] + } + + QmlFiles { + directory: "content" + } + + QmlFiles { + directory: "imports" + } + + QmlFiles { + directory: "asset_imports" + } + + JavaScriptFiles { + directory: "content" + } + + JavaScriptFiles { + directory: "imports" + } + + ImageFiles { + directory: "content" + } + + ImageFiles { + directory: "asset_imports" + } + + Files { + directory: "." + filters: "*.conf" + files: [ "qtquickcontrols2.conf" ] + } + + Files { + directory: "." + filters: "*.ttf;*.otf;*.ctf" + } + + Files { + directory: "asset_imports" + filters: "*.mesh" + } + + Files { + directory: "." + filters: "qmldir" + } + + Files { + directory: "." + filters: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" + } + + Files { + directory: "." + filters: "*.mp3;*.wav" + } + + Files { + directory: "." + filters: "*.mp4" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject new file mode 100644 index 00000000000..721dea3d28e --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject @@ -0,0 +1,112 @@ +import QmlProject + +Project { + QDS.mainFile: "content/App.qml" + QDS.mainUiFile: "content/Screen01.ui.qml" + + /* Include .qml, .js, and image files from current directory and subdirectories */ + QDS.QmlFiles { + directory: "content" + } + + QDS.QmlFiles { + directory: "imports" + } + + QDS.JavaScriptFiles { + directory: "content" + } + + QDS.JavaScriptFiles { + directory: "imports" + } + + QDS.ImageFiles { + directory: "content" + } + + QDS.ImageFiles { + directory: "asset_imports" + } + + QDS.Files { + filter: "*.conf" + files: ["qtquickcontrols2.conf"] + } + + QDS.Files { + filter: "qmldir" + directory: "." + } + + QDS.Files { + filter: "*.ttf;*.otf;*.ctf" + } + + QDS.Files { + filter: "*.wav;*.mp3" + } + + QDS.Files { + filter: "*.mp4" + } + + QDS.Files { + filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" + } + + QDS.Files { + filter: "*.mesh" + directory: "asset_imports" + } + + QDS.Files { + filter: "*.qml" + directory: "asset_imports" + } + + QDS.Environment { + QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" + QT_AUTO_SCREEN_SCALE_FACTOR: "1" + QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1" + QT_LOGGING_RULES: "qt.qml.connections=false" + QT_ENABLE_HIGHDPI_SCALING: "0" + /* Useful for debugging + QSG_VISUALIZE=batches + QSG_VISUALIZE=clip + QSG_VISUALIZE=changes + QSG_VISUALIZE=overdraw + */ + } + + QDS.qt6Project: true + + /* List of plugin directories passed to QML runtime */ + QDS.importPaths: [ "imports", "asset_imports" ] + + /* Required for deployment */ + QDS.targetDirectory: "/opt/UntitledProject13" + + QDS.qdsVersion: "4.0" + + QDS.quickVersion: "6.2" + + QDS.qtForMCUs: true + + /* If any modules the project imports require widgets (e.g. QtCharts), widgetApp must be true */ + QDS.widgetApp: true + + /* args: Specifies command line arguments for qsb tool to generate shaders. + files: Specifies target files for qsb tool. If path is included, it must be relative to this file. + Wildcard '*' can be used in the file name part of the path. + e.g. files: [ "content/shaders/*.vert", "*.frag" ] */ + QDS.ShaderTool { + args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12" + files: [ "content/shaders/*" ] + } + + QDS.multilanguageSupport: true + QDS.supportedLanguages: ["en"] + QDS.primaryLanguage: "en" + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson new file mode 100644 index 00000000000..293b8e96524 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson @@ -0,0 +1,178 @@ +{ + "deployment": { + "targetDirectory": "/opt/UntitledProject13" + }, + "environment": { + "QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT": "1", + "QT_AUTO_SCREEN_SCALE_FACTOR": "1", + "QT_ENABLE_HIGHDPI_SCALING": "0", + "QT_LOGGING_RULES": "qt.qml.connections=false", + "QT_QUICK_CONTROLS_CONF": "qtquickcontrols2.conf" + }, + "fileGroups": { + "config": { + "directories": [ + "." + ], + "files": [ + { + "name": "qtquickcontrols2.conf" + } + ], + "filters": [ + "*.conf" + ] + }, + "font": { + "directories": [ + "." + ], + "files": [ + ], + "filters": [ + "*.ttf", + "*.otf", + "*.ctf" + ] + }, + "image": { + "directories": [ + "content", + "asset_imports" + ], + "files": [ + ], + "filters": [ + "*.jpeg", + "*.jpg", + "*.png", + "*.svg", + "*.hdr", + ".ktx" + ] + }, + "javaScript": { + "directories": [ + "content", + "imports" + ], + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ] + }, + "meshes": { + "directories": [ + "asset_imports" + ], + "files": [ + ], + "filters": [ + "*.mesh" + ] + }, + "qml": { + "directories": [ + "content", + "imports", + "asset_imports" + ], + "files": [ + ], + "filters": [ + "*.qml" + ] + }, + "qmldir": { + "directories": [ + "." + ], + "files": [ + ], + "filters": [ + "qmldir" + ] + }, + "shader": { + "directories": [ + "." + ], + "files": [ + ], + "filters": [ + "*.glsl", + "*.glslv", + "*.glslf", + "*.vsh", + "*.fsh", + "*.vert", + "*.frag", + "*.trag" + ] + }, + "sound": { + "directories": [ + "." + ], + "files": [ + ], + "filters": [ + "*.mp3", + "*.wav" + ] + }, + "video": { + "directories": [ + "." + ], + "files": [ + ], + "filters": [ + "*.mp4" + ] + } + }, + "fileVersion": 1, + "importPaths": [ + "imports", + "asset_imports" + ], + "language": { + "multiLanguageSupport": true, + "primaryLanguage": "en", + "supportedLanguages": [ + "en" + ] + }, + "mcuConfig": { + "mcuEnabled": true + }, + "runConfig": { + "fileSelectors": [ + ], + "mainFile": "content/App.qml", + "mainUiFile": "content/Screen01.ui.qml", + "widgetApp": true + }, + "shaderTool": { + "args": [ + "-s", + "--glsl", + "\"100 es,120,150\"", + "--hlsl", + "50", + "--msl", + "12" + ], + "files": [ + "content/shaders/*" + ] + }, + "versions": { + "designStudio": "4.0", + "qt": "6", + "qtQuick": "6.2" + } +}