import QtQml 2.1 import QtQml.Models 2.1 import QtQuick 2.9 import QtQuick.Window 2.3 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 // Custom qml modules are in /theme (and included by resources.qrc) import Style 1.0 import com.nextcloud.desktopclient 1.0 Window { id: trayWindow width: Style.trayWindowWidth height: Style.trayWindowHeight color: "transparent" flags: Qt.Dialog | Qt.FramelessWindowHint // Close tray window when focus is lost (e.g. click somewhere else on the screen) onActiveChanged: { if(!active) { trayWindow.hide(); Systray.setClosed(); } } onVisibleChanged: { currentAccountAvatar.source = "" currentAccountAvatar.source = "image://avatars/currentUser" currentAccountStateIndicator.source = "" currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" // HACK: reload account Instantiator immediately by restting it - could be done better I guess // see also id:accountMenu below userLineInstantiator.active = false; userLineInstantiator.active = true; } Connections { target: UserModel onRefreshCurrentUserGui: { currentAccountAvatar.source = "" currentAccountAvatar.source = "image://avatars/currentUser" currentAccountStateIndicator.source = "" currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" } onNewUserSelected: { accountMenu.close(); } } Connections { target: Systray onShowWindow: { accountMenu.close(); appsMenu.close(); Systray.positionWindow(trayWindow); trayWindow.show(); trayWindow.raise(); trayWindow.requestActivate(); Systray.setOpened(); UserModel.fetchCurrentActivityModel(); } onHideWindow: { trayWindow.hide(); Systray.setClosed(); } } Rectangle { id: trayWindowBackground anchors.fill: parent radius: Style.trayWindowRadius border.width: Style.trayWindowBorderWidth border.color: Style.menuBorder Rectangle { id: trayWindowHeaderBackground anchors.left: trayWindowBackground.left anchors.top: trayWindowBackground.top height: Style.trayWindowHeaderHeight width: Style.trayWindowWidth radius: (Style.trayWindowRadius > 0) ? (Style.trayWindowRadius - 1) : 0 color: Style.ncBlue // The overlay rectangle below eliminates the rounded corners from the bottom of the header // as Qt only allows setting the radius for all corners right now, not specific ones Rectangle { id: trayWindowHeaderButtomHalfBackground anchors.left: trayWindowHeaderBackground.left anchors.bottom: trayWindowHeaderBackground.bottom height: Style.trayWindowHeaderHeight / 2 width: Style.trayWindowWidth color: Style.ncBlue } RowLayout { id: trayWindowHeaderLayout spacing: 0 anchors.fill: parent Button { id: currentAccountButton Layout.preferredWidth: Style.currentAccountButtonWidth Layout.preferredHeight: Style.trayWindowHeaderHeight display: AbstractButton.IconOnly flat: true MouseArea { id: accountBtnMouseArea anchors.fill: parent hoverEnabled: Style.hoverEffectsEnabled // HACK: Imitate Qt hover effect brightness (which is not accessible as property) // so that indicator background also flicks when hovered onContainsMouseChanged: { currentAccountStateIndicatorBackground.color = (containsMouse ? Style.ncBlueHover : Style.ncBlue) } // We call open() instead of popup() because we want to position it // exactly below the dropdown button, not the mouse onClicked: { syncPauseButton.text = Systray.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all") accountMenu.open() } Menu { id: accountMenu // x coordinate grows towards the right // y coordinate grows towards the bottom x: (currentAccountButton.x + 2) y: (currentAccountButton.y + Style.trayWindowHeaderHeight + 2) width: (Style.currentAccountButtonWidth - 2) closePolicy: "CloseOnPressOutside" background: Rectangle { border.color: Style.menuBorder radius: Style.currentAccountButtonRadius } onClosed: { // HACK: reload account Instantiator immediately by restting it - could be done better I guess // see also onVisibleChanged above userLineInstantiator.active = false; userLineInstantiator.active = true; } Instantiator { id: userLineInstantiator model: UserModel delegate: UserLine {} onObjectAdded: accountMenu.insertItem(index, object) onObjectRemoved: accountMenu.removeItem(object) } MenuItem { id: addAccountButton height: Style.addAccountButtonHeight hoverEnabled: true background: Item { height: parent.height width: parent.menu.width Rectangle { anchors.fill: parent anchors.margins: 1 color: parent.parent.hovered ? Style.lightHover : "transparent" } } RowLayout { anchors.fill: parent spacing: 0 Image { Layout.leftMargin: 12 verticalAlignment: Qt.AlignCenter source: "qrc:///client/theme/black/add.svg" sourceSize.width: Style.headerButtonIconSize sourceSize.height: Style.headerButtonIconSize } Label { Layout.leftMargin: 14 text: qsTr("Add account") color: "black" font.pixelSize: Style.topLinePixelSize } // Filler on the right Item { Layout.fillWidth: true Layout.fillHeight: true } } onClicked: UserModel.addAccount() } MenuSeparator { contentItem: Rectangle { implicitHeight: 1 color: Style.menuBorder } } MenuItem { id: syncPauseButton font.pixelSize: Style.topLinePixelSize hoverEnabled: true onClicked: Systray.pauseResumeSync() background: Item { height: parent.height width: parent.menu.width Rectangle { anchors.fill: parent anchors.margins: 1 color: parent.parent.hovered ? Style.lightHover : "transparent" } } } MenuItem { text: qsTr("Settings") font.pixelSize: Style.topLinePixelSize hoverEnabled: true onClicked: Systray.openSettings() background: Item { height: parent.height width: parent.menu.width Rectangle { anchors.fill: parent anchors.margins: 1 color: parent.parent.hovered ? Style.lightHover : "transparent" } } } MenuItem { text: qsTr("Exit"); font.pixelSize: Style.topLinePixelSize hoverEnabled: true onClicked: Systray.shutdown() background: Item { height: parent.height width: parent.menu.width Rectangle { anchors.fill: parent anchors.margins: 1 color: parent.parent.hovered ? Style.lightHover : "transparent" } } } } } background: Item { id: leftHoverContainer height: Style.trayWindowHeaderHeight width: Style.currentAccountButtonWidth Rectangle { width: Style.currentAccountButtonWidth / 2 height: Style.trayWindowHeaderHeight / 2 color: "transparent" clip: true Rectangle { width: Style.currentAccountButtonWidth height: Style.trayWindowHeaderHeight radius: Style.trayWindowRadius color: "white" opacity: 0.2 visible: accountBtnMouseArea.containsMouse } } Rectangle { width: Style.currentAccountButtonWidth / 2 height: Style.trayWindowHeaderHeight / 2 anchors.bottom: leftHoverContainer.bottom color: "white" opacity: 0.2 visible: accountBtnMouseArea.containsMouse } Rectangle { width: Style.currentAccountButtonWidth / 2 height: Style.trayWindowHeaderHeight / 2 anchors.right: leftHoverContainer.right color: "white" opacity: 0.2 visible: accountBtnMouseArea.containsMouse } Rectangle { width: Style.currentAccountButtonWidth / 2 height: Style.trayWindowHeaderHeight / 2 anchors.right: leftHoverContainer.right anchors.bottom: leftHoverContainer.bottom color: "white" opacity: 0.2 visible: accountBtnMouseArea.containsMouse } } RowLayout { id: accountControlRowLayout height: Style.trayWindowHeaderHeight width: Style.currentAccountButtonWidth spacing: 0 Image { id: currentAccountAvatar Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter cache: false source: "image://avatars/currentUser" Layout.preferredHeight: Style.accountAvatarSize Layout.preferredWidth: Style.accountAvatarSize Rectangle { id: currentAccountStateIndicatorBackground width: Style.accountAvatarStateIndicatorSize + 2 height: width anchors.bottom: currentAccountAvatar.bottom anchors.right: currentAccountAvatar.right color: Style.ncBlue radius: width*0.5 } Image { id: currentAccountStateIndicator source: UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" cache: false x: currentAccountStateIndicatorBackground.x + 1 y: currentAccountStateIndicatorBackground.y + 1 sourceSize.width: Style.accountAvatarStateIndicatorSize sourceSize.height: Style.accountAvatarStateIndicatorSize } } Column { id: accountLabels spacing: 4 Layout.alignment: Qt.AlignLeft Layout.leftMargin: 6 Label { id: currentAccountUser width: Style.currentAccountLabelWidth text: UserModel.currentUser.name elide: Text.ElideRight color: "white" font.pixelSize: Style.topLinePixelSize font.bold: true } Label { id: currentAccountServer width: Style.currentAccountLabelWidth text: UserModel.currentUser.server elide: Text.ElideRight color: "white" font.pixelSize: Style.subLinePixelSize } } Image { Layout.alignment: Qt.AlignRight verticalAlignment: Qt.AlignCenter Layout.margins: Style.accountDropDownCaretMargin source: "qrc:///client/theme/white/caret-down.svg" sourceSize.width: Style.accountDropDownCaretSize sourceSize.height: Style.accountDropDownCaretSize } } } // Filler between account dropdown and header app buttons Item { id: trayWindowHeaderSpacer Layout.fillWidth: true } HeaderButton { id: openLocalFolderButton visible: UserModel.currentUser.hasLocalFolder icon.source: "qrc:///client/theme/white/folder.svg" onClicked: UserModel.openCurrentAccountLocalFolder() } HeaderButton { id: trayWindowTalkButton visible: UserModel.currentUser.serverHasTalk icon.source: "qrc:///client/theme/white/talk-app.svg" onClicked: UserModel.openCurrentAccountTalk() } HeaderButton { id: trayWindowAppsButton icon.source: "qrc:///client/theme/white/more-apps.svg" onClicked: { /* // The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10) // so we handle this with UserModel.openCurrentAccountServer() // // See UserModel::openCurrentAccountServer() to disable this workaround // in the future for Qt >= 5.10 if(appsMenu.count() > 0) { appsMenu.popup(); } else { UserModel.openCurrentAccountServer(); } */ appsMenu.open(); UserModel.openCurrentAccountServer(); } Menu { id: appsMenu y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) width: Math.min(contentItem.childrenRect.width + 4, Style.trayWindowWidth / 2) closePolicy: "CloseOnPressOutside" background: Rectangle { border.color: Style.menuBorder radius: 2 } Instantiator { id: appsMenuInstantiator model: UserAppsModel onObjectAdded: appsMenu.insertItem(index, object) onObjectRemoved: appsMenu.removeItem(object) delegate: MenuItem { id: appEntry text: appName font.pixelSize: Style.topLinePixelSize icon.source: appIconUrl width: contentItem.implicitWidth + leftPadding + rightPadding onTriggered: UserAppsModel.openAppUrl(appUrl) hoverEnabled: true background: Item { width: appsMenu.width height: parent.height Rectangle { anchors.fill: parent anchors.margins: 1 color: appEntry.hovered ? Style.lightHover : "transparent" } } } } } } } } // Rectangle trayWindowHeaderBackground ListView { id: activityListView anchors.top: trayWindowHeaderBackground.bottom anchors.horizontalCenter: trayWindowBackground.horizontalCenter width: Style.trayWindowWidth - Style.trayWindowBorderWidth height: Style.trayWindowHeight - Style.trayWindowHeaderHeight clip: true ScrollBar.vertical: ScrollBar { id: listViewScrollbar } model: activityModel delegate: RowLayout { id: activityItem width: parent.width height: Style.trayWindowHeaderHeight spacing: 0 MouseArea { enabled: (path !== "" || link !== "") anchors.left: activityItem.left anchors.right: ((shareButton.visible) ? shareButton.left : activityItem.right) height: parent.height anchors.margins: 2 hoverEnabled: true onClicked: { if (path !== "") { Qt.openUrlExternally(path) } else { Qt.openUrlExternally(link) } } ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: path !== "" ? qsTr("Open sync item locally") : qsTr("Open URL") Rectangle { anchors.fill: parent color: (parent.containsMouse ? Style.lightHover : "transparent") } } Image { id: activityIcon anchors.left: activityItem.left anchors.leftMargin: 8 anchors.rightMargin: 8 Layout.preferredWidth: shareButton.icon.width Layout.preferredHeight: shareButton.icon.height verticalAlignment: Qt.AlignCenter cache: true source: icon sourceSize.height: 64 sourceSize.width: 64 } Column { id: activityTextColumn anchors.left: activityIcon.right anchors.leftMargin: 8 spacing: 4 Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle text: (type === "Activity" || type === "Notification") ? subject : message width: Style.activityLabelBaseWidth + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: Style.topLinePixelSize color: activityTextTitleColor } Text { id: activityTextInfo text: (type === "Sync") ? displayPath : (type === "File") ? subject : (type === "Notification") ? message : "" height: (text === "") ? 0 : activityTextTitle.height width: Style.activityLabelBaseWidth + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: Style.subLinePixelSize } Text { id: activityTextDateTime text: dateTime height: (text === "") ? 0 : activityTextTitle.height width: Style.activityLabelBaseWidth + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: Style.subLinePixelSize color: "#808080" } } Button { id: shareButton anchors.right: activityItem.right Layout.preferredWidth: (path === "") ? 0 : parent.height Layout.preferredHeight: parent.height Layout.alignment: Qt.AlignRight flat: true hoverEnabled: true visible: (path === "") ? false : true display: AbstractButton.IconOnly icon.source: "qrc:///client/theme/share.svg" icon.color: "transparent" background: Rectangle { color: parent.hovered ? Style.lightHover : "transparent" } ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: qsTr("Open share dialog") onClicked: Systray.openShareDialog(displayPath,absolutePath) } } /*add: Transition { NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } } remove: Transition { NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } } removeDisplaced: Transition { SequentialAnimation { PauseAnimation { duration: 100} NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } } } displaced: Transition { NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } }*/ } } // Rectangle trayWindowBackground }