/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the Qt Components project. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor ** the names of its contributors may be used to endorse or promote ** products derived from this software without specific prior written ** permission. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** $QT_END_LICENSE$ ** ****************************************************************************/ import QtQuick 1.1 import "." 1.0 import "SectionScroller.js" as Sections Item { id: root property ListView listView onListViewChanged: { if (listView && listView.model) { internal.initDirtyObserver(); } else if (listView) { listView.modelChanged.connect(function() { if (listView.model) { internal.initDirtyObserver(); } }); } } property Style platformStyle: SectionScrollerStyle {} //Deprecated, TODO Remove this on w13 property alias style: root.platformStyle Rectangle { id: container color: "transparent" width: 35 height: listView.height x: listView.x + listView.width - width property bool dragging: false MouseArea { id: dragArea objectName: "dragArea" anchors.fill: parent drag.target: tooltip drag.axis: Drag.YAxis drag.minimumY: listView.y drag.maximumY: listView.y + listView.height - tooltip.height onPressed: { mouseDownTimer.restart() } onReleased: { container.dragging = false; mouseDownTimer.stop() } onPositionChanged: { internal.adjustContentPosition(dragArea.mouseY); } Timer { id: mouseDownTimer interval: 150 onTriggered: { container.dragging = true; internal.adjustContentPosition(dragArea.mouseY); tooltip.positionAtY(dragArea.mouseY); } } } Item { id: tooltip objectName: "popup" opacity: container.dragging ? 1 : 0 anchors.right: parent.right anchors.rightMargin: 50 width: childrenRect.width height: childrenRect.height function positionAtY(yCoord) { tooltip.y = Math.max(dragArea.drag.minimumY, Math.min(yCoord - tooltip.height/2, dragArea.drag.maximumY)); } BorderImage { id: background width: childrenRect.width// + 20 height: childrenRect.height// + 20 anchors.left: parent.left source: platformStyle.backgroundImage border { left: 4; top: 4; right: 4; bottom: 4 } Column { width: Math.max(previousSectionLabel.width, currentSectionLabel.width, nextSectionLabel.width) height: childrenRect.height SectionScrollerLabel { id: previousSectionLabel objectName: "previousSectionLabel" text: internal.prevSection highlighted: internal.curSect === text up: !internal.down } Image { objectName: "divider1" source: platformStyle.dividerImage width: parent.width height: 1 anchors.horizontalCenter: parent.horizontalCenter } SectionScrollerLabel { id: currentSectionLabel objectName: "currentSectionLabel" text: internal.currentSection highlighted: internal.curSect === text up: !internal.down } Image { objectName: "divider2" source: platformStyle.dividerImage width: parent.width height: 1 anchors.horizontalCenter: parent.horizontalCenter } SectionScrollerLabel { id: nextSectionLabel objectName: "nextSectionLabel" text: internal.nextSection highlighted: internal.curSect === text up: !internal.down } } } Image { id: arrow objectName: "arrow" width: 8 height: 16 anchors.left: background.right property int threshold: currentSectionLabel.height property int yInitial: background.y + background.height/2 - height/2 y: getYPosition() source: platformStyle.arrowImage function getYPosition() { var v = internal.curPos; var adjust = v === "first" ? -threshold : v === "last" ? threshold : 0; return yInitial + adjust; } states: [ State { when: root.dragging && dragArea.mouseY < (root.listView.y + threshold) PropertyChanges { target: arrow y: yInitial - threshold } } ] Behavior on y { NumberAnimation { duration: 100 } } } states: [ State { name: "visible" when: container.dragging }, State { extend: "visible" name: "atTop" when: internal.curPos === "first" PropertyChanges { target: previousSectionLabel text: internal.currentSection } PropertyChanges { target: currentSectionLabel text: internal.nextSection } PropertyChanges { target: nextSectionLabel text: Sections.getNextSection(internal.nextSection) } }, State { extend: "visible" name: "atBottom" when: internal.curPos === "last" PropertyChanges { target: previousSectionLabel text: Sections.getPreviousSection(internal.prevSection) } PropertyChanges { target: currentSectionLabel text: internal.prevSection } PropertyChanges { target: nextSectionLabel text: internal.currentSection } } ] Behavior on opacity { NumberAnimation { duration: 100 } } } } Timer { id: dirtyTimer interval: 100 running: false onTriggered: { Sections.initSectionData(listView); internal.modelDirty = false; } } Connections { target: root.listView onCurrentSectionChanged: internal.curSect = container.dragging ? internal.curSect : "" } QtObject { id: internal property string prevSection: "" property string currentSection: listView.currentSection property string nextSection: "" property string curSect: "" property string curPos: "first" property int oldY: 0 property bool modelDirty: false property bool down: true function initDirtyObserver() { Sections.initialize(listView); function dirtyObserver() { if (!internal.modelDirty) { internal.modelDirty = true; dirtyTimer.running = true; } } if (listView.model.countChanged) listView.model.countChanged.connect(dirtyObserver); if (listView.model.itemsChanged) listView.model.itemsChanged.connect(dirtyObserver); if (listView.model.itemsInserted) listView.model.itemsInserted.connect(dirtyObserver); if (listView.model.itemsMoved) listView.model.itemsMoved.connect(dirtyObserver); if (listView.model.itemsRemoved) listView.model.itemsRemoved.connect(dirtyObserver); } function adjustContentPosition(y) { if (y < 0 || y > dragArea.height) return; internal.down = (y > internal.oldY); var sect = Sections.getClosestSection((y / dragArea.height), internal.down); internal.oldY = y; if (internal.curSect != sect) { internal.curSect = sect; internal.curPos = Sections.getSectionPositionString(internal.curSect); var sec = Sections.getRelativeSections(internal.curSect); internal.prevSection = sec[0]; internal.currentSection = sec[1]; internal.nextSection = sec[2]; var idx = Sections.getIndexFor(sect); listView.positionViewAtIndex(idx, ListView.Beginning); } } } }