diff --git a/src/controls/ActionToolBar.qml b/src/controls/ActionToolBar.qml index 572312de..7144d69b 100644 --- a/src/controls/ActionToolBar.qml +++ b/src/controls/ActionToolBar.qml @@ -1,160 +1,168 @@ /* * Copyright 2018 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.4 as Controls import org.kde.kirigami 2.5 as Kirigami import "private" /** * This is a simple toolbar built out of a list of actions * each action is represented by a ToolButton, those that won't fit * the size will go in a menu under a button with the overflow ... icon * * @inherits Item * @since 2.5 */ Item { id: root /** * actions: list * if the card should provide clickable actions, put them in this property, * they will be put in the footer as a list of ToolButtons plus an optional * overflow menu, when not all of them will fit in the available Card width. */ property list actions /** * actions: hiddenActions * This list of actions is for those you always want in the menu, even if there * is enough space. * @since 2.6 */ property list hiddenActions /** * flat: bool * Wether we want our buttons to have a flat appearance. Default: true */ property bool flat: true /** * display: enum * This controls the label position regarding the icon, is the same value to control individual Button components, * permitted values are: * * Button.IconOnly * * Button.TextOnly * * Button.TextBesideIcon * * Button.TextUnderIcon */ property int display: Controls.Button.TextBesideIcon property int alignment: Qt.AlignLeft /** * position enum * This property holds the position of the toolbar. * if this ActionToolBar is the contentItem of a QQC2 Toolbar, the position is binded to the ToolBar's position * * permitted values are: * *ToolBar.Header: The toolbar is at the top, as a window or page header. * *ToolBar.Footer: The toolbar is at the bottom, as a window or page footer. */ property int position: parent && parent.hasOwnProperty("position") ? parent.position : Controls.ToolBar.Header + /** + * The maximum width of the contents of this ToolBar. If the toolbar's width is larger than this value, empty space will + * be added on the sides, according to the Alignment property. + * + * The value of this property is derived from the ToolBar's actions and their properties. + */ + readonly property alias maximumContentWidth: details.maximumWidth + implicitHeight: actionsLayout.implicitHeight implicitWidth: actionsLayout.implicitWidth Layout.minimumWidth: moreButton.visible ? moreButton.implicitWidth : 0 RowLayout { id: actionsLayout anchors.fill: parent spacing: 0 Item { Layout.fillWidth: root.alignment == Qt.AlignRight || root.alignment == Qt.AlignHCenter || root.alignment == Qt.AlignCenter; Layout.fillHeight: true } Repeater { model: root.actions delegate: PrivateActionToolButton { id: actionDelegate Layout.alignment: Qt.AlignVCenter Layout.minimumWidth: implicitWidth // Use rightMargin instead of spacing on the layout to prevent spacer items // from creating useless spacing Layout.rightMargin: Kirigami.Units.smallSpacing visible: details.visibleActions.indexOf(modelData) != -1 && (modelData.visible === undefined || modelData.visible) flat: root.flat && !modelData.icon.color.a display: details.iconOnlyActions.indexOf(modelData) != -1 ? Controls.Button.IconOnly : root.display kirigamiAction: modelData } } Item { Layout.fillWidth: root.alignment == Qt.AlignLeft || root.alignment == Qt.AlignHCenter || root.alignment == Qt.AlignCenter; Layout.fillHeight: true } PrivateActionToolButton { id: moreButton Layout.alignment: Qt.AlignRight visible: root.hiddenActions.length > 0 || details.hiddenActions.length > 0 kirigamiAction: Kirigami.Action { icon.name: "overflow-menu" displayHint: Kirigami.Action.DisplayHint.IconOnly | Kirigami.Action.DisplayHint.HideChildIndicator children: Array.prototype.map.call(root.actions, function (i) { return i }).concat(Array.prototype.map.call(hiddenActions, function (i) { return i })) } menu.submenuComponent: ActionsMenu { Binding { target: parentItem property: "visible" value: details.visibleActions.indexOf(parentAction) == -1 && (parentAction.visible === undefined || parentAction.visible) } } menu.itemDelegate: ActionMenuItem { visible: details.visibleActions.indexOf(action) == -1 && (action.visible === undefined || action.visible) } } } ActionToolBarLayoutDetails { id: details anchors.fill: parent actions: root.actions rightPadding: moreButton.width + Kirigami.Units.smallSpacing flat: root.flat display: root.display } } diff --git a/src/controls/private/ActionToolBarLayoutDetails.qml b/src/controls/private/ActionToolBarLayoutDetails.qml index 0924eb61..2b7637fc 100644 --- a/src/controls/private/ActionToolBarLayoutDetails.qml +++ b/src/controls/private/ActionToolBarLayoutDetails.qml @@ -1,148 +1,149 @@ /* * Copyright 2018 Marco Martin * Copyright 2019 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.4 as Controls import org.kde.kirigami 2.5 as Kirigami /** * This fairly complex thing determines the layout of ActionToolBar, that is, * which actions should be displayed in full width toolbutton form, which should * be displayed in an icon-only reduced size and which should be placed in the * overflow menu. * * It makes use of two fairly static layouts, one contains all actions in expanded * full-size form, the other in reduced icon-only form. The items in these layouts * determine if they should be visible based on their relative position and size * and some properties of the action. The update function then goes through these * items, adding the actions to the relevant lists, so they can be used by the * ActionToolBar to determine which action should be visible in what state. * * The reason for using two separate layouts from ActionToolBar's main layout is * so that we can use the actual geometry of the buttons to calculate the * visibility, completely disregarding any other layouting quirks. We can then * use that information so only the relevant things in the ActionToolBar are * visible. This allows the main ActionToolBar to use normal layout features for * things like the positioning of the visible actions. */ Item { id: details property var actions property var visibleActions: [] property var hiddenActions: [] property var iconOnlyActions: [] property bool flat: false property int display: Controls.Button.TextBesideIcon property real spacing: Kirigami.Units.smallSpacing property real leftPadding: 0 property real rightPadding: 0 property real iconOnlyWidth: 0 readonly property real iconLayoutWidth: width - rightPadding readonly property real fullLayoutWidth: iconLayoutWidth - iconOnlyWidth + readonly property real maximumWidth: fullSizePlaceholderLayout.width + leftPadding + rightPadding enabled: false opacity: 0 // Cannot use visible: false because then relayout doesn't happen correctly function update() { var visible = [] var hidden = [] var iconOnly = [] var iconsWidth = 0 if (details.width >= fullSizePlaceholderLayout.width + details.rightPadding) { visibleActions = Array.prototype.map.call(details.actions, function(i) { return i }) hiddenActions = [] iconOnlyActions = [] iconOnlyWidth = 0 return } for (var i = 0; i < root.actions.length; ++i) { var item = fullSizePlaceholderRepeater.itemAt(i) var iconOnlyItem = iconOnlyPlaceholderRepeater.itemAt(i) if (item.actionVisible) { visible.push(item.kirigamiAction) } else if (iconOnlyItem.actionVisible) { visible.push(item.kirigamiAction) iconOnly.push(item.kirigamiAction) iconsWidth += iconOnlyItem.width + details.spacing } else { hidden.push(item.kirigamiAction) } } visibleActions = visible hiddenActions = hidden iconOnlyActions = iconOnly iconOnlyWidth = iconsWidth } onWidthChanged: Qt.callLater(update) Component.onCompleted: Qt.callLater(update) RowLayout { id: fullSizePlaceholderLayout spacing: details.spacing // This binding is here to take care of things like visibility changes onWidthChanged: Qt.callLater(details.update) Repeater { id: fullSizePlaceholderRepeater model: details.actions PrivateActionToolButton { flat: details.flat && !modelData.icon.color.a display: details.display visible: (modelData.visible === undefined || modelData.visible) && (modelData.displayHint !== undefined && !modelData.displayHintSet(Kirigami.Action.DisplayHint.AlwaysHide)) kirigamiAction: modelData property bool actionVisible: visible && (x + width < details.fullLayoutWidth) } } } RowLayout { id: iconOnlyPlaceholderLayout spacing: details.spacing Repeater { id: iconOnlyPlaceholderRepeater model: details.actions PrivateActionToolButton { flat: details.flat && !modelData.icon.color.a display: Controls.Button.IconOnly visible: (modelData.visible === undefined || modelData.visible) && (modelData.displayHint !== undefined && modelData.displayHintSet(Kirigami.Action.DisplayHint.KeepVisible)) kirigamiAction: modelData property bool actionVisible: visible && (x + width < details.iconLayoutWidth) } } } } diff --git a/src/controls/templates/InlineMessage.qml b/src/controls/templates/InlineMessage.qml index eeffd50b..bd048447 100644 --- a/src/controls/templates/InlineMessage.qml +++ b/src/controls/templates/InlineMessage.qml @@ -1,306 +1,306 @@ /* * Copyright 2018 Eike Hein * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.7 import QtQuick.Templates 2.0 as T2 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.0 import org.kde.kirigami 2.5 as Kirigami import "private" /** * An inline message item with support for informational, positive, * warning and error types, and with support for associated actions. * * InlineMessage can be used to give information to the user or * interact with the user, without requiring the use of a dialog. * * The InlineMessage item is hidden by default. It also manages its * height (and implicitHeight) during an animated reveal when shown. * You should avoid setting height on an InlineMessage unless it is * already visible. * * Optionally an icon can be set, defaulting to an icon appropriate * to the message type otherwise. * * Optionally a close button can be shown. * * Actions are added from left to right. If more actions are set than * can fit, an overflow menu is provided. * * Example: * @code * InlineMessage { * type: Kirigami.MessageType.Error * * text: "My error message" * * actions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * } * @endcode * * @since 5.45 */ T2.Control { id: root visible: false /** * Emitted when a link is hovered in the message text. * @param The hovered link. */ signal linkHovered(string link) /** * Emitted when a link is clicked or tapped in the message text. * @param The clicked or tapped link. */ signal linkActivated(string link) /** * type: int * The message type. One of Information, Positive, Warning or Error. * * The default is Kirigami.MessageType.Information. */ property int type: Kirigami.MessageType.Information /** * A grouped property describing an optional icon. * * source: The source of the icon, a freedesktop-compatible icon name is recommended. * * color: An optional tint color for the icon. * * If no custom icon is set, an icon appropriate to the message type * is shown. */ property IconPropertiesGroup icon: IconPropertiesGroup {} /** * text: string * The message text. */ property string text /** * showCloseButton: bool * When enabled, a close button is shown. * The default is false. */ property bool showCloseButton: false /** * actions: list * The list of actions to show. Actions are added from left to * right. If more actions are set than can fit, an overflow menu is * provided. */ property list actions /** * animating: bool * True while the message item is animating. */ readonly property bool animating: root.hasOwnProperty("_animating") && _animating implicitHeight: visible ? contentLayout.implicitHeight + (2 * (background.border.width + Kirigami.Units.smallSpacing)) : 0 property bool _animating: false leftPadding: background.border.width + Kirigami.Units.smallSpacing topPadding: background.border.width + Kirigami.Units.smallSpacing rightPadding: background.border.width + Kirigami.Units.smallSpacing bottomPadding: background.border.width + Kirigami.Units.smallSpacing Behavior on implicitHeight { enabled: !root.visible SequentialAnimation { PropertyAction { targets: root; property: "_animating"; value: true } NumberAnimation { duration: Kirigami.Units.longDuration } } } onVisibleChanged: { if (!visible) { contentLayout.opacity = 0.0; } } opacity: visible ? 1.0 : 0.0 Behavior on opacity { enabled: !root.visible NumberAnimation { duration: Kirigami.Units.shortDuration } } onOpacityChanged: { if (opacity == 0.0) { contentLayout.opacity = 0.0; } else if (opacity == 1.0) { contentLayout.opacity = 1.0; } } onImplicitHeightChanged: { height = implicitHeight; } - contentItem: GridLayout { + + contentItem: GridLayout { id: contentLayout // Used to defer opacity animation until we know if InlineMessage was // initialized visible. property bool complete: false Behavior on opacity { enabled: root.visible && contentLayout.complete SequentialAnimation { NumberAnimation { duration: Kirigami.Units.shortDuration * 2 } PropertyAction { targets: root; property: "_animating"; value: false } } } rowSpacing: Kirigami.Units.largeSpacing columnSpacing: Kirigami.Units.smallSpacing Kirigami.Icon { id: icon width: Kirigami.Units.iconSizes.smallMedium height: width Layout.alignment: text.lineCount > 1 ? Qt.AlignTop : Qt.AlignVCenter Layout.minimumWidth: width Layout.minimumHeight: height source: { if (root.icon.source) { return root.icon.source; } if (root.type == Kirigami.MessageType.Positive) { return "dialog-positive"; } else if (root.type == Kirigami.MessageType.Warning) { return "dialog-warning"; } else if (root.type == Kirigami.MessageType.Error) { return "dialog-error"; } return "dialog-information"; } color: root.icon.color } MouseArea { implicitHeight: text.implicitHeight Layout.fillWidth: true Layout.alignment: text.lineCount > 1 ? Qt.AlignTop : Qt.AlignVCenter Layout.row: 0 Layout.column: 1 cursorShape: text.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor Controls.Label { id: text width: parent.width color: Kirigami.Theme.textColor wrapMode: Text.WordWrap elide: Text.ElideRight text: root.text onLinkHovered: root.linkHovered(link) onLinkActivated: root.linkActivated(link) } //this must be child of an item which doesn't try to resize it TextMetrics { id: messageTextMetrics font: text.font text: text.text } } Kirigami.ActionToolBar { id: actionsLayout flat: false actions: root.actions visible: root.actions.length + alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight + Layout.maximumWidth: maximumContentWidth + Layout.fillWidth: true Layout.row: { - if (messageTextMetrics.width + Kirigami.Units.smallSpacing > - (contentLayout.width - icon.width - actionsLayout.width - - closeButton.width - (3 * contentLayout.columnSpacing))) { + var width = contentLayout.width - icon.width - actionsLayout.maximumContentWidth + - (closeButton.visible ? closeButton.width : 0) + - 3 * contentLayout.columnSpacing + + if (messageTextMetrics.width + Kirigami.Units.smallSpacing > width) { return 1; } return 0; } Layout.column: Layout.row ? 0 : 2 Layout.columnSpan: Layout.row ? (closeButton.visible ? 3 : 2) : 1 } Controls.ToolButton { id: closeButton visible: root.showCloseButton Layout.alignment: text.lineCount > 1 || actionsLayout.Layout.row ? Qt.AlignTop : Qt.AlignVCenter Layout.row: 0 Layout.column: actionsLayout.Layout.row ? 2 : 3 - //TODO: use toolbuttons icons when we can depend from Qt 5.10 - Kirigami.Icon { - anchors.centerIn: parent - source: "dialog-close" - width: Kirigami.Units.iconSizes.smallMedium - height: width - } + icon.name: "dialog-close" onClicked: root.visible = false } Component.onCompleted: complete = true } }