Improve scrollview

wilder-5.24
Carl Schwan 4 years ago committed by Nate Graham
parent 0de62b319f
commit 37bf7e5d73
  1. 612
      applets/notifications/package/contents/ui/FullRepresentation.qml

@ -20,12 +20,14 @@ import org.kde.notificationmanager 1.0 as NotificationManager
import "global"
PlasmaComponents3.Page {
PlasmaExtras.Representation {
// TODO these should be configurable in the future
readonly property int dndMorningHour: 6
readonly property int dndEveningHour: 20
Layout.fillHeight: plasmoid.formFactor === PlasmaCore.Types.Vertical
collapseMarginsHint: true
// HACK forward focus to the list
onActiveFocusChanged: {
if (activeFocus) {
@ -222,373 +224,363 @@ PlasmaComponents3.Page {
}
}
ColumnLayout{
// FIXME fix popup size when resizing panel smaller (so it collapses)
//Layout.preferredWidth: PlasmaCore.Units.gridUnit * 18
//Layout.preferredHeight: PlasmaCore.Units.gridUnit * 24
//Layout.minimumWidth: PlasmaCore.Units.gridUnit * 10
//Layout.minimumHeight: PlasmaCore.Units.gridUnit * 15
PlasmaComponents3.ScrollView {
id: scrollView
anchors.fill: parent
spacing: PlasmaCore.Units.smallSpacing
// actual notifications
PlasmaComponents3.ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: PlasmaCore.Units.gridUnit * 18
Layout.preferredHeight: PlasmaCore.Units.gridUnit * 24
Layout.leftMargin: PlasmaCore.Units.smallSpacing
background: null
// HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff
ListView {
id: list
model: historyModel
currentIndex: -1
Keys.onDeletePressed: {
var idx = historyModel.index(currentIndex, 0);
if (historyModel.data(idx, NotificationManager.Notifications.ClosableRole)) {
historyModel.close(idx);
// TODO would be nice to stay inside the current group when deleting an item
}
background: null
leftPadding: PlasmaCore.Units.smallSpacing * 2
rightPadding: PlasmaCore.Units.smallSpacing * 2
// HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff
contentItem: ListView {
id: list
model: historyModel
currentIndex: -1
topMargin: PlasmaCore.Units.smallSpacing * 2
bottomMargin: PlasmaCore.Units.smallSpacing * 2
spacing: PlasmaCore.Units.smallSpacing
Keys.onDeletePressed: {
var idx = historyModel.index(currentIndex, 0);
if (historyModel.data(idx, NotificationManager.Notifications.ClosableRole)) {
historyModel.close(idx);
// TODO would be nice to stay inside the current group when deleting an item
}
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: {
// Trigger default action, if any
var idx = historyModel.index(currentIndex, 0);
if (historyModel.data(idx, NotificationManager.Notifications.HasDefaultActionRole)) {
historyModel.invokeDefaultAction(idx);
return;
}
// Trigger thumbnail URL if there's one
var urls = historyModel.data(idx, NotificationManager.Notifications.UrlsRole);
if (urls && urls.length === 1) {
Qt.openUrlExternally(urls[0]);
return;
}
// TODO for finished jobs trigger "Open" or "Open Containing Folder" action
}
Keys.onLeftPressed: setGroupExpanded(currentIndex, LayoutMirroring.enabled)
Keys.onRightPressed: setGroupExpanded(currentIndex, !LayoutMirroring.enabled)
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Home:
currentIndex = 0;
break;
case Qt.Key_End:
currentIndex = count - 1;
break;
}
}
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: {
// Trigger default action, if any
var idx = historyModel.index(currentIndex, 0);
if (historyModel.data(idx, NotificationManager.Notifications.HasDefaultActionRole)) {
historyModel.invokeDefaultAction(idx);
return;
}
function isRowExpanded(row) {
var idx = historyModel.index(row, 0);
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
// Trigger thumbnail URL if there's one
var urls = historyModel.data(idx, NotificationManager.Notifications.UrlsRole);
if (urls && urls.length === 1) {
Qt.openUrlExternally(urls[0]);
return;
}
function setGroupExpanded(row, expanded) {
var rowIdx = historyModel.index(row, 0);
var persistentRowIdx = historyModel.makePersistentModelIndex(rowIdx);
var persistentGroupIdx = historyModel.makePersistentModelIndex(historyModel.groupIndex(rowIdx));
// TODO for finished jobs trigger "Open" or "Open Containing Folder" action
}
Keys.onLeftPressed: setGroupExpanded(currentIndex, LayoutMirroring.enabled)
Keys.onRightPressed: setGroupExpanded(currentIndex, !LayoutMirroring.enabled)
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Home:
currentIndex = 0;
break;
case Qt.Key_End:
currentIndex = count - 1;
break;
}
}
historyModel.setData(rowIdx, expanded, NotificationManager.Notifications.IsGroupExpandedRole);
function isRowExpanded(row) {
var idx = historyModel.index(row, 0);
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
}
// If the current item went away when the group collapsed, scroll to the group heading
if (!persistentRowIdx || !persistentRowIdx.valid) {
if (persistentGroupIdx && persistentGroupIdx.valid) {
list.positionViewAtIndex(persistentGroupIdx.row, ListView.Contain);
// When closed via keyboard, also set a sane current index
if (list.currentIndex > -1) {
list.currentIndex = persistentGroupIdx.row;
}
}
}
}
function setGroupExpanded(row, expanded) {
var rowIdx = historyModel.index(row, 0);
var persistentRowIdx = historyModel.makePersistentModelIndex(rowIdx);
var persistentGroupIdx = historyModel.makePersistentModelIndex(historyModel.groupIndex(rowIdx));
highlightMoveDuration: 0
highlightResizeDuration: 0
// Not using PlasmaComponents.Highlight as this is only for indicating keyboard focus
highlight: PlasmaCore.FrameSvgItem {
imagePath: "widgets/listitem"
prefix: "pressed"
}
historyModel.setData(rowIdx, expanded, NotificationManager.Notifications.IsGroupExpandedRole);
add: Transition {
SequentialAnimation {
PropertyAction { property: "opacity"; value: 0 }
PauseAnimation { duration: PlasmaCore.Units.longDuration }
ParallelAnimation {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: PlasmaCore.Units.longDuration }
NumberAnimation { property: "height"; from: 0; duration: PlasmaCore.Units.longDuration }
// If the current item went away when the group collapsed, scroll to the group heading
if (!persistentRowIdx || !persistentRowIdx.valid) {
if (persistentGroupIdx && persistentGroupIdx.valid) {
list.positionViewAtIndex(persistentGroupIdx.row, ListView.Contain);
// When closed via keyboard, also set a sane current index
if (list.currentIndex > -1) {
list.currentIndex = persistentGroupIdx.row;
}
}
}
addDisplaced: Transition {
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
}
remove: Transition {
id: removeTransition
highlightMoveDuration: 0
highlightResizeDuration: 0
// Not using PlasmaComponents.Highlight as this is only for indicating keyboard focus
highlight: PlasmaCore.FrameSvgItem {
imagePath: "widgets/listitem"
prefix: "pressed"
}
add: Transition {
SequentialAnimation {
PropertyAction { property: "opacity"; value: 0 }
PauseAnimation { duration: PlasmaCore.Units.longDuration }
ParallelAnimation {
NumberAnimation { property: "opacity"; to: 0; duration: PlasmaCore.Units.longDuration }
NumberAnimation {
id: removeXAnimation
property: "x"
to: list.width
duration: PlasmaCore.Units.longDuration
}
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: PlasmaCore.Units.longDuration }
NumberAnimation { property: "height"; from: 0; duration: PlasmaCore.Units.longDuration }
}
}
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: PlasmaCore.Units.longDuration }
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
addDisplaced: Transition {
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
remove: Transition {
id: removeTransition
ParallelAnimation {
NumberAnimation { property: "opacity"; to: 0; duration: PlasmaCore.Units.longDuration }
NumberAnimation {
id: removeXAnimation
property: "x"
to: list.width - (scrollView.PlasmaComponents3.ScrollBar.vertical.visible ? PlasmaCore.Units.smallSpacing * 4 : 0)
duration: PlasmaCore.Units.longDuration
}
}
// This is so the delegates can detect the change in "isInGroup" and show a separator
section {
property: "isInGroup"
criteria: ViewSection.FullString
}
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: PlasmaCore.Units.longDuration }
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
}
delegate: DraggableDelegate {
id: delegate
width: list.width
contentItem: delegateLoader
// This is so the delegates can detect the change in "isInGroup" and show a separator
section {
property: "isInGroup"
criteria: ViewSection.FullString
}
draggable: !model.isGroup && model.type != NotificationManager.Notifications.JobType
delegate: DraggableDelegate {
id: delegate
width: ListView.view.width - (scrollView.PlasmaComponents3.ScrollBar.vertical.visible ? PlasmaCore.Units.smallSpacing * 4 : 0)
contentItem: delegateLoader
onDismissRequested: {
// Setting the animation target explicitly before removing the notification:
// Using ViewTransition.item.x to get the x position in the animation
// causes random crash in attached property access (cf. Bug 414066)
if (x < 0) {
removeXAnimation.to = -list.width;
}
draggable: !model.isGroup && model.type != NotificationManager.Notifications.JobType
historyModel.close(historyModel.index(index, 0));
onDismissRequested: {
// Setting the animation target explicitly before removing the notification:
// Using ViewTransition.item.x to get the x position in the animation
// causes random crash in attached property access (cf. Bug 414066)
if (x < 0) {
removeXAnimation.to = -delegate.width;
}
Loader {
id: delegateLoader
width: list.width
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
historyModel.close(historyModel.index(index, 0));
}
Component {
id: groupDelegate
NotificationHeader {
applicationName: model.applicationName
applicationIconSource: model.applicationIconName
originName: model.originName || ""
Loader {
id: delegateLoader
width: delegate.width
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
// don't show timestamp for group
Component {
id: groupDelegate
NotificationHeader {
applicationName: model.applicationName
applicationIconSource: model.applicationIconName
originName: model.originName || ""
configurable: model.configurable
closable: model.closable
closeButtonTooltip: i18n("Close Group")
// don't show timestamp for group
onCloseClicked: historyModel.close(historyModel.index(index, 0))
onConfigureClicked: historyModel.configure(historyModel.index(index, 0))
}
configurable: model.configurable
closable: model.closable
closeButtonTooltip: i18n("Close Group")
onCloseClicked: historyModel.close(historyModel.index(index, 0))
onConfigureClicked: historyModel.configure(historyModel.index(index, 0))
}
}
Component {
id: notificationDelegate
ColumnLayout {
spacing: PlasmaCore.Units.smallSpacing
RowLayout {
Item {
id: groupLineContainer
Layout.fillHeight: true
Layout.topMargin: PlasmaCore.Units.smallSpacing
width: PlasmaCore.Units.iconSizes.small
visible: model.isInGroup
PlasmaCore.SvgItem {
elementId: "vertical-line"
svg: lineSvg
anchors.horizontalCenter: parent.horizontalCenter
// Want a thicker than default bar
width: Math.min(groupLineContainer.width, naturalSize.width * PlasmaCore.Units.devicePixelRatio * 3)
height: parent.height
}
Component {
id: notificationDelegate
ColumnLayout {
spacing: PlasmaCore.Units.smallSpacing
RowLayout {
Item {
id: groupLineContainer
Layout.fillHeight: true
Layout.topMargin: PlasmaCore.Units.smallSpacing
width: PlasmaCore.Units.iconSizes.small
visible: model.isInGroup
PlasmaCore.SvgItem {
elementId: "vertical-line"
svg: lineSvg
anchors.horizontalCenter: parent.horizontalCenter
// Want a thicker than default bar
width: Math.min(groupLineContainer.width, naturalSize.width * PlasmaCore.Units.devicePixelRatio * 3)
height: parent.height
}
}
NotificationItem {
Layout.fillWidth: true
NotificationItem {
Layout.fillWidth: true
notificationType: model.type
inGroup: model.isInGroup
inHistory: true
listViewParent: list
applicationName: model.applicationName
applicationIconSource: model.applicationIconName
originName: model.originName || ""
time: model.updated || model.created
// configure button on every single notifications is bit overwhelming
configurable: !inGroup && model.configurable
dismissable: model.type === NotificationManager.Notifications.JobType
&& model.jobState !== NotificationManager.Notifications.JobStateStopped
&& model.dismissed
// TODO would be nice to be able to undismiss jobs even when they autohide
&& notificationSettings.permanentJobPopups
dismissed: model.dismissed || false
closable: model.closable
summary: model.summary
body: model.body || ""
icon: model.image || model.iconName
urls: model.urls || []
jobState: model.jobState || 0
percentage: model.percentage || 0
jobError: model.jobError || 0
suspendable: !!model.suspendable
killable: !!model.killable
jobDetails: model.jobDetails || null
configureActionLabel: model.configureActionLabel || ""
// In the popup the default action is triggered by clicking on the popup
// however in the list this is undesirable, so instead show a clickable button
// in case you have a non-expired notification in history (do not disturb mode)
// unless it has the same label as an action
readonly property bool addDefaultAction: (model.hasDefaultAction
&& model.defaultActionLabel
&& (model.actionLabels || []).indexOf(model.defaultActionLabel) === -1) ? true : false
actionNames: {
var actions = (model.actionNames || []);
if (addDefaultAction) {
actions.unshift("default"); // prepend
}
return actions;
notificationType: model.type
inGroup: model.isInGroup
inHistory: true
listViewParent: list
applicationName: model.applicationName
applicationIconSource: model.applicationIconName
originName: model.originName || ""
time: model.updated || model.created
// configure button on every single notifications is bit overwhelming
configurable: !inGroup && model.configurable
dismissable: model.type === NotificationManager.Notifications.JobType
&& model.jobState !== NotificationManager.Notifications.JobStateStopped
&& model.dismissed
// TODO would be nice to be able to undismiss jobs even when they autohide
&& notificationSettings.permanentJobPopups
dismissed: model.dismissed || false
closable: model.closable
summary: model.summary
body: model.body || ""
icon: model.image || model.iconName
urls: model.urls || []
jobState: model.jobState || 0
percentage: model.percentage || 0
jobError: model.jobError || 0
suspendable: !!model.suspendable
killable: !!model.killable
jobDetails: model.jobDetails || null
configureActionLabel: model.configureActionLabel || ""
// In the popup the default action is triggered by clicking on the popup
// however in the list this is undesirable, so instead show a clickable button
// in case you have a non-expired notification in history (do not disturb mode)
// unless it has the same label as an action
readonly property bool addDefaultAction: (model.hasDefaultAction
&& model.defaultActionLabel
&& (model.actionLabels || []).indexOf(model.defaultActionLabel) === -1) ? true : false
actionNames: {
var actions = (model.actionNames || []);
if (addDefaultAction) {
actions.unshift("default"); // prepend
}
actionLabels: {
var labels = (model.actionLabels || []);
if (addDefaultAction) {
labels.unshift(model.defaultActionLabel);
}
return labels;
return actions;
}
actionLabels: {
var labels = (model.actionLabels || []);
if (addDefaultAction) {
labels.unshift(model.defaultActionLabel);
}
return labels;
}
onCloseClicked: close()
onDismissClicked: {
model.dismissed = false;
root.closePlasmoid();
}
onConfigureClicked: historyModel.configure(historyModel.index(index, 0))
onCloseClicked: close()
onActionInvoked: {
if (actionName === "default") {
historyModel.invokeDefaultAction(historyModel.index(index, 0));
} else {
historyModel.invokeAction(historyModel.index(index, 0), actionName);
}
onDismissClicked: {
model.dismissed = false;
root.closePlasmoid();
}
onConfigureClicked: historyModel.configure(historyModel.index(index, 0))
expire();
onActionInvoked: {
if (actionName === "default") {
historyModel.invokeDefaultAction(historyModel.index(index, 0));
} else {
historyModel.invokeAction(historyModel.index(index, 0), actionName);
}
onOpenUrl: {
Qt.openUrlExternally(url);
expire();
}
onOpenUrl: {
Qt.openUrlExternally(url);
expire();
}
onFileActionInvoked: {
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
close();
} else {
expire();
}
onFileActionInvoked: {
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
close();
} else {
expire();
}
}
}
onSuspendJobClicked: historyModel.suspendJob(historyModel.index(index, 0))
onResumeJobClicked: historyModel.resumeJob(historyModel.index(index, 0))
onKillJobClicked: historyModel.killJob(historyModel.index(index, 0))
onSuspendJobClicked: historyModel.suspendJob(historyModel.index(index, 0))
onResumeJobClicked: historyModel.resumeJob(historyModel.index(index, 0))
onKillJobClicked: historyModel.killJob(historyModel.index(index, 0))
function expire() {
if (model.resident) {
model.expired = true;
} else {
historyModel.expire(historyModel.index(index, 0));
}
function expire() {
if (model.resident) {
model.expired = true;
} else {
historyModel.expire(historyModel.index(index, 0));
}
}
function close() {
historyModel.close(historyModel.index(index, 0));
}
function close() {
historyModel.close(historyModel.index(index, 0));
}
}
}
PlasmaComponents3.ToolButton {
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
text: model.isGroupExpanded ? i18n("Show Fewer")
: i18nc("Expand to show n more notifications",
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
&& delegate.ListView.nextSection !== delegate.ListView.section
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded)
}
PlasmaComponents3.ToolButton {
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
text: model.isGroupExpanded ? i18n("Show Fewer")
: i18nc("Expand to show n more notifications",
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
&& delegate.ListView.nextSection !== delegate.ListView.section
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded)
}
PlasmaCore.SvgItem {
Layout.fillWidth: true
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
elementId: "horizontal-line"
svg: lineSvg
PlasmaCore.SvgItem {
Layout.fillWidth: true
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
elementId: "horizontal-line"
svg: lineSvg
// property is only atached to the delegate itself (the Loader in our case)
visible: (!model.isInGroup || delegate.ListView.nextSection !== delegate.ListView.section)
&& delegate.ListView.nextSection !== "" // don't show after last item
}
// property is only atached to the delegate itself (the Loader in our case)
visible: (!model.isInGroup || delegate.ListView.nextSection !== delegate.ListView.section)
&& delegate.ListView.nextSection !== "" // don't show after last item
}
}
}
}
}
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
text: i18n("No unread notifications")
visible: list.count === 0 && NotificationManager.Server.valid
}
text: i18n("No unread notifications")
visible: list.count === 0 && NotificationManager.Server.valid
}
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
text: i18n("Notification service not available")
visible: list.count === 0 && !NotificationManager.Server.valid
// TODO: port to using the subtitle property once it exists
PlasmaComponents3.Label {
// Checking valid to avoid creating ServerInfo object if everything is alright
readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner
: null
// PlasmaExtras.PlaceholderMessage is internally a ColumnLayout,
// so we can use Layout.whatever properties here
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: currentOwner ? i18nc("Vendor and product name",
"Notifications are currently provided by '%1 %2'",
currentOwner.vendor,
currentOwner.name)
: ""
visible: currentOwner && currentOwner.vendor && currentOwner.name
}
text: i18n("Notification service not available")
visible: list.count === 0 && !NotificationManager.Server.valid
// TODO: port to using the subtitle property once it exists
PlasmaComponents3.Label {
// Checking valid to avoid creating ServerInfo object if everything is alright
readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner
: null
// PlasmaExtras.PlaceholderMessage is internally a ColumnLayout,
// so we can use Layout.whatever properties here
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: currentOwner ? i18nc("Vendor and product name",
"Notifications are currently provided by '%1 %2'",
currentOwner.vendor,
currentOwner.name)
: ""
visible: currentOwner && currentOwner.vendor && currentOwner.name
}
}
}

Loading…
Cancel
Save