diff --git a/src/controls/ActionToolBar.qml b/src/controls/ActionToolBar.qml index 09184877..07a4407f 100644 --- a/src/controls/ActionToolBar.qml +++ b/src/controls/ActionToolBar.qml @@ -1,206 +1,203 @@ /* * 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 implicitHeight: actionsLayout.implicitHeight - implicitWidth: actionsLayout.implicitWidth - - Layout.minimumWidth: moreButton.implicitWidth - Layout.maximumWidth: placeholderLayout.implicitWidth + moreButton.width + 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: placeholderLayout.visibleActions 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 flat: root.flat && !modelData.icon.color.a display: 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: hiddenActions.length > 0 || placeholderLayout.hiddenActions.length > 0 showMenuArrow: false kirigamiAction: Kirigami.Action { icon.name: "overflow-menu" 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: placeholderLayout.visibleActions.indexOf(parentAction) == -1 && (parentAction.visible === undefined || parentAction.visible) } } menu.itemDelegate: ActionMenuItem { visible: placeholderLayout.visibleActions.indexOf(action) == -1 && (action.visible === undefined || action.visible) } } } RowLayout { id: placeholderLayout enabled: false opacity: 0 // Cannot use visible: false because then relayout doesn't happen correctly spacing: Kirigami.Units.smallSpacing property var visibleActions: [] property var hiddenActions: [] property real layoutWidth: root.width - moreButton.width - Kirigami.Units.smallSpacing Repeater { id: placeholderRepeater model: root.actions PrivateActionToolButton { flat: root.flat && !modelData.icon.color.a display: root.display visible: modelData.visible === undefined || modelData.visible kirigamiAction: modelData property bool actionVisible: x + width < placeholderLayout.layoutWidth } } Component.onCompleted: Qt.callLater(updateVisibleActions) onWidthChanged: Qt.callLater(updateVisibleActions) function updateVisibleActions() { var visible = [] var hidden = [] if (root.width >= placeholderLayout.width + moreButton.width + Kirigami.Units.smallSpacing) { visibleActions = Array.prototype.map.call(root.actions, function(item) { return item }) hiddenActions = [] return } for (var i = 0; i < root.actions.length; ++i) { var item = placeholderRepeater.itemAt(i) if (item.actionVisible) { visible.push(item.kirigamiAction) } else { hidden.push(item.kirigamiAction) } } visibleActions = visible hiddenActions = hidden } } onWidthChanged: Qt.callLater(placeholderLayout.updateVisibleActions) } diff --git a/src/controls/Page.qml b/src/controls/Page.qml index 5cc403bc..3d980012 100644 --- a/src/controls/Page.qml +++ b/src/controls/Page.qml @@ -1,420 +1,414 @@ /* * Copyright 2015 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.5 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.7 as Kirigami import "private" import QtQuick.Templates 2.1 as T2 /** * Page is a container for all the app pages: everything pushed to the * ApplicationWindow stackView should be a Page instance (or a subclass, * such as ScrollablePage) * @see ScrollablePage * @inherit QtQuick.Templates.Page */ T2.Page { id: root /** * leftPadding: int * default contents padding at left */ leftPadding: Kirigami.Units.gridUnit /** * topPadding: int * default contents padding at top */ topPadding: Kirigami.Units.gridUnit /** * rightPadding: int * default contents padding at right */ rightPadding: Kirigami.Units.gridUnit /** * bottomPadding: int * default contents padding at bottom */ bottomPadding: actionButtons.item ? actionButtons.height : Kirigami.Units.gridUnit /** * flickable: Flickable * if the central element of the page is a Flickable * (ListView and Gridview as well) you can set it there. * normally, you wouldn't need to do that, but just use the * ScrollablePage element instead * @see ScrollablePage * Use this if your flickable has some non standard properties, such as not covering the whole Page */ property Flickable flickable /** * actions.contextualActions: list * Defines the contextual actions for the page: * an easy way to assign actions in the right sliding panel * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * [...] * } * @endcode * * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.Page { * [...] * actions.contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * @endcode */ //TODO: remove property alias contextualActions: actionsGroup.contextualActions /** * actions.main: Action * An optional single action for the action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.main: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias mainAction: actionsGroup.main /** * actions.left: Action * An optional extra action at the left of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.left: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias leftAction: actionsGroup.left /** * actions.right: Action * An optional extra action at the right of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.right: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias rightAction: actionsGroup.right /** * Actions properties are grouped. * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions { * main: Kirigami.Action {...} * left: Kirigami.Action {...} * right: Kirigami.Action {...} * contextualActions: [ * Kirigami.Action {...}, * Kirigami.Action {...} * ] * } * } * @endcode */ readonly property alias actions: actionsGroup /** * contextualActionsAboutToShow: signal * Emitted when a visualization for the actions is about to be shown, * such as the toolbar menu or the contextDrawer * @since 2.7 */ signal contextualActionsAboutToShow /** * isCurrentPage: bool * * Specifies if it's the currently selected page in the window's pages row. * * @since 2.1 */ readonly property bool isCurrentPage: typeof applicationWindow === "undefined" || !globalToolBar.row ? true : (globalToolBar.row.layers.depth > 1 ? globalToolBar.row.layers.currentItem === root : globalToolBar.row.currentItem === root) /** * overlay: Item * an item which stays on top of every other item in the page, * if you want to make sure some elements are completely in a * layer on top of the whole content, parent items to this one. * It's a "local" version of ApplicationWindow's overlay * @since 2.5 */ readonly property alias overlay: overlayItem /** * titleDelegate: Component * The delegate which will be used to draw the page title. It can be customized to put any kind of Item in there. * @since 2.7 */ property Component titleDelegate: Kirigami.Heading { id: title level: 1 Layout.fillWidth: true - - Layout.preferredWidth: titleTextMetrics.width - Layout.minimumWidth: titleTextMetrics.width + Layout.maximumWidth: implicitWidth + 1 // The +1 is to make sure we do not trigger eliding at max width + Layout.minimumWidth: 0 opacity: root.isCurrentPage ? 1 : 0.4 maximumLineCount: 1 elide: Text.ElideRight text: root.title - TextMetrics { - id: titleTextMetrics - text: root.title - font: title.font - } } /** * emitted When the application requests a Back action * For instance a global "back" shortcut or the Android * Back button has been pressed. * The page can manage the back event by itself, * and if it set event.accepted = true, it will stop the main * application to manage the back event. */ signal backRequested(var event); // Look for sheets and cose them //FIXME: port Sheets to Popup? onBackRequested: { for(var i in root.resources) { var item = root.resources[i]; if (item.hasOwnProperty("close") && item.hasOwnProperty("sheetOpen") && item.sheetOpen) { item.close() event.accepted = true; return; } } } /** * globalToolBarItem: Item * The item used as global toolbar for the page * present only if we are in a PageRow as a page or as a layer, * and the style is either Titles or ToolBar * @since 2.5 */ readonly property Item globalToolBarItem: globalToolBar.item /** * The style for the automatically generated global toolbar: by default the Page toolbar is the one set globally in the PageRow in its globalToolBar.style property. * A single page can override the application toolbar style for itself. * It is discouraged to use this, except very specific exceptions, like a chat * application which can't have controls on the bottom except the text field. */ property int globalToolBarStyle: { if (globalToolBar.row && !globalToolBar.stack) { return globalToolBar.row.globalToolBar.actualStyle; } else if (globalToolBar.stack) { return Kirigami.Settings.isMobile ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.ToolBar; } else { return Kirigami.ApplicationHeaderStyle.None; } } //NOTE: contentItem will be created if not existing (and contentChildren of Page would become its children) This with anchors enforces the geometry we want, where globalToolBar is a super-header, on top of header contentItem: Item { anchors { top: root.header ? root.header.bottom : (globalToolBar.visible ? globalToolBar.bottom : parent.top) topMargin: root.topPadding + root.spacing bottom: root.footer ? root.footer.top : parent.bottom bottomMargin: root.bottomPadding + root.spacing } } background: Rectangle { color: Kirigami.Theme.backgroundColor } implicitHeight: (header ? header.implicitHeight : 0) + (footer ? footer.implicitHeight : 0) + contentItem.implicitHeight + topPadding + bottomPadding implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding //FIXME: on material the shadow would bleed over clip: root.header != null; onHeaderChanged: { if (header) { header.anchors.top = Qt.binding(function() {return globalToolBar.visible ? globalToolBar.bottom : root.top}); } } Component.onCompleted: { headerChanged(); parentChanged(root.parent); } onParentChanged: { if (!parent) { return; } globalToolBar.stack = null; globalToolBar.row = null; if (root.Kirigami.ColumnView.view) { globalToolBar.row = root.Kirigami.ColumnView.view.__pageRow; } if (root.T2.StackView.view) { globalToolBar.stack = root.T2.StackView.view; globalToolBar.row = root.T2.StackView.view ? root.T2.StackView.view.parent : null; } if (globalToolBar.row) { root.globalToolBarStyleChanged.connect(globalToolBar.syncSource); globalToolBar.syncSource(); } } //in data in order for them to not be considered for contentItem, contentChildren, contentData data: [ PageActionPropertyGroup { id: actionsGroup }, Item { id: overlayItem parent: root z: 9997 anchors.fill: parent }, //global top toolbar if we are in a PageRow (in the row or as a layer) Loader { id: globalToolBar z: 9999 height: item ? item.implicitHeight : 0 anchors { left: parent.left right: parent.right top: parent.top } property Kirigami.PageRow row property T2.StackView stack visible: active active: (row || stack) && (root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar || root.globalToolBarStyle == Kirigami.ApplicationHeaderStyle.Titles) function syncSource() { if (row && active) { setSource(Qt.resolvedUrl(root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar ? "private/globaltoolbar/ToolBarPageHeader.qml" : "private/globaltoolbar/TitlesPageHeader.qml"), //TODO: find container reliably, remove assumption {"pageRow": Qt.binding(function() {return row}), "page": root, "current": Qt.binding(function() {return stack || row.currentIndex === root.Kirigami.ColumnView.level})}); } } }, //bottom action buttons Loader { id: actionButtons z: 9999 parent: root anchors { left: parent.left right: parent.right bottom: parent.bottom } //It should be T2.Page, Qt 5.7 doesn't like it property Item page: root height: item ? item.implicitHeight : 0 active: typeof applicationWindow !== "undefined" && (!globalToolBar.row || root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar) && root.actions && (root.actions.main || root.actions.left || root.actions.right || root.actions.contextualActions.length) && //Legacy (typeof applicationWindow === "undefined" || (!applicationWindow().header || applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === -1) && (!applicationWindow().footer || applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") === -1)) source: Qt.resolvedUrl("./private/ActionButton.qml") } ] Layout.fillWidth: true } diff --git a/src/controls/private/globaltoolbar/ToolBarPageHeader.qml b/src/controls/private/globaltoolbar/ToolBarPageHeader.qml index b60c1fb1..f3952883 100644 --- a/src/controls/private/globaltoolbar/ToolBarPageHeader.qml +++ b/src/controls/private/globaltoolbar/ToolBarPageHeader.qml @@ -1,101 +1,101 @@ /* * 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.5 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.5 import "../" as Private AbstractPageHeader { id: root - implicitWidth: titleLoader.implcitWidth + toolBar.implicitWidth + Units.smallSpacing * 3 - + implicitWidth: layout.implcitWidth + Units.smallSpacing * 2 Layout.preferredHeight: Math.max(titleLoader.implicitHeight, toolBar.implicitHeight) + Units.smallSpacing * 2 MouseArea { anchors.fill: parent onClicked: page.forceActiveFocus() } - Loader { - id: titleLoader - - anchors { - verticalCenter: parent.verticalCenter - left: parent.left - leftMargin: Units.smallSpacing - rightMargin: Units.smallSpacing - } - - visible: pageRow.globalToolBar.toolbarActionAlignment == Qt.AlignRight && width > item.Layout.minimumWidth + RowLayout { + id: layout + anchors.fill: parent + anchors.leftMargin: Units.smallSpacing + anchors.rightMargin: Units.smallSpacing + spacing: Units.smallSpacing - sourceComponent: page ? page.titleDelegate : null - } + Loader { + id: titleLoader - ActionToolBar { - id: toolBar + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: item ? item.Layout.fillWidth : undefined + Layout.minimumWidth: item ? item.Layout.minimumWidth : undefined + Layout.preferredWidth: item ? item.Layout.preferredWidth : undefined + Layout.maximumWidth: item ? item.Layout.maximumWidth : undefined - anchors { - left: titleLoader.right - leftMargin: Units.smallSpacing - right: parent.right - rightMargin: Units.smallSpacing - verticalCenter: parent.verticalCenter + sourceComponent: page ? page.titleDelegate : null } - alignment: pageRow.globalToolBar.toolbarActionAlignment - display: buttonTextMetrics.toobig ? Controls.Button.IconOnly : Controls.Button.TextBesideIcon - - actions: { - var result = [] - - if (page) { - if (page.actions.main) { - result.push(page.actions.main) - } - if (page.actions.left) { - result.push(page.actions.left) - } - if (page.actions.right) { - result.push(page.actions.right) - } - if (page.actions.contextualActions.length > 0 && !buttonTextMetrics.toobig) { - result = result.concat(Array.prototype.map.call(page.actions.contextualActions, function(item) { return item })) + ActionToolBar { + id: toolBar + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + visible: width > 0 + alignment: pageRow.globalToolBar.toolbarActionAlignment + display: buttonTextMetrics.toobig ? Controls.Button.IconOnly : Controls.Button.TextBesideIcon + + actions: { + var result = [] + + if (page) { + if (page.actions.main) { + result.push(page.actions.main) + } + if (page.actions.left) { + result.push(page.actions.left) + } + if (page.actions.right) { + result.push(page.actions.right) + } + if (page.actions.contextualActions.length > 0 && !buttonTextMetrics.toobig) { + result = result.concat(Array.prototype.map.call(page.actions.contextualActions, function(item) { return item })) + } } + + return result } - return result + hiddenActions: page && buttonTextMetrics.toobig ? page.actions.contextualActions : [] } - - hiddenActions: page && buttonTextMetrics.toobig ? page.actions.contextualActions : [] } TextMetrics { id: buttonTextMetrics text: (page.actions.left ? page.actions.left.text : "") + (page.actions.main ? page.actions.main.text : "") + (page.actions.right ? page.actions.right.text : "") readonly property int collapsedButtonsWidth: toolBar.Layout.minimumWidth + (page.actions.left ? toolBar.Layout.minimumWidth + Units.gridUnit : 0) + (page.actions.main ? toolBar.Layout.minimumWidth + Units.gridUnit : 0) + (page.actions.right ? toolBar.Layout.minimumWidth + Units.gridUnit : 0) readonly property int requiredWidth: width + collapsedButtonsWidth readonly property bool toobig: root.width - root.leftPadding - root.rightPadding - titleLoader.implicitWidth - Units.gridUnit < buttonTextMetrics.requiredWidth } }