/*************************************************************/ /* */ /* Copyright (C) Microsoft Corporation. All rights reserved. */ /* */ /*************************************************************/ import Colors 1.0 import Fonts 1.0 import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Window 2.2 import QtQuick.Controls.Styles 1.4 import QtQml 2.2 import com.microsoft.OneDrive 1.0 import ColorThemeManager 1.0 import "fabricMdl2.js" as FabricMDL import "HelperFunctions.js" as HelperFunctions // Z order: // 100 - Mac border, Always on top of everything // 3 - Header, Footer - Always on top of other content // 2 - Error view controls - Toggled errors list window shows on top of history list // 1 - History list, ACMs, old error message, error view entry point, sync status, pause, signing in // Note on Accessibility and focus: // We want to set the focus: property to true // on the element that we want the Narrator/Voiceover tool to read // out first. // This is because of implicit accessibility behavior in Qt: when // all the nodes in the QML tree have been created, // the one with focus: true receives the initial keyboard // focus. Along with keyboard focus, Qt raises a FocusChanged accessibility // event which makes the Narrator/Voiceover tool read out load the Accessible.name // of the node in focus. // CONCLUSION: we want to give focus to the ActivityCenterHeader node. Rectangle { id: root objectName: "activityCenterRoot" width: 360 height: 640 color: Colors.activity_center.list.background property bool isOnlineMode: (headerModel.visualState >= ActivityCenterHeaderModel.Online) property bool isRTL: headerModel.isRTL property bool isPaused: (headerModel.visualState === ActivityCenterHeaderModel.Paused) property bool isNoNetwork: (headerModel.visualState === ActivityCenterHeaderModel.NoNetwork) property bool needsAttention: (headerModel.isError || headerModel.isWarning) property bool showErrorBackground: (headerModel.isError || headerModel.isActionableWarning) property bool isSyncingBlocked: headerModel.isSyncingBlocked property color focusBorderColor: (Qt.platform.os === "osx") ? Colors.common.mac_border_focus : Colors.common.border_focus property int focusBorderWidth: (Qt.platform.os === "osx") ? 1 : 2 // New vs old error view properties property bool hasErrorBanner: errorsModel.hasErrors property bool isErrorViewCurrentlyVisible: (errorsModel.isErrorViewOpened && errorsModel.hasErrors && !isPaused) LayoutMirroring.enabled: isRTL LayoutMirroring.childrenInherit: true Accessible.ignored: true Accessible.role: Accessible.Pane Accessible.focused: false Connections { target: headerModel // Refocus to pause rectangle or header so Narrator/Voiceover can announce the sync state. // As narrator tend to only announce the first state when the control gets focus, // we only refocus for below two steady states for pause/resume. function onVisualStateChanged() { if (headerModel.visualState === ActivityCenterHeaderModel.Paused) { // Pause rect has more details than header when entering paused state // and contains the button to remove pauseStatusRect.forceActiveFocus(); } else if (headerModel.visualState === ActivityCenterHeaderModel.Online) { theHeader.setFocusOnSubState(); } } } Window.onVisibilityChanged: { if (Window.visibility === Window.Hidden) { // When the AC window hides, clear the fact that // the user was previously using keyboard navigation theList.isUsingKeyboard = false; } } // draw a border around the window for Mac Rectangle { id: macBorder color: "transparent" border.width: 1 border.color: Colors.activity_center.common.mac_border anchors.fill: parent z: 100 // Mac Border should always be on top of everything visible: Qt.platform.os === "osx" } Component { id: listItemDelegate FocusScope { width: parent.width height: 68 id: item function chooseBGColor(isPressed, isHovering) { var color = Colors.activity_center.list.background if (isHovering) { color = Colors.activity_center.list.background_hovering; } if (isPressed) { color = Colors.activity_center.list.background_press; } return color; } function setOpacity(isDeleted) { if(isDeleted) { return 0.3; } else { return 1; } } function isSelfOrMenuActive() { return hoverRectangle.activeFocus || (itemMenu.activeFocus && (itemMenu.parentIndex == index)); } function containsHoverFocus() { return itemMouseArea.containsMouse || itemMenuButton.hovered || folderLink.hovered || itemCancelButton.hovered || folderLinkMouseArea.containsMouse; } function containsPressFocus() { return itemMouseArea.containsPress || itemMenuButton.pressed || folderLink.pressed || itemCancelButton.pressed; } // This helper function differentiates between one-off messages and the usual upload file history items. // The usual items have a submenu button and a hyperlinked folder, while these other messages do not. function isActionableHistoryListItem() { return !isDeletedItem && !isAutoRenameItem && !isSuccessfulResetItem && !isFailedResetItem; } // This will set the focus to the cell on every navigation. If we dont then the list item // will default to the last focus location which is jarring. // Do not do this when screen reader is on, because it interferes with keyboard navigation onFocusChanged: if (focus && !listModel.isScreenReaderOn) hoverRectangle.forceActiveFocus(); Rectangle { id: hoverRectangle width: parent.width height: parent.height color: chooseBGColor(containsPressFocus(), containsHoverFocus()) // Only show border when using keyboard border.color: theList.isUsingKeyboard && !containsPressFocus() && isSelfOrMenuActive() ? root.focusBorderColor : "transparent" border.width: root.focusBorderWidth focus: true function makeItemCurrent() { theList.currentIndex = index; theList.positionViewAtIndex(index, ListView.Visible); theList.forceActiveFocus(); } function activateItem() { makeItemCurrent(); if (isActionableHistoryListItem()) { theList.itemActivated(index); } } function openItemFolder() { makeItemCurrent(); if (isActionableHistoryListItem()) { theList.itemFolderClicked(index); } } function cancelProgressItem() { makeItemCurrent(); if (canBeCanceled) { theList.itemCancelProgressClicked(index); } } MouseArea { id: itemMouseArea anchors.fill: parent hoverEnabled: true onDoubleClicked: hoverRectangle.activateItem(); onClicked: hoverRectangle.makeItemCurrent(); } Accessible.role: Accessible.LayeredPane Accessible.name: { var name = itemNameText.text if (isHistoryItem) { name += ";" + itemDescriptionPrefixText.text if (isActionableHistoryListItem()) name += folderLink.text + itemDescriptionSuffixText.text } name += ";" + elapsedTime.text if (!isHistoryItem) { name += inProgressStatus.text } name += ";" + itemIndexAccessibleText return name } Accessible.focusable: true Accessible.focused: item.ListView.isCurrentItem Accessible.ignored: !visible Accessible.selectable: true Accessible.selected: item.ListView.isCurrentItem Accessible.onPressAction: { hoverRectangle.onAccessiblePressAction(); } Keys.onEnterPressed: hoverRectangle.activateItem() Keys.onReturnPressed: hoverRectangle.activateItem() Keys.onSpacePressed: hoverRectangle.activateItem() KeyNavigation.left: itemMenuButton.visible ? itemMenuButton : (itemCancelButton.visible ? itemCancelButton : folderLink) KeyNavigation.right: folderLink Rectangle { color: "transparent" width: parent.width anchors.left: parent.left anchors.leftMargin: 12 anchors.right: parent.right anchors.rightMargin: 12 anchors.top: parent.top anchors.topMargin: 8 anchors.bottom: parent.bottom anchors.bottomMargin: 8 Image { visible: !isAutoRenameItem && !isSuccessfulResetItem && !isFailedResetItem id: thumbnail anchors.left: parent.left width: 48; height: 48 anchors.verticalCenter: parent.verticalCenter source: isAddedFolderItem ? ("file:///" + imageLocation + ((Qt.platform.os === "osx") ? "addedFolderIcon_mac.svg" : "addedFolderIcon_win.svg")) : ("image://fileIcons/" + itemName) opacity: setOpacity(isDeletedItem) } Text { visible: isAutoRenameItem || isSuccessfulResetItem id: successCheckmark anchors.left: parent.left anchors.leftMargin: 8 width: 36; height: 36 anchors.verticalCenter: parent.verticalCenter text: FabricMDL.Icons.Completed font.family: fabricMDL2.name font.pixelSize: 36 color: Colors.common.checkmark } Text { visible: isFailedResetItem id: errorBadge anchors.left: parent.left anchors.leftMargin: 8 width: 36; height: 36 anchors.verticalCenter: parent.verticalCenter text: FabricMDL.Icons.ErrorBadge font.family: fabricMDL2.name font.pixelSize: 36 color: Colors.common.error_badge } Rectangle { id: itemRect visible: true anchors.left: thumbnail.right anchors.leftMargin: 12 anchors.right: parent.right height: parent.height anchors.verticalCenter: parent.verticalCenter color: "transparent" Rectangle { id: itemNameRect anchors.top: parent.top anchors.left: parent.left anchors.right: itemMenuButton.left anchors.rightMargin: 12 color: "transparent" height: itemNameText.height Text { id: itemNameText text: itemName font.pixelSize: 14 font.family: Fonts.standard color: itemMouseArea.containsMouse ? Colors.activity_center.common.text_hover : Colors.activity_center.common.text width: parent.width elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left horizontalAlignment: Text.AlignLeft } } Rectangle { id: itemStatusRect anchors.left: parent.left anchors.right: itemMenuButton.left anchors.rightMargin: 12 color: "transparent" height: 20 anchors.verticalCenter: parent.verticalCenter Row { id: itemDescription anchors.fill: parent visible: isHistoryItem anchors.topMargin: 4 anchors.bottomMargin: 4 Text { id: itemDescriptionPrefixText width: { var spaceAvailable = itemStatusRect.width - itemDescriptionSuffixText.width; // add 1 to the calculated width because of truncation on Mac var stringWidth = headerModel.getPixelWidthOfString(Fonts.standard, 12, itemActionPrefix) + 1; return Math.min(spaceAvailable, stringWidth); } elide: Text.ElideRight text: itemActionPrefix font.pixelSize: 12 font.family: Fonts.standard color: itemMouseArea.containsMouse ? Colors.activity_center.common.text_secondary_hover : Colors.activity_center.common.text_secondary horizontalAlignment: Text.AlignLeft // Read entire subtext out at once for better context, otherwise keyboard nav to // the folder button feels jarring and the elapsedTime feels out of place Accessible.name: (text + ((folderLink.visible) ? folderLink.text : "" ) + ((itemActionSuffix.visible) ? itemActionSuffix.text : "") + " " + ((elapsedTime.visible) ? elapsedTime.text : "")) } Button { id: folderLink width: { // width should be the minimum of space available or the actual width of button text var spaceAvailable = itemStatusRect.width - itemDescriptionPrefixText.width - itemDescriptionSuffixText.width; // add 1 to the calculated width because of truncation on Mac var buttonWidth = headerModel.getPixelWidthOfString(Fonts.standard, 12, itemParentFolder) + 1; return Math.min(buttonWidth, spaceAvailable); } height: contentItem.paintedHeight leftPadding: 0 rightPadding: 0 bottomPadding: 0 topPadding: 0 activeFocusOnTab: false visible: (isActionableHistoryListItem() && !isQuantumPlaceholder) text: itemParentFolder contentItem: Text { id: folderLinkText text: folderLink.text font.family: Fonts.standard font.pixelSize: 12 font.underline: folderLink.visualFocus color: folderLinkMouseArea.containsPress ? Colors.common.hyperlink_pressed : (folderLinkMouseArea.containsMouse ? Colors.common.hyperlink_hovering : Colors.common.hyperlink) horizontalAlignment: Text.AlignLeft elide: Text.ElideRight // Work around for a Qt bug where a second underline appears at (2x,2y) co-ordinates clip: true } background: Rectangle { implicitWidth: folderLinkText.width implicitHeight: folderLinkText.height color: "transparent" border.color: "transparent" } Accessible.ignored: !visible Accessible.onPressAction: { hoverRectangle.openItemFolder(); } KeyNavigation.left: hoverRectangle KeyNavigation.right: itemMenuButton.visible ? itemMenuButton : itemCancelButton ? itemCancelButton : folderLink MouseArea { id: folderLinkMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { hoverRectangle.openItemFolder(); } } } Text { id: itemDescriptionSuffixText text: itemActionSuffix visible: (isActionableHistoryListItem()) font.pixelSize: 12 font.family: Fonts.standard color: itemMouseArea.containsMouse ? Colors.activity_center.common.text_secondary_hover : Colors.activity_center.common.text_secondary wrapMode: Text.WordWrap horizontalAlignment: Text.AlignLeft } } Rectangle { id: progressGauge anchors.right: parent.right anchors.left: parent.left color: Colors.activity_center.list.progress_background visible: !isHistoryItem height: 2 anchors.verticalCenter: parent.verticalCenter Rectangle { visible: !isIndeterminateBarOn // show indeterminate progress instead width: itemProgressPercentage * parent.width / 100; height: parent.height color: Colors.activity_center.list.progress anchors.left: parent.left anchors.top: parent.top } // Indeterminate progress bar used for reviewing diff sync items FabricProgressBar { anchors.fill: parent animatorObjectWidth: progressGauge.width visible: isIndeterminateBarOn isRTL: headerModel.isRTL } } } Rectangle { id: secondaryTextRect anchors.top: itemStatusRect.bottom anchors.left: parent.left anchors.right: itemMenuButton.left anchors.rightMargin: 12 color: "transparent" height: childrenRect.height Row { id: secondaryText anchors.fill: parent Text { id: elapsedTime text: isHistoryItem ? timestamp : itemProgressString font.pixelSize: isHistoryItem ? 10 : 12 font.family: Fonts.standard color: itemMouseArea.containsMouse ? Colors.activity_center.common.text_secondary_hover : Colors.activity_center.common.text_secondary horizontalAlignment: Text.AlignLeft elide: Text.ElideRight width: { // width should be the minimum of space available or the actual width of elapsedTime text var spaceAvailable = parent.width - inProgressStatus.width; // add 1 to the calculated width because of truncation on Mac var desiredWidth = headerModel.getPixelWidthOfString(Fonts.standard, font.pixelSize, elapsedTime.text) + 1; return Math.min(desiredWidth, spaceAvailable); } } Text { id: inProgressStatus text: (isHistoryItem || isWaitingForDiffSync) ? "" : itemProgress font.pixelSize: 12 font.family: Fonts.standard color: itemMouseArea.containsMouse ? Colors.activity_center.common.text_secondary_hover : Colors.activity_center.common.text_secondary horizontalAlignment: Text.AlignLeft visible: (text.length > 0) Accessible.name: text } } } Button { id: itemCancelButton visible: canBeCanceled && (itemCancelButton.activeFocus || hoverRectangle.activeFocus || itemCancelButton.hovered || itemMouseArea.containsMouse) contentItem: Image { source: "file:///" + imageLocation + "cancelIcon.svg" width: 36 height: 36 sourceSize.width: 36 sourceSize.height: 36 anchors.fill: parent } activeFocusOnTab: true anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right width: 36 height: 36 // This text is only used for accessibility. QT Button pulls the accessibility string from the button text. // We are overwritting the contentItem so the text isnt shown on the screen. text: listModel.cancelButtonAccessibleName background: Rectangle { anchors.fill: parent color : itemCancelButton.pressed ? Colors.activity_center.context_menu.button_pressed : (itemCancelButton.hovered ? Colors.activity_center.context_menu.button_hover : "transparent") border.color: itemCancelButton.activeFocus ? Colors.activity_center.context_menu.button_hover : "transparent" border.width: 1 } onClicked: hoverRectangle.cancelProgressItem(); KeyNavigation.right: hoverRectangle KeyNavigation.left: hoverRectangle Keys.onReturnPressed: hoverRectangle.cancelProgressItem(); Keys.onEnterPressed: hoverRectangle.cancelProgressItem(); Keys.onSpacePressed: hoverRectangle.cancelProgressItem(); Accessible.name: listModel.cancelButtonAccessibleName Accessible.ignored: !visible Accessible.onPressAction: hoverRectangle.cancelProgressItem(); } CustomMenuButton { id: itemMenuButton visible: itemMenuButton.shouldShowMenuButton() bordercolor: root.focusBorderColor triggerToolTip: !itemMenu.visible isUsingKeyboard: theList.isUsingKeyboard KeyNavigation.right: hoverRectangle KeyNavigation.left: folderLink callback: function() { hoverRectangle.makeItemCurrent(); itemMenu.parent = itemMenuButton; // Assign -1 to parentIndex first to force itemMenu retrieve new data on every click // when determining whether to show certain menu items itemMenu.parentIndex = -1; itemMenu.parentIndex = index; itemMenu.parentItemWidth = itemMenuButton.width; var offset = item.y - theList.contentY; var totalHeight = item.height + itemMenu.height; var extraVerticalSpacing = 2; var listItemVerticalMargins = 8; if (totalHeight + offset < theList.height) { var bottomOfMenuButton = itemMenuButton.y + itemMenuButton.height - listItemVerticalMargins; itemMenu.y = bottomOfMenuButton + extraVerticalSpacing; } else { var topOfMenuButton = itemMenuButton.y - itemMenu.height - listItemVerticalMargins; itemMenu.y = topOfMenuButton - extraVerticalSpacing; } itemMenu.visible = !itemMenu.visible; } // This function should return true whenever the menu button should be shown. The button should only be // shown on non-delete, non-AutoRename history items when the the item either has active focus on any of its children // or hover focus on any of its children. // ActiveFocus is used to decide whether show menu button when using keyboard function shouldShowMenuButton() { var shouldShowIfUsingKeyboard = (theList.isUsingKeyboard && (itemMenuButton.activeFocus || hoverRectangle.activeFocus || folderLink.activeFocus || (itemMenu.activeFocus && (itemMenu.parentIndex == index)))); if (isHistoryItem && isActionableHistoryListItem() && (shouldShowIfUsingKeyboard || containsHoverFocus())) { return true; } return false; } } Connections { target: theList function onContentYChanged() { // This event is fired when the list view scrolls itemMenu.close(); } } // This will close the menu when user action causes AC to hide Window.onVisibilityChanged: { if (Window.visibility == Window.Hidden) { itemMenu.close(); } } } } } } } CustomMenu { id: itemMenu property int parentItemWidth: 0 property int parentIndex: 0 // menu width is sum widths of longest string, left margin, and right margin menuWidth: Math.min(Math.max(viewOnlineMenu.menuItemWidth, shareMenu.menuItemWidth, openMenu.menuItemWidth, versionHistoryMenu.menuItemWidth) + (2 * openMenu.menuItemMargin), theList.width) menuHeight: openMenu.menuItemHeight + shareMenu.menuItemHeight + viewOnlineMenu.menuItemHeight + versionHistoryMenu.menuItemHeight // Explanation: the Menu control inherits from Popup, whose behavior is to automatically adjust // position in a "popup" way: it makes sure it never overflows outside the window. The x value // we specify gets applied on top of the default positioning that Popup takes for itself. // E.g. if Popup decides its x value relative to the itemMenuButton is x:-152 (152 pixels to the left), // and we want it even 10 pixels to the left, we can adjust that position by explicitly setting x: -10 here. // The resulting final x will be -152 + (-10), -162, which is 162 pixels left of the left edge of the menu's parent. // For RTL, align menu to left of button. For LTR, align to right of button. x: headerModel.isRTL ? 0 : parentItemWidth - itemMenu.width onClosed: { theList.currentItem.forceActiveFocus(); listModel.isMenuOpen = false; } onOpened: { if (parentItemWidth == 0) { console.warn("Item Menu parentItemWidth set to 0, needs to be the size of the item that the menu belongs to"); } listModel.isMenuOpen = true; itemMenu.forceActiveFocus(); itemMenu.itemAt(0).forceActiveFocus(); } CustomMenuItem { id: openMenu textcontrol.text: listModel.menuItemOpen image.source: "file:///" + imageLocation + "openFolder.svg" imageSize: 22 callback: function() { theList.itemActivated(itemMenu.parentIndex); itemMenu.close(); } } CustomMenuItem { id: shareMenu enabled: !isPaused && !listModel.isVaultItem(itemMenu.parentIndex) textcontrol.text: listModel.menuItemShare image.source: "file:///" + imageLocation + "share.svg" imageSize: 22 callback: function() { theList.itemShareClicked(itemMenu.parentIndex); itemMenu.close(); } } CustomMenuItem { id: viewOnlineMenu enabled: !isPaused && !listModel.isVaultItem(itemMenu.parentIndex) textcontrol.text: listModel.menuItemViewOnline image.source: "file:///" + imageLocation + "globe.svg" imageSize: 22 callback: function() { theList.itemViewOnlineClicked(itemMenu.parentIndex); itemMenu.close(); } } CustomMenuItem { id: versionHistoryMenu visible: listModel.isVersionHistoryVisible enabled: visible && !isPaused && listModel.shouldEnableVersionHistory(itemMenu.parentIndex) menuItemHeight: visible ? openMenu.height : 0 textcontrol.text: listModel.menuItemVersionHistory image.source: "file:///" + imageLocation + "stack.svg" imageSize: 22 callback: function() { theList.itemVersionHistoryClicked(itemMenu.parentIndex); itemMenu.close(); } } } FontLoader { id: fabricMDL2; source: Qt.platform.os === "osx" ? "FabExMDL2.ttf" : "file:///" + qmlEngineBasePath + "FabExMDL2.ttf" } property string visualStateEnum: root.needsAttention ? ActivityCenterHeaderModel.Error : (headerModel.isSyncing ? ActivityCenterHeaderModel.Syncing : headerModel.visualState) Rectangle { // This component is purely tracking visual state for BVTs. // By default, it is zero height/width and should not show up on the UI // It is only accessible on DEBUG builds for BVTs - on Production, this component will be hidden from accessibility tree id: hiddenVisualStateComponent objectName: "hiddenVisualStateComponent" height: 0 width: 0 anchors.top: parent.top Accessible.role: Accessible.StaticText Accessible.name: "VisualState:" + visualStateEnum Accessible.ignored: !headerModel.showHiddenNodesInAccTree z: 0 } ActivityCenterHeader { id: theHeader objectName: "theHeader" // Per the Accessibility note at the top // this item receives the initial default focus. focus: true width: parent.width anchors.top: parent.top z: 3 // Header and footer should be at top of z-order errorViewCurrentlyVisible: isErrorViewCurrentlyVisible Window.onVisibilityChanged: { if (Window.visibility !== Window.Hidden) { theHeader.setFocusOnSubState(); } } } ActivityCenterMessage { id: acmRect objectName: "acmRect" visible: (messageModel.isVisible && !messageModel.isMegaMode && !hasErrorBanner && !isPaused) anchors.left: parent.left anchors.right: parent.right anchors.top: theHeader.bottom anchors.leftMargin: 12 anchors.rightMargin: 12 z: 1 signal primaryMessageAction() signal secondaryMessageAction() signal dismissMessage() primaryActionCallback: primaryMessageAction secondaryActionCallback: secondaryMessageAction dismissButton.callback: dismissMessage messageColors: Colors.getAcmThemeColors(messageModel.theme) } ActivityCenterMessageMega { id: acMegaRect objectName: "acMegaRect" visible: (messageModel.isVisible && messageModel.isMegaMode && !hasErrorBanner && !isPaused) anchors.left: parent.left anchors.right: parent.right anchors.top: theHeader.bottom anchors.leftMargin: 12 anchors.rightMargin: 12 z: 1 signal primaryMessageAction() signal secondaryMessageAction() signal dismissMessage() primaryActionCallback: primaryMessageAction secondaryActionCallback: secondaryMessageAction dismissButton.callback: dismissMessage messageColors: Colors.getAcmThemeColors(messageModel.theme) } function calcBannerHeight(childrenHeight) { if (childrenHeight < 68) { return 68; } else { return childrenHeight; } } // Errors V2 entry point into Errors List ErrorsViewEntryPoint { id: viewErrorsEntryPoint visible: (!isErrorViewCurrentlyVisible && (hasErrorBanner && !isPaused)) z: 1 height: childrenRect.height anchors.top: messageModel.isMegaMode ? acMegaRect.bottom : acmRect.bottom anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 12 anchors.rightMargin: 12 radius: 3 function switchToErrorsView(isKeyboard) { errorsModel.SetErrorViewOpened(true); // Record whether the user is using keyboard or mouse // so we can show the proper focus indicators // Keyboard is more visible but not pretty for regular mouse use errorsPage.usingKeyboard = isKeyboard; if (isKeyboard) { theHeader.setFocusOnErrorView(); } else { theHeader.setFocusOnSubState(); } } } Rectangle { id: pauseStatusRect objectName: "pauseStatusRect" visible: isPaused height: visible ? calcBannerHeight(pausedTextGroup.height + 2 * pausedStatusInner.margin) : 0 anchors.left: parent.left anchors.right: parent.right anchors.top: viewErrorsEntryPoint.bottom activeFocusOnTab: true z: 1 color: (theList.count == 0) ? "transparent" : Colors.activity_center.error.background_warning border.color: activeFocus ? Colors.activity_center.common.focused_border : "transparent" border.width: 1 Accessible.role: Accessible.Button Accessible.name: pausedStatusPrimary.text + " . " + pausedStatusSecondary.text Accessible.ignored: !(pauseStatusRect.visible || headerModel.showHiddenNodesInAccTree) Accessible.focusable: true Accessible.onPressAction: { pauseStatusRect.pauseStatusRectClicked() signingInRect.forceActiveFocus() } Keys.onEnterPressed: pauseStatusRect.pauseStatusRectClicked() Keys.onReturnPressed: pauseStatusRect.pauseStatusRectClicked() Keys.onSpacePressed: pauseStatusRect.pauseStatusRectClicked() signal pauseStatusRectClicked() Rectangle { id: pausedStatusInner anchors.fill: parent property int margin: 12 anchors.topMargin: margin anchors.bottomMargin: margin color: parent.color anchors.verticalCenter: parent.verticalCenter Image { id: pausedIcon width: 32 height: 32 anchors.leftMargin : 14 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left source: "file:///" + imageLocation + "paused.svg" sourceSize.width: 42 sourceSize.height: 42 } Column { id: pausedTextGroup anchors.left: pausedIcon.right anchors.leftMargin: 12 anchors.right : parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter Rectangle { height: pausedStatusPrimary.height width: parent.width color: "transparent" Text { id: pausedStatusPrimary color: (theList.count == 0) ? Colors.common.text : Colors.activity_center.error.error_rect_text text: headerModel.pausedTextPrimary font.family: Fonts.standard font.pixelSize: 14 wrapMode: Text.WordWrap anchors.left: parent.left anchors.right: parent.right } } Rectangle { height: pausedStatusSecondary.height width: parent.width color: "transparent" Text { id: pausedStatusSecondary color: (theList.count == 0) ? Colors.common.text : Colors.activity_center.error.error_rect_text text: headerModel.pausedTextSecondary font.pixelSize: 12 font.family: Fonts.standard wrapMode: Text.WordWrap anchors.left: parent.left anchors.right: parent.right } } } MouseArea { id: pauseStatusMouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: pauseStatusRect.pauseStatusRectClicked() } } } Rectangle { id: signingInRect color: Colors.activity_center.list.background z: 1 visible: !needsAttention && ((headerModel.visualState === ActivityCenterHeaderModel.SigningIn) || (headerModel.visualState === ActivityCenterHeaderModel.SigningOut)|| (headerModel.visualState === ActivityCenterHeaderModel.Updating)) anchors { left: parent.left right: parent.right top: theHeader.bottom bottom: parent.bottom } Accessible.ignored: !visible Accessible.role: Accessible.StaticText Accessible.name: signingInTxt.text Text { id: signingInTxt anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: headerModel.signingInText font.family: Fonts.standard font.pixelSize: 12 color: Colors.activity_center.list.progress } Image { id: spinningGraphic source: "file:///" + imageLocation + "loading_spinner.svg" width: 28 height: 28 sourceSize.height: 28 sourceSize.width: 28 anchors { horizontalCenter: parent.horizontalCenter bottom: signingInTxt.top bottomMargin: 4 } // NOTE on battery drain and Animations: we need to explicitly disable the animation // the the window is hidden (the Window.visibility attached property is Window.Hidden) NumberAnimation on rotation { id: rotationAnimation easing.type: Easing.InOutQuad from: -45 to: 315 duration: 1500 loops: Animation.Infinite property bool enabled : false running: enabled && signingInRect.visible } } Window.onVisibilityChanged : { if (Window.visibility === Window.Hidden) { rotationAnimation.enabled = false; } else { rotationAnimation.enabled = true; } } } ListView { id: theList objectName: "theList" anchors.top: pauseStatusRect.bottom width: parent.width; anchors.bottom: footerBar.top model: listModel delegate: listItemDelegate clip: true activeFocusOnTab: (theList.count != 0) visible: !errorsPage.visible keyNavigationWraps: true ScrollBar.vertical: ScrollBar { active: true onActiveChanged: { active = true; /* Keeps scrollbar visible always */} Accessible.ignored: parent.Accessible.ignored } Accessible.role: Accessible.List Accessible.focusable: true Accessible.ignored: ((theList.count == 0) || !visible) Accessible.name: listModel.historyListAccessibleText highlightFollowsCurrentItem: true // changes when a keypress vs mouse movement is detected // used to determine whether list item has a border or not, property bool isUsingKeyboard: false signal itemActivated(int index); signal itemFolderClicked(int index); signal itemShareClicked(int index); signal itemViewOnlineClicked(int index); signal itemVersionHistoryClicked(int index); signal itemCancelProgressClicked(int index); function updateIndexAndScrollToItem(theIndex) { var newIndex = theIndex; if (newIndex < 0) { newIndex = 0; } else if (newIndex >= theList.count) { newIndex = theList.count - 1; } theList.currentIndex = newIndex; theList.positionViewAtIndex(newIndex, ListView.Visible); } Keys.onPressed: { if (theList.count > 0) { isUsingKeyboard = true; var newIndex = -1; var itemHeight = 68; var pageIncrement = theList.height / itemHeight; // How many items to scroll if (event.key === Qt.Key_End) { newIndex = theList.count - 1; updateIndexAndScrollToItem(newIndex); } else if (event.key === Qt.Key_Home) { newIndex = 0; updateIndexAndScrollToItem(newIndex); } else if (event.key === Qt.Key_PageUp) { newIndex = theList.currentIndex - pageIncrement; updateIndexAndScrollToItem(newIndex); } else if (event.key === Qt.Key_PageDown) { newIndex = theList.currentIndex + pageIncrement; updateIndexAndScrollToItem(newIndex); } } } } // The errors page in the Activity Center Rectangle { id: errorsPage anchors.top: theHeader.bottom anchors.bottom: footerBar.top anchors.left: parent.left anchors.right: parent.right visible: isErrorViewCurrentlyVisible color: "transparent" // Allow base color to show through z: 2 // The errors view goes on top of the history view when toggled on // changes when a keypress vs mouse click is detected to enter the page // used to determine whether to start with a black border or not, // for accessibility on page change property bool usingKeyboard: false Loader { id: errorsViewRect objectName: "errorsViewRect" anchors.fill: parent source: "ErrorsView.qml" asynchronous: true active: errorsModel.hasOpenedErrorsView // Proxy for keyPresses to be read in child qml // Set when ErrorsView.qml is loaded and when errors view is loaded property var keyHandler: undefined // Proxy for setting item in focus, set when errors view is loaded signal setFocusOnViewChanged() onLoaded: function() { // Sets key handler and setFocusOnViewChanged handler item.setHandlers(errorsViewRect); } onStatusChanged: function() { if (status === Loader.Error) { activityCenterView.onErrorsViewLoaderError(); } } } } ActivityCenterFooter { id: footerBar objectName: "footerBar" anchors.bottom: parent.bottom width: parent.width isOnlineMode: parent.isOnlineMode isPaused: parent.isPaused isNoNetwork: parent.isNoNetwork isRTL: parent.isRTL z: 3 // Header and footer should be at top of z-order Window.onVisibilityChanged: { if (Window.visibility === Window.Hidden) { footerBar.hidden(); } else { footerBar.shown(); } } } signal onEscapePressed(); Keys.onPressed: { theList.isUsingKeyboard = true; if (!theList.activeFocus && !isErrorViewCurrentlyVisible) { if (HelperFunctions.isHandledKeyPress(event)) { theList.forceActiveFocus(Qt.MouseFocusReason); theList.Keys.onPressed(event); } } else if (isErrorViewCurrentlyVisible && !errorsViewRect.listHasFocus) { if (HelperFunctions.isHandledKeyPress(event)) { errorsViewRect.keyHandler.forceActiveFocus(); errorsViewRect.keyHandler.Keys.onPressed(event); } } if (event.key === Qt.Key_Escape) { onEscapePressed(); } } // This Popup Animation logic is duplicated here // and in ActivityCenterLoader.qml. // This enables us to turn off async loading via ActivityCenterLoader.qml // and still preserve functionality. NumberAnimation { id: anim target: theHeader.Window.window property: "y" duration: 550 easing.type: Easing.OutQuint } function startPopupAnimation(startY, endY) { anim.from = startY; anim.to = endY; anim.start(); } }