├── desktop ├── common ├── Pill.qml ├── dialogs │ ├── EpisodeDetailsDialog.qml │ └── AddPodcastDialog.qml └── gpodder.qml ├── touch ├── common ├── icons │ ├── README │ ├── iconic_fill.ttf │ ├── AUTHORS │ ├── icons.js │ └── LICENSE ├── images │ ├── mask.png │ ├── noise.png │ ├── play.png │ ├── delete.png │ ├── gpodder.png │ ├── search.png │ ├── pageshadow.png │ ├── subscriptions.png │ ├── toolbarshadow.png │ └── toolbarshadow-top.png ├── fonts │ └── source-sans-pro.extralight.ttf ├── gpodder.qml ├── PPlaceholder.qml ├── SettingsLabel.qml ├── PLabel.qml ├── PIcon.qml ├── RectangleIndicator.qml ├── IconContextMenu.qml ├── ButtonArea.qml ├── ButtonRow.qml ├── PBusyIndicator.qml ├── PListView.qml ├── ConfirmationButton.qml ├── Dragging.qml ├── PToolbar.qml ├── SectionHeader.qml ├── EpisodeListView.qml ├── PScrollIntoView.qml ├── EpisodeQueryPage.qml ├── EpisodeQueryControl.qml ├── CoverArt.qml ├── PScrollDecorator.qml ├── Stacking.qml ├── DialogStacking.qml ├── SlidePageHeader.qml ├── IconMenuItem.qml ├── DirectoryItem.qml ├── SlidePage.qml ├── PToolbarButton.qml ├── TextInputDialog.qml ├── PExpander.qml ├── PodcastItem.qml ├── PToolbarLabel.qml ├── PSlider.qml ├── SelectionDialog.qml ├── AboutPage.qml ├── Dialog.qml ├── Directory.qml ├── Confirmation.qml ├── EpisodesPage.qml ├── PTextField.qml ├── SettingsPage.qml ├── EpisodeDetail.qml ├── PodcastDetail.qml ├── PodcastsPage.qml ├── EpisodeItem.qml ├── PlayerPage.qml └── Main.qml ├── setup.cfg ├── makefile ├── README └── common ├── GPodderPodcastListModel.qml ├── GPodderPlatform.qml ├── GPodderAutoFire.qml ├── GPodderDirectorySearchModel.qml ├── GPodderPodcastListModelConnections.qml ├── GPodderEpisodeListModelConnections.qml ├── constants.js ├── util.js ├── GPodderCore.qml ├── GPodderEpisodeListModel.qml └── GPodderPlayback.qml /desktop/common: -------------------------------------------------------------------------------- 1 | ../common -------------------------------------------------------------------------------- /touch/common: -------------------------------------------------------------------------------- 1 | ../common -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /touch/icons/README: -------------------------------------------------------------------------------- 1 | http://somerandomdude.com/work/open-iconic/ 2 | -------------------------------------------------------------------------------- /touch/images/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/mask.png -------------------------------------------------------------------------------- /touch/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/noise.png -------------------------------------------------------------------------------- /touch/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/play.png -------------------------------------------------------------------------------- /touch/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/delete.png -------------------------------------------------------------------------------- /touch/images/gpodder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/gpodder.png -------------------------------------------------------------------------------- /touch/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/search.png -------------------------------------------------------------------------------- /touch/icons/iconic_fill.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/icons/iconic_fill.ttf -------------------------------------------------------------------------------- /touch/images/pageshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/pageshadow.png -------------------------------------------------------------------------------- /touch/images/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/subscriptions.png -------------------------------------------------------------------------------- /touch/images/toolbarshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/toolbarshadow.png -------------------------------------------------------------------------------- /touch/images/toolbarshadow-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/images/toolbarshadow-top.png -------------------------------------------------------------------------------- /touch/fonts/source-sans-pro.extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpodder/gpodder-ui-qml/HEAD/touch/fonts/source-sans-pro.extralight.ttf -------------------------------------------------------------------------------- /desktop/Pill.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Image { 4 | property int leftCount: 0 5 | property int rightCount: 0 6 | source: 'image://python/pill/' + leftCount + '/' + rightCount 7 | cache: true 8 | } 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PROJECT := gpodder-ui-qml 2 | VERSION := 4.11.1 3 | 4 | all: 5 | @echo "" 6 | @echo " make release ..... Build source release" 7 | @echo "" 8 | 9 | release: dist/$(PROJECT)-$(VERSION).tar.gz 10 | 11 | dist/$(PROJECT)-$(VERSION).tar.gz: 12 | mkdir -p dist 13 | git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ $(VERSION) | gzip >$@ 14 | 15 | clean: 16 | find . -name '__pycache__' -exec rm -rf {} + 17 | 18 | distclean: clean 19 | rm -rf dist 20 | 21 | .PHONY: all release clean 22 | -------------------------------------------------------------------------------- /touch/icons/AUTHORS: -------------------------------------------------------------------------------- 1 | Iconic was created and is maintained primarily by P.J. Onori (www.github.com/somerandomdude) 2 | 3 | 4 | Many thanks to all the contributors of Iconic. Their help has added immeasurable worth to the project. 5 | 6 | Philip Shaw (www.codestyle.org): Provided guidance for Unicode assignment method in Iconic's fonts. 7 | 8 | Yann Hourdel (www.github.com/yhourdel): Developed the font creation Python scripts. 9 | 10 | Ian Storm Taylor (www.github.com/ianstormtaylor): Created JSON files containing Unicode character assignments in Iconic's font files for better browser compatibility and easier management for font creation. -------------------------------------------------------------------------------- /desktop/dialogs/EpisodeDetailsDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Dialogs 1.2 5 | 6 | import '../common' 7 | import '../common/util.js' as Util 8 | 9 | Rectangle { 10 | property var episode 11 | color: '#aa000000' 12 | 13 | anchors.fill: parent 14 | 15 | MouseArea { 16 | anchors.fill: parent 17 | onClicked: parent.destroy(); 18 | } 19 | 20 | TextArea { 21 | anchors.fill: parent 22 | anchors.margins: 50 23 | readOnly: true 24 | text: episode ? episode.description : '...' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /touch/icons/icons.js: -------------------------------------------------------------------------------- 1 | var font = 'Iconic'; 2 | var trash = '\ue05a'; 3 | var cd = '\ue064'; 4 | var play = '\ue047'; 5 | var reload = '\ue030'; 6 | var plus = '\u2795'; 7 | var pause = '\ue049'; 8 | var cloud_download = '\ue044'; 9 | var star = '\u2605'; 10 | var article = '\ue053'; 11 | var first = '\ue04c'; 12 | var arrow_left = '\u2190'; 13 | var arrow_right = '\u2192'; 14 | var last = '\ue04d'; 15 | var aperture = '\ue026'; 16 | var eye = '\ue025'; 17 | var loop_alt2 = '\ue033'; 18 | var folder = '\ue065'; 19 | var magnifying_glass = '\ue074'; 20 | var cog = '\u2699'; 21 | var link = '\ue077'; 22 | var paperclip = '\ue08a'; 23 | var tag_fill = '\ue02b'; 24 | var headphones = '\ue061'; 25 | var sleep = '\u263e'; 26 | var stack = '\ue020'; 27 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | gPodder 4 QML UI Reference Implementation 2 | ----------------------------------------- 3 | 4 | This is the reference implementation of the QML UI for gPodder 4. It contains a 5 | (work in progress) Desktop UI and a minimal Touch UI for mobile devices. The 6 | Desktop UI requires Qt Quick Controls, included in Qt 5.2 and newer. 7 | 8 | Usage: 9 | 10 | qmlscene desktop/gpodder.qml 11 | qmlscene touch/gpodder.qml 12 | 13 | Dependencies: 14 | 15 | Package Min.Version URL 16 | ------------------------------------------------------------ 17 | Qt 5.1.0 http://qt-project.org/ 18 | Qt Multimedia 5.1.0 http://qt-project.org/ 19 | Python 3.2.0 http://python.org/ 20 | PyOtherSide 1.3.0 http://thp.io/2011/pyotherside/ 21 | gPodder Core 4.0.0 http://gpodder.org/ 22 | -------------------------------------------------------------------------------- /desktop/dialogs/AddPodcastDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Dialogs 1.2 5 | 6 | import '../common' 7 | import '../common/util.js' as Util 8 | 9 | Dialog { 10 | property alias labelText: urlEntyLabel.text 11 | signal addUrl(string url) 12 | 13 | width: 300 14 | height: 100 15 | title: 'Add new podcast' 16 | standardButtons: StandardButton.Open | StandardButton.Cancel 17 | 18 | RowLayout { 19 | anchors.fill: parent 20 | 21 | Label { 22 | id: urlEntyLabel 23 | text: 'URL:' 24 | } 25 | 26 | TextField { 27 | id: urlEntry 28 | focus: true 29 | 30 | Layout.fillWidth: true 31 | } 32 | } 33 | 34 | onAccepted: { 35 | addUrl(urlEntry.text); 36 | visible = false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /touch/gpodder.qml: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * gPodder QML UI Reference Implementation 4 | * Copyright (c) 2013, Thomas Perl 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 11 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 12 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 15 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | * PERFORMANCE OF THIS SOFTWARE. 17 | * 18 | */ 19 | 20 | import QtQuick 2.0 21 | 22 | import 'common/constants.js' as Constants 23 | 24 | Rectangle { 25 | color: Constants.colors.page 26 | 27 | width: 480 28 | height: 800 29 | 30 | Main {} 31 | } 32 | -------------------------------------------------------------------------------- /touch/PPlaceholder.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | 26 | PLabel { 27 | anchors.centerIn: parent 28 | font.pixelSize: 40 * pgst.scalef 29 | color: Constants.colors.placeholder 30 | } 31 | -------------------------------------------------------------------------------- /touch/SettingsLabel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2015, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | PLabel { 26 | anchors { 27 | left: parent.left 28 | right: parent.right 29 | margins: Constants.layout.padding * pgst.scalef 30 | } 31 | color: Constants.colors.secondaryHighlight 32 | } 33 | -------------------------------------------------------------------------------- /touch/PLabel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Text { 26 | font.pixelSize: 30 * pgst.scalef 27 | font.family: Constants.font 28 | color: Constants.colors.text 29 | onLinkActivated: Qt.openUrlExternally(link) 30 | linkColor: Constants.colors.highlight 31 | } 32 | 33 | -------------------------------------------------------------------------------- /touch/PIcon.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * 5 | * gPodder QML UI Reference Implementation 6 | * Copyright (c) 2013, Thomas Perl 7 | * 8 | * Permission to use, copy, modify, and/or distribute this software for any 9 | * purpose with or without fee is hereby granted, provided that the above 10 | * copyright notice and this permission notice appear in all copies. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 13 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 14 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 15 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 16 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 17 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 18 | * PERFORMANCE OF THIS SOFTWARE. 19 | * 20 | */ 21 | 22 | import QtQuick 2.0 23 | 24 | import 'common/constants.js' as Constants 25 | import 'icons/icons.js' as Icons 26 | 27 | PLabel { 28 | id: picon 29 | 30 | property int size: 48 31 | property string icon 32 | 33 | text: icon 34 | font.pixelSize: picon.size * pgst.scalef 35 | font.family: Icons.font 36 | } 37 | -------------------------------------------------------------------------------- /touch/RectangleIndicator.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2015, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Rectangle { 26 | width: Constants.layout.padding * 2 * pgst.scalef * enabled 27 | height: width 28 | 29 | Behavior on width { PropertyAnimation { } } 30 | 31 | anchors { 32 | verticalCenter: parent.verticalCenter 33 | right: parent.right 34 | margins: Constants.layout.padding * 2 * pgst.scalef - width / 2 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/GPodderPodcastListModel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'util.js' as Util 24 | 25 | ListModel { 26 | id: podcastListModel 27 | property bool firstRun: false 28 | 29 | function reload() { 30 | py.call('main.load_podcasts', [], function (podcasts) { 31 | Util.updateModelFrom(podcastListModel, podcasts); 32 | if(!firstRun) { 33 | firstRun = true; 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /touch/IconContextMenu.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Item { 26 | id: contextMenu 27 | default property alias children: contextMenuRow.children 28 | height: 80 * pgst.scalef 29 | 30 | Row { 31 | id: contextMenuRow 32 | anchors { 33 | verticalCenter: parent.verticalCenter 34 | right: parent.right 35 | margins: Constants.layout.padding * pgst.scalef 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/GPodderPlatform.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Item { 24 | property bool emulatingAndroid: false 25 | 26 | property bool android: (typeof(gpodderAndroid) !== 'undefined') || emulatingAndroid 27 | 28 | property bool needsBackButton: !android 29 | 30 | property bool toolbarOnTop: true 31 | property bool invertedToolbar: toolbarOnTop 32 | property bool titleInToolbar: toolbarOnTop 33 | 34 | property bool floatingPlayButton: true 35 | property bool hideDisabledMenu: true 36 | } 37 | -------------------------------------------------------------------------------- /common/GPodderAutoFire.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2015, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Timer { 24 | property int triggerCount: 0 25 | property int initialInterval: 1500 26 | property int autoFireInterval: 200 27 | 28 | signal fired() 29 | 30 | interval: triggerCount > 1 ? autoFireInterval : initialInterval 31 | 32 | repeat: true 33 | triggeredOnStart: true 34 | 35 | onRunningChanged: { 36 | if (!running) { 37 | triggerCount = 0 38 | } 39 | } 40 | 41 | onTriggered: { 42 | triggerCount += 1 43 | fired() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /touch/ButtonArea.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | MouseArea { 26 | id: mouseArea 27 | property bool transparent: false 28 | property bool canHighlight: true 29 | property alias color: background.color 30 | 31 | Rectangle { 32 | id: background 33 | anchors.fill: parent 34 | color: Constants.colors.area 35 | opacity: (mouseArea.pressed && mouseArea.canHighlight) ? 1 : .5 36 | visible: parent.enabled && ((mouseArea.canHighlight && mouseArea.pressed) || !mouseArea.transparent) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/GPodderDirectorySearchModel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | ListModel { 24 | id: directorySearchModel 25 | property string provider 26 | 27 | function search(query, callback) { 28 | clear(); 29 | 30 | py.call('main.get_directory_entries', [directorySearchModel.provider, query], function (result) { 31 | for (var i=0; i 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Row { 24 | id: buttonRow 25 | property var model 26 | 27 | height: 100 * pgst.scalef 28 | 29 | Repeater { 30 | id: repeater 31 | model: buttonRow.model 32 | 33 | delegate: ButtonArea { 34 | height: buttonRow.height 35 | width: buttonRow.width / repeater.count 36 | onClicked: buttonRow.model[index].clicked() 37 | 38 | PLabel { 39 | anchors.centerIn: parent 40 | text: modelData.label 41 | } 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /touch/PBusyIndicator.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'icons/icons.js' as Icons 24 | import 'common/constants.js' as Constants 25 | 26 | Item { 27 | height: 64 * pgst.scalef 28 | width: 64 * pgst.scalef 29 | 30 | PIcon { 31 | anchors.centerIn: parent 32 | icon: Icons.aperture 33 | rotation: 180 * parent.phase 34 | color: Constants.colors.highlight 35 | } 36 | 37 | property real phase: 0 38 | 39 | PropertyAnimation on phase { 40 | loops: Animation.Infinite 41 | duration: 5000 42 | running: parent.visible 43 | from: 0 44 | to: 2*Math.PI 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /touch/PListView.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | ListView { 24 | id: pListView 25 | 26 | anchors.fill: parent 27 | 28 | property string title 29 | property real pushPhase: 0 30 | 31 | boundsBehavior: Flickable.StopAtBounds 32 | 33 | function relayout() { 34 | var _contentY = contentY; 35 | var _model = model; 36 | model = null; 37 | model = _model; 38 | contentY = _contentY; 39 | } 40 | 41 | Connections { 42 | target: pgst 43 | onScalefChanged: relayout(); 44 | } 45 | 46 | header: SlidePageHeader { 47 | id: header 48 | title: pListView.title 49 | } 50 | 51 | PScrollDecorator { flickable: pListView } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /touch/ConfirmationButton.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | ButtonArea { 26 | id: confirmationButton 27 | 28 | property alias text: label.text 29 | property alias icon: icon.icon 30 | property color contentColor: Constants.colors.highlight 31 | 32 | Row { 33 | anchors.centerIn: parent 34 | spacing: 5 * pgst.scalef 35 | 36 | PIcon { 37 | id: icon 38 | anchors.verticalCenter: parent.verticalCenter 39 | color: confirmationButton.contentColor 40 | } 41 | 42 | PLabel { 43 | id: label 44 | anchors.verticalCenter: parent.verticalCenter 45 | color: confirmationButton.contentColor 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/GPodderPodcastListModelConnections.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Connections { 24 | target: py 25 | 26 | onPodcastListChanged: { 27 | podcastListModel.reload(); 28 | } 29 | 30 | onUpdatingPodcast: { 31 | for (var i=0; i 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | MouseArea { 24 | id: dragging 25 | 26 | property Item stacking 27 | property bool canClose: true 28 | 29 | anchors.fill: parent 30 | 31 | drag { 32 | target: parent 33 | axis: Drag.XAxis 34 | minimumX: 0 35 | maximumX: canClose ? parent.width : 0 36 | filterChildren: true 37 | } 38 | 39 | onPressedChanged: { 40 | if (pgst.loadPageInProgress) { 41 | return; 42 | } 43 | 44 | if (pressed) { 45 | dragging.stacking.stopAllAnimations(); 46 | } else { 47 | if (parent.x > parent.width / 3) { 48 | dragging.stacking.startFadeOut(); 49 | } else { 50 | dragging.stacking.fadeInAgain(); 51 | } 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /touch/PToolbar.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import 'common' 23 | 24 | import 'common/constants.js' as Constants 25 | import 'icons/icons.js' as Icons 26 | 27 | Rectangle { 28 | id: toolbar 29 | property bool showing: true 30 | 31 | color: platform.invertedToolbar ? Constants.colors.inverted.toolbar : Constants.colors.toolbar 32 | 33 | height: 80 * pgst.scalef 34 | 35 | MouseArea { 36 | // Capture all touch events 37 | anchors.fill: parent 38 | } 39 | 40 | anchors { 41 | left: parent.left 42 | right: parent.right 43 | topMargin: toolbar.showing ? 0 : -toolbar.height 44 | bottomMargin: toolbar.showing ? 0 : -toolbar.height 45 | } 46 | 47 | Behavior on anchors.bottomMargin { PropertyAnimation { duration: 100 } } 48 | Behavior on anchors.topMargin { PropertyAnimation { duration: 100 } } 49 | } 50 | -------------------------------------------------------------------------------- /touch/SectionHeader.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Item { 26 | property alias text: pLabel.text 27 | property alias color: pLabel.color 28 | 29 | height: 70 * pgst.scalef 30 | width: parent.width 31 | 32 | Rectangle { 33 | anchors { 34 | verticalCenter: parent.verticalCenter 35 | left: parent.left 36 | right: pLabel.left 37 | margins: Constants.layout.padding * pgst.scalef 38 | } 39 | 40 | color: pLabel.color 41 | height: 1 * pgst.scalef 42 | } 43 | 44 | PLabel { 45 | id: pLabel 46 | anchors { 47 | right: parent.right 48 | verticalCenter: parent.verticalCenter 49 | margins: Constants.layout.padding * pgst.scalef 50 | } 51 | 52 | color: Constants.colors.secondaryHighlight 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /touch/EpisodeListView.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | 25 | import 'common/util.js' as Util 26 | 27 | 28 | PListView { 29 | id: episodeList 30 | 31 | property int selectedIndex: -1 32 | 33 | PScrollIntoView { id: scrollIntoView } 34 | 35 | onSelectedIndexChanged: { 36 | if (selectedIndex === count - 1) { 37 | scrollIntoView.begin(episodeList); 38 | } 39 | } 40 | 41 | model: GPodderEpisodeListModel { id: episodeListModel } 42 | GPodderEpisodeListModelConnections {} 43 | 44 | PBusyIndicator { 45 | visible: !episodeListModel.ready 46 | anchors.centerIn: parent 47 | } 48 | 49 | PPlaceholder { 50 | // TODO: If filter is "all", say "No episodes" 51 | text: 'No episodes found' 52 | visible: episodeList.count === 0 && episodeListModel.ready 53 | } 54 | 55 | delegate: EpisodeItem { } 56 | } 57 | -------------------------------------------------------------------------------- /touch/PScrollIntoView.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | 26 | Timer { 27 | id: scrollTimer 28 | interval: 10 29 | repeat: true 30 | 31 | function begin(flickable_) { 32 | flickable = flickable_; 33 | lastHeight = flickable.contentHeight; 34 | repeatLimit = defaultRepeatLimit; 35 | scrollTimer.start(); 36 | } 37 | 38 | property var flickable 39 | property int defaultRepeatLimit: 10 40 | property int repeatLimit: defaultRepeatLimit 41 | property real lastHeight: 0 42 | 43 | onTriggered: { 44 | flickable.contentY = flickable.contentHeight - flickable.height; 45 | flickable.returnToBounds(); 46 | 47 | repeatLimit = repeatLimit - 1; 48 | lastHeight = flickable.contentHeight; 49 | 50 | if (repeatLimit <= 0 && lastHeight === flickable.contentHeight) { 51 | stop(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /touch/EpisodeQueryPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | import 'common/util.js' as Util 25 | import 'common/constants.js' as Constants 26 | import 'icons/icons.js' as Icons 27 | 28 | SlidePage { 29 | id: page 30 | 31 | hasMenuButton: true 32 | menuButtonIcon: Icons.magnifying_glass 33 | menuButtonLabel: 'Filter' 34 | onMenuButtonClicked: queryControl.showSelectionDialog() 35 | 36 | EpisodeQueryControl { 37 | id: queryControl 38 | model: episodeList.model 39 | title: 'Select filter' 40 | } 41 | 42 | EpisodeListView { 43 | id: episodeList 44 | title: 'Episodes' 45 | 46 | section.property: 'section' 47 | section.delegate: SectionHeader { 48 | text: section 49 | color: episodeList.selectedIndex === -1 ? Constants.colors.secondaryHighlight : Constants.colors.text 50 | opacity: episodeList.selectedIndex === -1 ? 1 : 0.2 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /touch/EpisodeQueryControl.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Item { 24 | id: episodeQueryControl 25 | 26 | property var model 27 | property string title 28 | property string currentFilter: model.filters[model.currentFilterIndex].label 29 | 30 | function showSelectionDialog() { 31 | pgst.loadPage('SelectionDialog.qml', { 32 | title: episodeQueryControl.title, 33 | callback: function (index, result) { 34 | episodeQueryControl.model.setQueryIndex(index); 35 | episodeQueryControl.model.reload(); 36 | }, 37 | items: function () { 38 | var labels = []; 39 | for (var i in episodeQueryControl.model.filters) { 40 | labels.push(episodeQueryControl.model.filters[i].label); 41 | } 42 | return labels; 43 | }(), 44 | selectedIndex: episodeQueryControl.model.currentFilterIndex, 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /touch/CoverArt.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * 5 | * gPodder QML UI Reference Implementation 6 | * Copyright (c) 2013, 2014, Thomas Perl 7 | * 8 | * Permission to use, copy, modify, and/or distribute this software for any 9 | * purpose with or without fee is hereby granted, provided that the above 10 | * copyright notice and this permission notice appear in all copies. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 13 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 14 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 15 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 16 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 17 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 18 | * PERFORMANCE OF THIS SOFTWARE. 19 | * 20 | */ 21 | 22 | import QtQuick 2.0 23 | 24 | import 'common/constants.js' as Constants 25 | 26 | Item { 27 | id: coverArt 28 | 29 | property string text 30 | property alias source: cover.source 31 | 32 | Image { 33 | id: cover 34 | asynchronous: true 35 | 36 | opacity: source && status == Image.Ready 37 | Behavior on opacity { PropertyAnimation { duration: 100 } } 38 | 39 | sourceSize.width: width 40 | sourceSize.height: height 41 | 42 | width: parent.width 43 | height: parent.height 44 | } 45 | 46 | Rectangle { 47 | opacity: 0.3 * (1 - cover.opacity) 48 | anchors.fill: cover 49 | clip: true 50 | 51 | color: Constants.colors.background 52 | 53 | PLabel { 54 | text: coverArt.text[0] 55 | color: Constants.colors.highlight 56 | anchors.centerIn: parent 57 | font.pixelSize: parent.height * .8 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /common/GPodderEpisodeListModelConnections.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'util.js' as Util 24 | 25 | Connections { 26 | target: py 27 | 28 | onDownloadProgress: { 29 | Util.updateModelWith(episodeListModel, 'id', episode_id, 30 | {'progress': progress}); 31 | } 32 | onPlaybackProgress: { 33 | Util.updateModelWith(episodeListModel, 'id', episode_id, 34 | {'playbackProgress': progress}); 35 | } 36 | onUpdatedEpisode: { 37 | for (var i=0; i 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Rectangle { 26 | property var flickable 27 | 28 | x: flickable.width - width 29 | y: flickable.visibleArea.yPosition * flickable.height 30 | width: 10 * pgst.scalef 31 | height: flickable.visibleArea.heightRatio * flickable.height 32 | visible: flickable.visibleArea.heightRatio < 1 33 | color: Constants.colors.background 34 | opacity: (showMoreTimer.showTemporarily || flickable.moving) ? .5 : 0 35 | Behavior on opacity { PropertyAnimation { duration: 100 } } 36 | 37 | Timer { 38 | id: showMoreTimer 39 | property bool showTemporarily: false 40 | interval: 500 41 | onTriggered: { 42 | if (parent.visible && !showTemporarily) { 43 | showTemporarily = true; 44 | showMoreTimer.interval = 2000; 45 | showMoreTimer.start(); 46 | } else { 47 | showTemporarily = false; 48 | } 49 | } 50 | } 51 | 52 | Component.onCompleted: { 53 | showMoreTimer.start(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /touch/Stacking.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Item { 24 | id: stacking 25 | property variant page: parent 26 | 27 | PropertyAnimation { 28 | id: fadeIn 29 | target: stacking.page 30 | property: 'x' 31 | to: 0 32 | duration: 500 33 | easing.type: Easing.OutCubic 34 | 35 | onStopped: { 36 | pgst.loadPageInProgress = false; 37 | } 38 | } 39 | 40 | PropertyAnimation { 41 | id: fadeOut 42 | target: stacking.page 43 | property: 'x' 44 | to: stacking.page.width 45 | duration: 500 46 | easing.type: Easing.OutCubic 47 | } 48 | 49 | function startFadeOut() { 50 | fadeOut.start(); 51 | pgst.topOfStackChanged(-1); 52 | page.destroy(500); 53 | } 54 | 55 | function fadeInAgain() { 56 | fadeIn.start(); 57 | } 58 | 59 | function stopAllAnimations() { 60 | fadeIn.stop(); 61 | } 62 | 63 | Component.onCompleted: { 64 | if (pgst.loadPageInProgress) { 65 | page.x = page.width; 66 | fadeIn.start(); 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /touch/DialogStacking.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | Item { 24 | id: stacking 25 | property variant page: parent 26 | 27 | PropertyAnimation { 28 | id: fadeIn 29 | target: stacking.page 30 | property: 'opacity' 31 | to: 1 32 | duration: 500 33 | easing.type: Easing.OutCubic 34 | 35 | onStopped: { 36 | pgst.loadPageInProgress = false; 37 | } 38 | } 39 | 40 | PropertyAnimation { 41 | id: fadeOut 42 | target: stacking.page 43 | property: 'opacity' 44 | to: 0 45 | duration: 500 46 | easing.type: Easing.OutCubic 47 | } 48 | 49 | function startFadeOut() { 50 | fadeOut.start(); 51 | pgst.topOfStackChanged(-1); 52 | page.destroy(500); 53 | } 54 | 55 | function fadeInAgain() { 56 | fadeIn.start(); 57 | } 58 | 59 | function stopAllAnimations() { 60 | fadeIn.stop(); 61 | } 62 | 63 | Component.onCompleted: { 64 | if (pgst.loadPageInProgress) { 65 | page.x = 0; 66 | page.opacity = 0; 67 | fadeIn.start(); 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /touch/SlidePageHeader.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'icons/icons.js' as Icons 25 | 26 | Item { 27 | id: slidePageHeader 28 | property alias title: label.text 29 | property alias color: label.color 30 | property alias wrapMode: label.wrapMode 31 | property bool isOnSlidePage: (typeof(page) !== 'undefined') ? page : null 32 | property real padding: 20 33 | 34 | width: parent.width 35 | 36 | visible: !platform.titleInToolbar || !isOnSlidePage 37 | height: visible ? (2 * padding * pgst.scalef + label.height) : 0 38 | 39 | Binding { 40 | target: isOnSlidePage ? page : null 41 | property: 'title' 42 | value: slidePageHeader.title 43 | when: platform.titleInToolbar 44 | } 45 | 46 | PLabel { 47 | id: label 48 | anchors { 49 | left: parent.left 50 | right: parent.right 51 | rightMargin: slidePageHeader.padding * pgst.scalef 52 | leftMargin: slidePageHeader.padding * pgst.scalef 53 | verticalCenter: parent.verticalCenter 54 | } 55 | 56 | color: Constants.colors.highlight 57 | font.pixelSize: Constants.layout.header.height * pgst.scalef * .4 58 | elide: Text.ElideRight 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /touch/IconMenuItem.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | ButtonArea { 26 | id: iconMenuItem 27 | 28 | property alias text: label.text 29 | property color color: Constants.colors.secondaryHighlight 30 | property color colorDisabled: Constants.colors.placeholder 31 | property color _real_color: enabled ? color : colorDisabled 32 | property alias icon: icon.icon 33 | property alias size: icon.size 34 | property bool alwaysShowText: false 35 | 36 | Behavior on _real_color { ColorAnimation { duration: 100 } } 37 | 38 | transparent: true 39 | canHighlight: false 40 | 41 | height: 80 * pgst.scalef 42 | width: height 43 | 44 | PIcon { 45 | id: icon 46 | anchors.centerIn: parent 47 | opacity: iconMenuItem.enabled ? 1 : .2 48 | color: label.color 49 | } 50 | 51 | PLabel { 52 | id: label 53 | font.pixelSize: 15 * pgst.scalef 54 | visible: parent.pressed || parent.alwaysShowText 55 | color: parent.pressed ? Qt.darker(iconMenuItem._real_color, 1.1) : iconMenuItem._real_color 56 | 57 | anchors { 58 | bottom: icon.top 59 | horizontalCenter: icon.horizontalCenter 60 | margins: 5 * pgst.scalef 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /touch/DirectoryItem.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | ButtonArea { 24 | id: podcastItem 25 | 26 | transparent: true 27 | 28 | height: 100 * pgst.scalef 29 | anchors { 30 | left: parent.left 31 | right: parent.right 32 | } 33 | 34 | Image { 35 | id: cover 36 | 37 | anchors { 38 | left: parent.left 39 | leftMargin: 10 * pgst.scalef 40 | verticalCenter: parent.verticalCenter 41 | } 42 | 43 | sourceSize.width: width 44 | sourceSize.height: height 45 | 46 | width: 80 * pgst.scalef 47 | height: 80 * pgst.scalef 48 | 49 | source: image 50 | } 51 | 52 | PLabel { 53 | anchors { 54 | left: cover.right 55 | leftMargin: 10 * pgst.scalef 56 | rightMargin: 10 * pgst.scalef 57 | right: subcount.left 58 | verticalCenter: parent.verticalCenter 59 | } 60 | 61 | elide: Text.ElideRight 62 | text: title 63 | } 64 | 65 | PLabel { 66 | id: subcount 67 | anchors { 68 | right: parent.right 69 | rightMargin: 10 * pgst.scalef 70 | verticalCenter: parent.verticalCenter 71 | } 72 | 73 | text: (subscribers > 0) ? subscribers : '' 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /touch/SlidePage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'icons/icons.js' as Icons 25 | 26 | Rectangle { 27 | id: page 28 | color: Constants.colors.page 29 | 30 | Component.onCompleted: pgst.topOfStackChanged(); 31 | 32 | default property alias children: dragging.children 33 | property alias canClose: dragging.canClose 34 | property bool isDialog: false 35 | 36 | property string title: '' 37 | property bool hasMenuButton: false 38 | property string menuButtonLabel: 'Menu' 39 | property string menuButtonIcon: Icons.stack 40 | signal menuButtonClicked() 41 | 42 | function closePage() { 43 | if (canClose) { 44 | stacking.startFadeOut(); 45 | } 46 | } 47 | 48 | onXChanged: pgst.update(page, x) 49 | 50 | width: parent.width 51 | height: parent.height - parent.bottomSpacing 52 | 53 | y: platform.toolbarOnTop ? parent.bottomSpacing : 0 54 | 55 | Stacking { id: stacking } 56 | 57 | Dragging { 58 | id: dragging 59 | stacking: stacking 60 | } 61 | 62 | Image { 63 | anchors { 64 | right: parent.left 65 | top: parent.top 66 | bottom: parent.bottom 67 | } 68 | width: 10 * pgst.scalef 69 | source: 'images/pageshadow.png' 70 | opacity: .1 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /touch/PToolbarButton.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import 'common' 23 | 24 | import 'common/constants.js' as Constants 25 | import 'icons/icons.js' as Icons 26 | 27 | Rectangle { 28 | id: toolbarButton 29 | 30 | //property alias color: iconMenuItem.color 31 | //property alias text: iconMenuItem.text 32 | property string text: '' 33 | property alias icon: iconMenuItem.icon 34 | 35 | signal clicked() 36 | 37 | width: iconMenuItem.width 38 | height: iconMenuItem.height 39 | color: iconMenuItem.pressed ? (platform.invertedToolbar ? Constants.colors.inverted.toolbarArea : Constants.colors.toolbarArea) : 'transparent' 40 | 41 | Rectangle { 42 | visible: iconMenuItem.pressed && !platform.invertedToolbar 43 | height: 5 * pgst.scalef 44 | color: Constants.colors.secondaryHighlight 45 | 46 | anchors { 47 | left: parent.left 48 | right: parent.right 49 | top: parent.top 50 | } 51 | } 52 | 53 | IconMenuItem { 54 | id: iconMenuItem 55 | color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText 56 | colorDisabled: platform.invertedToolbar ? Constants.colors.inverted.toolbarDisabled : Constants.colors.toolbarDisabled 57 | transparent: true 58 | enabled: parent.enabled 59 | onClicked: toolbarButton.clicked() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /touch/TextInputDialog.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Dialog { 26 | id: textInputDialog 27 | moveToTop: true 28 | 29 | property string buttonText 30 | property string placeholderText 31 | property string text 32 | property bool pasteOnLoad: false 33 | property var callback 34 | 35 | contentHeight: contentColumn.height + 20 * pgst.scalef 36 | 37 | Component.onCompleted: { 38 | if (pasteOnLoad) { 39 | input.paste(); 40 | } 41 | } 42 | 43 | function accept() { 44 | textInputDialog.callback(input.text); 45 | textInputDialog.closePage(); 46 | } 47 | 48 | Column { 49 | id: contentColumn 50 | 51 | anchors.centerIn: parent 52 | spacing: 20 * pgst.scalef 53 | 54 | PTextField { 55 | id: input 56 | width: textInputDialog.width *.8 57 | placeholderText: textInputDialog.placeholderText 58 | text: textInputDialog.text 59 | onAccepted: textInputDialog.accept(); 60 | } 61 | 62 | ButtonArea { 63 | id: button 64 | width: input.width 65 | height: input.height 66 | visible: textInputDialog.buttonText !== '' 67 | 68 | PLabel { 69 | anchors.centerIn: parent 70 | text: textInputDialog.buttonText 71 | } 72 | 73 | onClicked: textInputDialog.accept(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /common/constants.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | var layout = { 22 | header: { 23 | height: 100, /* page header height */ 24 | }, 25 | item: { 26 | height: 80, /* podcast/episode item height */ 27 | }, 28 | coverart: 80, /* cover art size */ 29 | padding: 10, /* padding of items left/right */ 30 | }; 31 | 32 | var colors = { 33 | download: '#7ac224', /* download green */ 34 | select: '#7f5785', /* gpodder dark purple */ 35 | fresh: '#815c86', /* gpodder purple */ 36 | playback: '#729fcf', /* playback blue */ 37 | destructive: '#cf424f', /* destructive actions */ 38 | 39 | toolbar: '#d0d0d0', 40 | toolbarText: '#333333', 41 | toolbarDisabled: '#666666', 42 | 43 | inverted: { 44 | toolbar: '#815c86', 45 | toolbarText: '#ffffff', 46 | toolbarDisabled: '#aaffffff', 47 | }, 48 | 49 | page: '#dddddd', 50 | dialog: '#dddddd', 51 | dialogBackground: '#aa000000', 52 | text: '#333333', /* text color */ 53 | dialogText: '#333333', 54 | highlight: '#433b67', 55 | dialogHighlight: '#433b67', 56 | secondaryHighlight: '#605885', 57 | area: '#cccccc', 58 | dialogArea: '#d0d0d0', 59 | toolbarArea: '#bbbbbb', 60 | placeholder: '#666666', 61 | 62 | //page: '#000000', 63 | //text: '#ffffff', /* text color */ 64 | //highlight: Qt.lighter('#433b67', 1.2), 65 | //secondaryHighlight: Qt.lighter('#605885', 1.2), 66 | //area: '#333333', 67 | //placeholder: '#aaaaaa', 68 | 69 | background: '#948db3', 70 | secondaryBackground: '#d0cce1', 71 | }; 72 | 73 | var font = 'Source Sans Pro'; 74 | 75 | var state = { 76 | normal: 0, 77 | downloaded: 1, 78 | deleted: 2, 79 | }; 80 | 81 | -------------------------------------------------------------------------------- /touch/PExpander.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'common/util.js' as Util 25 | 26 | 27 | Item { 28 | id: expander 29 | 30 | property bool expanded: !canExpand 31 | property bool canExpand: expandedHeight > contractedHeight 32 | 33 | property real contractedHeight: 100 * pgst.scalef 34 | property real expandedHeight: childrenRect.height 35 | property color backgroundColor: Constants.colors.page 36 | 37 | height: expanded ? expandedHeight : contractedHeight 38 | clip: true 39 | 40 | Behavior on height { PropertyAnimation { } } 41 | 42 | MouseArea { 43 | anchors.fill: parent 44 | onClicked: { 45 | if (expander.canExpand) { 46 | expander.expanded = !expander.expanded 47 | } 48 | } 49 | } 50 | 51 | Rectangle { 52 | z: 100 53 | 54 | opacity: chapterExpander.opacity 55 | 56 | anchors { 57 | left: parent.left 58 | right: parent.right 59 | bottom: parent.bottom 60 | } 61 | 62 | height: expander.contractedHeight * 0.6 63 | 64 | gradient: Gradient { 65 | GradientStop { position: 0; color: '#00000000' } 66 | GradientStop { position: 1; color: expander.backgroundColor } 67 | } 68 | } 69 | 70 | PLabel { 71 | id: chapterExpander 72 | 73 | z: 200 74 | 75 | anchors { 76 | right: parent.right 77 | bottom: parent.bottom 78 | } 79 | 80 | text: '...' 81 | font.pixelSize: 60 * pgst.scalef 82 | 83 | color: Constants.colors.highlight 84 | opacity: !expander.expanded 85 | 86 | Behavior on opacity { PropertyAnimation { } } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /common/util.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | function updateModelFrom(model, data) { 22 | for (var i=0; i data.length) { 31 | model.remove(model.count-1); 32 | } 33 | } 34 | 35 | function updateModelWith(model, key, value, update) { 36 | for (var row=0; row 0 ? (h < 10 ? '0' + h : h) + ':' : '' 56 | var ms = (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s) 57 | 58 | return hh + ms 59 | } 60 | 61 | function formatPosition(position,duration) { 62 | return formatDuration(position) + " / " + formatDuration(duration) 63 | } 64 | 65 | // Call a Python function and disable item until the function returns 66 | function disableUntilReturn(item, py, func, args) { 67 | item.enabled = false; 68 | py.call(func, args, function() { 69 | item.enabled = true; 70 | }); 71 | } 72 | 73 | function format(s, d) { 74 | return s.replace(/{([^}]*)}/g, function (m, k) { 75 | return (k in d) ? d[k] : m; 76 | }); 77 | } 78 | 79 | function atMostOnce(callback) { 80 | var called = false; 81 | return function () { 82 | if (!called) { 83 | called = true; 84 | callback(); 85 | } 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /touch/PodcastItem.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | ButtonArea { 26 | id: podcastItem 27 | 28 | transparent: true 29 | 30 | height: Constants.layout.item.height * pgst.scalef 31 | anchors { 32 | left: parent.left 33 | right: parent.right 34 | } 35 | 36 | CoverArt { 37 | id: cover 38 | visible: !updating 39 | 40 | anchors { 41 | left: parent.left 42 | leftMargin: Constants.layout.padding * pgst.scalef 43 | verticalCenter: parent.verticalCenter 44 | } 45 | 46 | width: Constants.layout.coverart * pgst.scalef 47 | height: Constants.layout.coverart * pgst.scalef 48 | 49 | source: coverart 50 | text: title 51 | } 52 | 53 | PBusyIndicator { 54 | anchors.centerIn: cover 55 | visible: updating 56 | } 57 | 58 | PLabel { 59 | anchors { 60 | left: cover.right 61 | leftMargin: Constants.layout.padding * pgst.scalef 62 | rightMargin: Constants.layout.padding * pgst.scalef 63 | right: downloadsLabel.left 64 | verticalCenter: parent.verticalCenter 65 | } 66 | 67 | elide: Text.ElideRight 68 | text: title 69 | color: newEpisodes ? Constants.colors.fresh : Constants.colors.text 70 | } 71 | 72 | PLabel { 73 | id: downloadsLabel 74 | anchors { 75 | right: newEpisodesIndicator.enabled ? newEpisodesIndicator.left : parent.right 76 | rightMargin: Constants.layout.padding * pgst.scalef 77 | verticalCenter: parent.verticalCenter 78 | } 79 | 80 | text: downloaded ? downloaded : '' 81 | color: Constants.colors.text 82 | } 83 | 84 | RectangleIndicator { 85 | id: newEpisodesIndicator 86 | enabled: newEpisodes > 0 87 | color: Constants.colors.fresh 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /touch/PToolbarLabel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import 'common' 23 | 24 | import 'common/constants.js' as Constants 25 | import 'icons/icons.js' as Icons 26 | 27 | Item { 28 | id: toolbarLabel 29 | 30 | property string text: '' 31 | property bool firstLable: false 32 | 33 | onTextChanged: { 34 | state = (state === 'a') ? 'b': 'a'; 35 | if (state === 'a') { 36 | a.text = text; 37 | } else { 38 | b.text = text; 39 | } 40 | } 41 | 42 | states: [ 43 | State { 44 | name: 'a' 45 | PropertyChanges { target: a; opacity: 1; anchors.leftMargin: 0 } 46 | PropertyChanges { target: b; opacity: 0; anchors.leftMargin: 10 * pgst.scalef } 47 | }, 48 | State { 49 | name: 'b' 50 | PropertyChanges { target: a; opacity: 0; anchors.leftMargin: -10 * pgst.scalef } 51 | PropertyChanges { target: b; opacity: 1; anchors.leftMargin: 0 } 52 | } 53 | ] 54 | 55 | PLabel { 56 | id: a 57 | 58 | anchors { 59 | left: parent.left 60 | right: parent.right 61 | verticalCenter: parent.verticalCenter 62 | } 63 | color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText 64 | elide: Text.ElideRight 65 | 66 | Behavior on anchors.leftMargin { NumberAnimation { } } 67 | Behavior on opacity { NumberAnimation { } } 68 | } 69 | 70 | PLabel { 71 | id: b 72 | 73 | anchors { 74 | left: parent.left 75 | right: parent.right 76 | verticalCenter: parent.verticalCenter 77 | } 78 | color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText 79 | elide: Text.ElideRight 80 | 81 | Behavior on anchors.leftMargin { NumberAnimation { } } 82 | Behavior on opacity { NumberAnimation { } } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /touch/PSlider.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Item { 26 | id: slider 27 | 28 | property real value 29 | property real min: 0.0 30 | property real max: 1.0 31 | property real step: 0.0 32 | property color color: Constants.colors.highlight 33 | 34 | property real displayedValue: mouseArea.pressed ? temporaryValue : value 35 | property real temporaryValue 36 | 37 | signal valueChangeRequested(real newValue) 38 | 39 | clip: true 40 | 41 | height: 50 * pgst.scalef 42 | 43 | MouseArea { 44 | id: mouseArea 45 | anchors.fill: parent 46 | function updateValue(x) { 47 | if (x > width) { 48 | x = width; 49 | } else if (x < 0) { 50 | x = 0; 51 | } 52 | var v = (max - min) * (x / width); 53 | if (slider.step > 0.0) { 54 | v = slider.step * parseInt(0.5 + v / slider.step); 55 | } 56 | slider.temporaryValue = min + v; 57 | return slider.temporaryValue; 58 | } 59 | onClicked: slider.valueChangeRequested(updateValue(mouse.x)); 60 | onPressed: updateValue(mouse.x); 61 | onPositionChanged: updateValue(mouse.x); 62 | preventStealing: true 63 | } 64 | 65 | Rectangle { 66 | anchors.fill: parent 67 | color: slider.color 68 | opacity: .3 69 | } 70 | 71 | Rectangle { 72 | id: fillBackground 73 | color: slider.color 74 | height: parent.height 75 | width: parent.width * (parent.displayedValue - parent.min) / (parent.max - parent.min) 76 | 77 | anchors { 78 | verticalCenter: parent.verticalCenter 79 | } 80 | } 81 | 82 | Repeater { 83 | model: slider.step ? ((slider.max - slider.min) / slider.step - 1) : 0 84 | delegate: Rectangle { 85 | width: Math.max(1, 1 * pgst.scalef) 86 | height: parent.height 87 | color: Constants.colors.area 88 | x: parent.width * ((slider.step * (1 + index)) / (slider.max - slider.min)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /touch/SelectionDialog.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'icons/icons.js' as Icons 25 | 26 | Dialog { 27 | id: selectionDialog 28 | 29 | property string title: '' 30 | property var callback: undefined 31 | property var items: ([]) 32 | property var selectedIndex: -1 33 | 34 | contentHeight: selectionDialogFlickable.contentHeight 35 | 36 | Flickable { 37 | id: selectionDialogFlickable 38 | 39 | boundsBehavior: Flickable.StopAtBounds 40 | 41 | anchors.fill: parent 42 | contentHeight: contentColumn.height 43 | 44 | Column { 45 | id: contentColumn 46 | width: parent.width 47 | 48 | SlidePageHeader { 49 | id: header 50 | visible: title != '' 51 | color: Constants.colors.dialogHighlight 52 | title: selectionDialog.title 53 | } 54 | 55 | Repeater { 56 | model: selectionDialog.items 57 | 58 | delegate: ButtonArea { 59 | id: buttonArea 60 | 61 | color: Constants.colors.dialogArea 62 | width: parent.width 63 | height: 70 * pgst.scalef 64 | 65 | transparent: (index != selectionDialog.selectedIndex) 66 | 67 | PLabel { 68 | anchors { 69 | left: parent.left 70 | right: parent.right 71 | verticalCenter: parent.verticalCenter 72 | margins: 20 * pgst.scalef 73 | } 74 | 75 | text: modelData 76 | color: (index == selectionDialog.selectedIndex || buttonArea.pressed) ? Constants.colors.dialogHighlight : Constants.colors.dialogText 77 | font.pixelSize: 30 * pgst.scalef 78 | elide: Text.ElideRight 79 | } 80 | 81 | onClicked: { 82 | if (selectionDialog.callback !== undefined) { 83 | selectionDialog.callback(index, modelData); 84 | } 85 | selectionDialog.closePage(); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | PScrollDecorator { flickable: selectionDialogFlickable } 93 | } 94 | -------------------------------------------------------------------------------- /touch/AboutPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | SlidePage { 26 | id: page 27 | 28 | Flickable { 29 | id: flickable 30 | anchors.fill: parent 31 | boundsBehavior: Flickable.StopAtBounds 32 | 33 | contentWidth: detailColumn.width 34 | contentHeight: detailColumn.height + detailColumn.spacing 35 | 36 | Column { 37 | id: detailColumn 38 | 39 | width: page.width 40 | spacing: 15 * pgst.scalef 41 | 42 | SlidePageHeader { 43 | title: 'About gPodder' 44 | } 45 | 46 | Column { 47 | width: parent.width 48 | 49 | Item { height: 10 * pgst.scalef; width: 1 } 50 | 51 | PLabel { 52 | width: parent.width * .95 53 | font.pixelSize: 30 * pgst.scalef 54 | anchors.horizontalCenter: parent.horizontalCenter 55 | wrapMode: Text.WordWrap 56 | text: 'gPodder ' + py.uiversion 57 | color: Constants.colors.highlight 58 | } 59 | 60 | PLabel { 61 | width: parent.width * .95 62 | font.pixelSize: 20 * pgst.scalef 63 | anchors.horizontalCenter: parent.horizontalCenter 64 | wrapMode: Text.WordWrap 65 | text: 'http://gpodder.org/' 66 | color: Constants.colors.placeholder 67 | } 68 | } 69 | 70 | PLabel { 71 | width: parent.width * .95 72 | font.pixelSize: 30 * pgst.scalef 73 | anchors.horizontalCenter: parent.horizontalCenter 74 | wrapMode: Text.WordWrap 75 | text: [ 76 | '© 2005-2015 Thomas Perl and the gPodder Team', 77 | 'License: ISC / GPLv3 or later', 78 | 'Website: http://gpodder.org/', 79 | '', 80 | 'gPodder Core ' + py.coreversion, 81 | 'gPodder QML UI ' + py.uiversion, 82 | 'Podcastparser ' + py.parserversion, 83 | 'PyOtherSide ' + py.pluginVersion(), 84 | 'Python ' + py.pythonVersion() 85 | ].join('\n') 86 | } 87 | } 88 | } 89 | 90 | PScrollDecorator { flickable: flickable } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /touch/Dialog.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | Rectangle { 26 | id: page 27 | z: 200 28 | property bool activatedFromMenu: false 29 | property bool moveToTop: false 30 | property bool attachToToolbar: platform.toolbarOnTop && activatedFromMenu 31 | 32 | color: Constants.colors.dialogBackground 33 | 34 | Component.onCompleted: { 35 | pgst.dialogsVisible = pgst.dialogsVisible + 1; 36 | pgst.topOfStackChanged(); 37 | } 38 | 39 | Component.onDestruction: { 40 | pgst.dialogsVisible = pgst.dialogsVisible - 1; 41 | pgst.onDialogDismissed(page); 42 | } 43 | 44 | default property alias children: contents.children 45 | property bool isDialog: true 46 | property int contentHeight: -1 47 | property bool fullWidth: false 48 | 49 | function closePage() { 50 | stacking.startFadeOut(); 51 | } 52 | 53 | onXChanged: pgst.update(page, x) 54 | 55 | width: parent.width 56 | height: parent.height 57 | 58 | DialogStacking { id: stacking } 59 | 60 | MouseArea { 61 | anchors.fill: parent 62 | onClicked: page.closePage(); 63 | } 64 | 65 | MouseArea { 66 | // Tapping on the dialog contents should do nothing 67 | anchors.fill: contents 68 | } 69 | 70 | Rectangle { 71 | id: contents 72 | property int border: parent.width * 0.1 73 | width: parent.fullWidth ? parent.width : (parent.width - 2 * border) 74 | property int maxHeight: parent.height - toolbar.height 75 | height: ((page.contentHeight > 0 && page.contentHeight < maxHeight) ? page.contentHeight : maxHeight) * parent.opacity 76 | anchors { 77 | horizontalCenter: activatedFromMenu ? undefined : parent.horizontalCenter 78 | verticalCenter: (moveToTop || activatedFromMenu) ? undefined : parent.verticalCenter 79 | 80 | right: activatedFromMenu ? parent.right : undefined 81 | 82 | top: (moveToTop || (activatedFromMenu && platform.toolbarOnTop)) ? parent.top : undefined 83 | topMargin: (moveToTop || (activatedFromMenu && platform.toolbarOnTop)) ? pgst.bottomSpacing : 0 84 | 85 | bottom: (!moveToTop && activatedFromMenu && !platform.toolbarOnTop) ? parent.bottom : undefined 86 | bottomMargin: (!moveToTop && activatedFromMenu && !platform.toolbarOnTop) ? pgst.bottomSpacing : 0 87 | } 88 | color: Constants.colors.dialog 89 | clip: true 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /touch/Directory.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | 25 | SlidePage { 26 | id: page 27 | property string provider 28 | property bool can_search 29 | 30 | Component.onCompleted: { 31 | if (!page.can_search) { 32 | // Load static data 33 | search(''); 34 | } 35 | } 36 | 37 | function search(text) { 38 | loading.visible = true; 39 | directorySearchModel.search(text, function() { 40 | loading.visible = false; 41 | }); 42 | } 43 | 44 | ListView { 45 | id: listView 46 | 47 | anchors.fill: parent 48 | boundsBehavior: Flickable.StopAtBounds 49 | 50 | PScrollDecorator { flickable: listView } 51 | 52 | model: GPodderDirectorySearchModel { id: directorySearchModel; provider: page.provider } 53 | 54 | header: Column { 55 | anchors { 56 | left: parent.left 57 | right: parent.right 58 | } 59 | 60 | SlidePageHeader { title: page.provider } 61 | 62 | Column { 63 | visible: page.can_search 64 | 65 | spacing: 0.5 * 30 * pgst.scalef 66 | 67 | anchors { 68 | left: parent.left 69 | right: parent.right 70 | margins: 30 * pgst.scalef 71 | } 72 | 73 | PTextField { 74 | id: input 75 | width: parent.width 76 | placeholderText: 'Search term' 77 | onAccepted: page.search(input.text); 78 | } 79 | 80 | ButtonArea { 81 | id: button 82 | width: input.width 83 | height: input.height 84 | 85 | PLabel { 86 | anchors.centerIn: parent 87 | text: 'Search' 88 | } 89 | 90 | onClicked: page.search(input.text); 91 | } 92 | } 93 | } 94 | 95 | delegate: DirectoryItem { 96 | onClicked: { 97 | py.call('main.subscribe', [url], function () { 98 | page.closePage(); 99 | }); 100 | } 101 | } 102 | } 103 | 104 | PBusyIndicator { 105 | id: loading 106 | visible: false 107 | anchors.centerIn: parent 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /touch/Confirmation.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'icons/icons.js' as Icons 25 | 26 | Dialog { 27 | id: confirmation 28 | 29 | property alias title: header.title 30 | property alias description: description.text 31 | property alias affirmativeAction: affirmativeButton.text 32 | property alias negativeAction: negativeButton.text 33 | 34 | property string icon 35 | property alias color: header.color 36 | property var callback: undefined 37 | 38 | contentHeight: contentColumn.height 39 | 40 | Column { 41 | id: contentColumn 42 | width: parent.width 43 | 44 | SlidePageHeader { 45 | id: header 46 | color: Constants.colors.destructive 47 | title: 'Confirmation' 48 | } 49 | 50 | PLabel { 51 | id: description 52 | 53 | width: parent.width - 30 * pgst.scalef 54 | anchors.horizontalCenter: parent.horizontalCenter 55 | wrapMode: Text.WordWrap 56 | 57 | visible: text 58 | } 59 | 60 | Item { width: parent.width; height: 10 * pgst.scalef } 61 | 62 | Row { 63 | width: parent.width - 30 * pgst.scalef 64 | height: 80 * pgst.scalef 65 | spacing: 30 * pgst.scalef 66 | anchors.horizontalCenter: parent.horizontalCenter 67 | 68 | ConfirmationButton { 69 | id: affirmativeButton 70 | 71 | width: (parent.width - parent.spacing) * 2 / 3 72 | height: parent.height 73 | 74 | text: 'Yes' 75 | icon: confirmation.icon 76 | contentColor: header.color 77 | 78 | onClicked: { 79 | if (confirmation.callback !== undefined) { 80 | confirmation.callback(); 81 | confirmation.closePage(); 82 | } 83 | } 84 | } 85 | 86 | ConfirmationButton { 87 | id: negativeButton 88 | 89 | width: (parent.width - parent.spacing) * 1 / 3 90 | height: parent.height 91 | 92 | text: 'Cancel' 93 | contentColor: Constants.colors.placeholder 94 | 95 | onClicked: { 96 | confirmation.closePage(); 97 | } 98 | } 99 | } 100 | 101 | Item { width: parent.width; height: 30 * pgst.scalef } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /common/GPodderCore.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import io.thp.pyotherside 1.3 23 | 24 | 25 | Python { 26 | id: py 27 | 28 | property string progname: 'gpodder' 29 | property bool ready: false 30 | property bool refreshing: false 31 | 32 | property string coreversion 33 | property string uiversion 34 | property string parserversion 35 | 36 | signal downloadProgress(int episode_id, real progress) 37 | signal playbackProgress(int episode_id, real progress) 38 | signal podcastListChanged() 39 | signal updatingPodcast(int podcast_id) 40 | signal updatedPodcast(var podcast) 41 | signal episodeListChanged(int podcast_id) 42 | signal updatedEpisode(var episode) 43 | signal updateStats() 44 | signal configChanged(string key, var value) 45 | 46 | Component.onCompleted: { 47 | setHandler('hello', function (coreversion, uiversion, parserversion) { 48 | py.coreversion = coreversion; 49 | py.uiversion = uiversion; 50 | py.parserversion = parserversion; 51 | 52 | console.log('gPodder Core ' + py.coreversion); 53 | console.log('gPodder QML UI ' + py.uiversion); 54 | console.log('Podcastparser ' + py.parserversion); 55 | console.log('PyOtherSide ' + py.pluginVersion()); 56 | console.log('Python ' + py.pythonVersion()); 57 | }); 58 | 59 | setHandler('download-progress', py.downloadProgress); 60 | setHandler('playback-progress', py.playbackProgress); 61 | setHandler('podcast-list-changed', py.podcastListChanged); 62 | setHandler('updating-podcast', py.updatingPodcast); 63 | setHandler('updated-podcast', py.updatedPodcast); 64 | setHandler('refreshing', function(v) { py.refreshing = v; }); 65 | setHandler('episode-list-changed', py.episodeListChanged); 66 | setHandler('updated-episode', py.updatedEpisode); 67 | setHandler('update-stats', py.updateStats); 68 | setHandler('config-changed', py.configChanged); 69 | 70 | addImportPath(Qt.resolvedUrl('../..')); 71 | 72 | // Load the Python side of things 73 | importModule('main', function() { 74 | py.call('main.initialize', [py.progname], function() { 75 | py.ready = true; 76 | }); 77 | }); 78 | } 79 | 80 | function setConfig(key, value) { 81 | py.call('main.set_config_value', [key, value]); 82 | } 83 | 84 | function getConfig(key, callback) { 85 | py.call('main.get_config_value', [key], function (result) { 86 | callback(result); 87 | }); 88 | } 89 | 90 | onReceived: { 91 | console.log('unhandled message: ' + data); 92 | } 93 | 94 | onError: { 95 | console.log('Python failure: ' + traceback); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /touch/EpisodesPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | import 'common/util.js' as Util 25 | import 'common/constants.js' as Constants 26 | import 'icons/icons.js' as Icons 27 | 28 | SlidePage { 29 | id: page 30 | 31 | property int podcast_id 32 | property string title 33 | 34 | hasMenuButton: true 35 | menuButtonLabel: 'Settings' 36 | onMenuButtonClicked: { 37 | pgst.showSelection([ 38 | { 39 | label: 'Filter list (' + queryControl.currentFilter + ')', 40 | callback: function () { 41 | queryControl.showSelectionDialog(); 42 | } 43 | }, 44 | { 45 | label: 'Mark episodes as old', 46 | callback: function () { 47 | py.call('main.mark_episodes_as_old', [page.podcast_id]); 48 | }, 49 | }, 50 | { 51 | label: 'Enqueue episodes in player', 52 | callback: function () { 53 | var startPlayback = Util.atMostOnce(function () { 54 | if (!player.isPlaying) { 55 | player.jumpToQueueIndex(0); 56 | } 57 | }); 58 | 59 | episodeList.model.forEachEpisode(function (episode) { 60 | player.enqueueEpisode(episode.id, startPlayback); 61 | }); 62 | }, 63 | }, 64 | { 65 | label: 'Podcast details', 66 | callback: function () { 67 | pgst.loadPage('PodcastDetail.qml', {podcast_id: podcast_id, title: title}); 68 | } 69 | }, 70 | { 71 | label: 'Unsubscribe', 72 | callback: function () { 73 | var ctx = { py: py, id: page.podcast_id, page: page }; 74 | pgst.showConfirmation(title, 'Unsubscribe', 'Cancel', 'Remove this podcast and all downloaded episodes?', Icons.trash, function () { 75 | ctx.py.call('main.unsubscribe', [ctx.id]); 76 | ctx.page.closePage(); 77 | }); 78 | }, 79 | }, 80 | ], undefined, undefined, true); 81 | } 82 | 83 | 84 | Component.onCompleted: { 85 | episodeList.model.podcast_id = podcast_id; 86 | // List model will be loaded automatically on load 87 | } 88 | 89 | EpisodeQueryControl { 90 | id: queryControl 91 | model: episodeList.model 92 | title: 'Select filter' 93 | } 94 | 95 | EpisodeListView { 96 | id: episodeList 97 | title: page.title 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /touch/PTextField.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'icons/icons.js' as Icons 25 | 26 | Item { 27 | id: textField 28 | 29 | property alias text: textInput.text 30 | property string placeholderText: '' 31 | signal accepted 32 | 33 | function paste() { 34 | textInput.paste(); 35 | } 36 | 37 | height: 50 * pgst.scalef 38 | 39 | TextInput { 40 | id: textInput 41 | 42 | Component.onDestruction: { 43 | // Return keyboard focus to pgst 44 | pgst.focus = true; 45 | } 46 | 47 | anchors { 48 | verticalCenter: parent.verticalCenter 49 | left: parent.left 50 | right: clipboardIcon.left 51 | margins: 5 * pgst.scalef 52 | } 53 | clip: true 54 | 55 | inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText 56 | 57 | color: Constants.colors.text 58 | selectionColor: Constants.colors.background 59 | font.pixelSize: parent.height * 0.7 60 | font.family: placeholder.font.family 61 | focus: true 62 | onAccepted: textField.accepted() 63 | } 64 | 65 | PLabel { 66 | id: placeholder 67 | anchors.fill: textInput 68 | visible: (textInput.text == '') 69 | text: textField.placeholderText 70 | color: Constants.colors.placeholder 71 | font.pixelSize: textInput.font.pixelSize 72 | } 73 | 74 | IconMenuItem { 75 | id: clipboardIcon 76 | 77 | anchors { 78 | right: parent.right 79 | margins: 5 * pgst.scalef 80 | verticalCenter: parent.verticalCenter 81 | } 82 | 83 | icon: Icons.paperclip 84 | onClicked: { 85 | pgst.showSelection([ 86 | { 87 | label: 'Copy', 88 | callback: function () { 89 | textInput.copy(); 90 | } 91 | }, 92 | { 93 | label: 'Paste', 94 | callback: function() { 95 | textInput.paste(); 96 | } 97 | }, 98 | { 99 | label: 'Cut', 100 | callback: function() { 101 | textInput.cut(); 102 | } 103 | }, 104 | { 105 | label: 'Clear', 106 | callback: function() { 107 | textInput.text = ''; 108 | } 109 | }, 110 | { 111 | label: 'Select all', 112 | callback: function() { 113 | textInput.selectAll(); 114 | } 115 | } 116 | ]); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /touch/SettingsPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2015, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | 25 | SlidePage { 26 | id: page 27 | 28 | Component.onCompleted: { 29 | py.getConfig('plugins.youtube.api_key_v3', function (value) { 30 | youtube_api_key_v3.text = value; 31 | }); 32 | py.getConfig('limit.episodes', function (value) { 33 | limit_episodes.value = value; 34 | }); 35 | } 36 | 37 | Component.onDestruction: { 38 | py.setConfig('plugins.youtube.api_key_v3', youtube_api_key_v3.text); 39 | py.setConfig('limit.episodes', parseInt(limit_episodes.value)); 40 | } 41 | 42 | Flickable { 43 | id: flickable 44 | anchors.fill: parent 45 | boundsBehavior: Flickable.StopAtBounds 46 | 47 | contentWidth: detailColumn.width 48 | contentHeight: detailColumn.height + detailColumn.spacing 49 | 50 | Column { 51 | id: detailColumn 52 | 53 | width: page.width 54 | spacing: 15 * pgst.scalef 55 | 56 | SlidePageHeader { title: 'Settings' } 57 | 58 | SectionHeader { text: 'YouTube' } 59 | 60 | SettingsLabel { text: 'API Key (v3)' } 61 | 62 | PTextField { 63 | id: youtube_api_key_v3 64 | anchors { 65 | left: parent.left 66 | right: parent.right 67 | margins: Constants.layout.padding * pgst.scalef 68 | } 69 | } 70 | 71 | SectionHeader { text: 'Limits' } 72 | 73 | SettingsLabel { text: 'Maximum episodes per feed' } 74 | 75 | PSlider { 76 | id: limit_episodes 77 | min: 100 78 | step: 100 79 | max: 1000 80 | anchors { 81 | left: parent.left 82 | right: parent.right 83 | margins: Constants.layout.padding * pgst.scalef 84 | } 85 | onValueChangeRequested: { value = newValue; } 86 | } 87 | 88 | PLabel { 89 | text: parseInt(limit_episodes.displayedValue) 90 | anchors { 91 | left: parent.left 92 | right: parent.right 93 | margins: Constants.layout.padding * pgst.scalef 94 | } 95 | } 96 | 97 | SectionHeader { text: 'About' } 98 | 99 | ButtonArea { 100 | width: parent.width 101 | height: Constants.layout.item.height * pgst.scalef 102 | PLabel { 103 | anchors.centerIn: parent 104 | text: 'About gPodder ' + py.uiversion 105 | } 106 | onClicked: pgst.loadPage('AboutPage.qml') 107 | } 108 | } 109 | } 110 | 111 | PScrollDecorator { flickable: flickable } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /common/GPodderEpisodeListModel.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'util.js' as Util 24 | import 'constants.js' as Constants 25 | 26 | ListModel { 27 | id: episodeListModel 28 | 29 | property var podcast_id: -1 30 | 31 | property var queries: ({ 32 | All: '', 33 | Fresh: 'new or downloading', 34 | Downloaded: 'downloaded or downloading', 35 | UnplayedDownloads: 'downloaded and not played', 36 | FinishedDownloads: 'downloaded and finished', 37 | HideDeleted: 'not deleted', 38 | Deleted: 'deleted', 39 | ShortDownloads: 'downloaded and min > 0 and min < 10', 40 | }) 41 | 42 | property var filters: ([ 43 | { label: qsTr("All"), query: episodeListModel.queries.All }, 44 | { label: qsTr("Fresh"), query: episodeListModel.queries.Fresh }, 45 | { label: qsTr("Downloaded"), query: episodeListModel.queries.Downloaded }, 46 | { label: qsTr("Unplayed downloads"), query: episodeListModel.queries.UnplayedDownloads }, 47 | { label: qsTr("Finished downloads"), query: episodeListModel.queries.FinishedDownloads }, 48 | { label: qsTr("Hide deleted"), query: episodeListModel.queries.HideDeleted }, 49 | { label: qsTr("Deleted episodes"), query: episodeListModel.queries.Deleted }, 50 | { label: qsTr("Short downloads (< 10 min)"), query: episodeListModel.queries.ShortDownloads }, 51 | ]) 52 | 53 | property bool ready: false 54 | property int currentFilterIndex: -1 55 | property string currentCustomQuery: queries.All 56 | 57 | Component.onCompleted: { 58 | // Request filter, then load episodes 59 | py.call('main.get_config_value', ['ui.qml.episode_list.filter_eql'], function (result) { 60 | setQueryFromUpdate(result); 61 | reload(); 62 | }); 63 | } 64 | 65 | function forEachEpisode(callback) { 66 | // Go from bottom up (= chronological order) 67 | for (var i=count-1; i>=0; i--) { 68 | callback(get(i)); 69 | } 70 | } 71 | 72 | function setQueryIndex(index) { 73 | currentFilterIndex = index; 74 | py.call('main.set_config_value', ['ui.qml.episode_list.filter_eql', filters[currentFilterIndex].query]); 75 | } 76 | 77 | function setQueryFromUpdate(query) { 78 | setQueryEx(query, false); 79 | } 80 | 81 | function setQuery(query) { 82 | setQueryEx(query, true); 83 | } 84 | 85 | function setQueryEx(query, update) { 86 | for (var i=0; i 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'common/util.js' as Util 25 | import 'icons/icons.js' as Icons 26 | 27 | SlidePage { 28 | id: detailPage 29 | 30 | property int episode_id 31 | property string title 32 | property string link 33 | property bool ready: false 34 | property var chapters: ([]) 35 | 36 | hasMenuButton: detailPage.link != '' 37 | menuButtonIcon: Icons.link 38 | menuButtonLabel: 'Website' 39 | onMenuButtonClicked: Qt.openUrlExternally(detailPage.link) 40 | 41 | PBusyIndicator { 42 | anchors.centerIn: parent 43 | visible: !detailPage.ready 44 | } 45 | 46 | Component.onCompleted: { 47 | py.call('main.show_episode', [episode_id], function (episode) { 48 | detailPage.title = episode.title; 49 | descriptionLabel.text = episode.description; 50 | metadataLabel.text = episode.metadata; 51 | detailPage.link = episode.link; 52 | detailPage.ready = true; 53 | detailPage.chapters = episode.chapters; 54 | }); 55 | } 56 | 57 | Flickable { 58 | id: flickable 59 | anchors.fill: parent 60 | boundsBehavior: Flickable.StopAtBounds 61 | 62 | contentWidth: detailColumn.width 63 | contentHeight: detailColumn.height + detailColumn.spacing 64 | 65 | Column { 66 | id: detailColumn 67 | 68 | width: detailPage.width 69 | spacing: Constants.layout.padding * pgst.scalef 70 | 71 | Item { height: Constants.layout.padding * pgst.scalef; width: parent.width } 72 | 73 | Column { 74 | width: parent.width - 2 * Constants.layout.padding * pgst.scalef 75 | anchors.horizontalCenter: parent.horizontalCenter 76 | spacing: Constants.layout.padding * pgst.scalef 77 | 78 | SlidePageHeader { 79 | padding: 0 80 | title: detailPage.title 81 | width: parent.width 82 | wrapMode: Text.WordWrap 83 | color: Constants.colors.highlight 84 | } 85 | 86 | PLabel { 87 | id: metadataLabel 88 | width: parent.width 89 | wrapMode: Text.WordWrap 90 | font.pixelSize: 20 * pgst.scalef 91 | color: Constants.colors.placeholder 92 | } 93 | 94 | PExpander { 95 | visible: detailPage.chapters.length > 0 96 | 97 | width: parent.width 98 | expandedHeight: chaptersColumn.childrenRect.height 99 | 100 | Column { 101 | id: chaptersColumn 102 | width: parent.width 103 | 104 | PLabel { 105 | text: 'Chapters' 106 | color: Constants.colors.secondaryHighlight 107 | } 108 | 109 | Repeater { 110 | model: detailPage.chapters 111 | 112 | delegate: Column { 113 | width: parent.width 114 | 115 | PLabel { 116 | width: parent.width 117 | text: Util.formatDuration(modelData.start) 118 | font.pixelSize: 20 * pgst.scalef 119 | color: Constants.colors.secondaryHighlight 120 | } 121 | 122 | PLabel { 123 | width: parent.width 124 | text: modelData.title 125 | font.pixelSize: 20 * pgst.scalef 126 | color: Constants.colors.placeholder 127 | } 128 | } 129 | } 130 | } 131 | } 132 | 133 | PLabel { 134 | id: descriptionLabel 135 | width: parent.width 136 | font.pixelSize: 30 * pgst.scalef 137 | wrapMode: Text.WordWrap 138 | } 139 | } 140 | } 141 | } 142 | 143 | PScrollDecorator { flickable: flickable } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /touch/PodcastDetail.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'common/util.js' as Util 25 | import 'icons/icons.js' as Icons 26 | 27 | SlidePage { 28 | id: page 29 | 30 | property int podcast_id 31 | property string title 32 | property string description 33 | property string link 34 | property string section 35 | property string coverart 36 | property string url 37 | 38 | property bool ready: false 39 | 40 | hasMenuButton: true 41 | onMenuButtonClicked: { 42 | pgst.showSelection([ 43 | { 44 | label: 'Visit website', 45 | callback: function () { 46 | Qt.openUrlExternally(page.link); 47 | } 48 | }, 49 | { 50 | label: 'Copy feed URL', 51 | callback: function () { 52 | pgst.loadPage('TextInputDialog.qml', { 53 | placeholderText: 'Feed URL', 54 | text: page.url, 55 | }); 56 | } 57 | }, 58 | { 59 | label: 'Change section', 60 | callback: function () { 61 | var ctx = { py: py, id: page.podcast_id }; 62 | pgst.loadPage('TextInputDialog.qml', { 63 | buttonText: 'Change section', 64 | placeholderText: 'New section', 65 | text: section, 66 | callback: function (new_section) { 67 | ctx.py.call('main.change_section', [ctx.id, new_section]); 68 | } 69 | }); 70 | } 71 | }, 72 | ], undefined, undefined, true); 73 | } 74 | 75 | PBusyIndicator { 76 | anchors.centerIn: parent 77 | visible: !page.ready 78 | } 79 | 80 | Component.onCompleted: { 81 | py.call('main.show_podcast', [podcast_id], function (podcast) { 82 | page.title = podcast.title; 83 | page.description = podcast.description; 84 | page.link = podcast.link; 85 | page.section = podcast.section; 86 | page.coverart = podcast.coverart; 87 | page.url = podcast.url; 88 | page.ready = true; 89 | }); 90 | } 91 | 92 | Flickable { 93 | id: flickable 94 | anchors.fill: parent 95 | boundsBehavior: Flickable.StopAtBounds 96 | 97 | contentWidth: detailColumn.width 98 | contentHeight: detailColumn.height + detailColumn.spacing 99 | 100 | Column { 101 | id: detailColumn 102 | 103 | width: page.width 104 | spacing: Constants.layout.padding * pgst.scalef 105 | 106 | Item { height: Constants.layout.padding * pgst.scalef; width: parent.width } 107 | 108 | Column { 109 | width: parent.width - 2 * Constants.layout.padding * pgst.scalef 110 | anchors.horizontalCenter: parent.horizontalCenter 111 | spacing: Constants.layout.padding * pgst.scalef 112 | 113 | PExpander { 114 | width: parent.width 115 | expandedHeight: coverImage.height 116 | 117 | Image { 118 | id: coverImage 119 | source: page.coverart 120 | fillMode: Image.PreserveAspectFit 121 | width: parent.width 122 | } 123 | } 124 | 125 | SlidePageHeader { 126 | title: page.title 127 | width: parent.width 128 | wrapMode: Text.WordWrap 129 | color: Constants.colors.highlight 130 | } 131 | 132 | PLabel { 133 | visible: text !== '' 134 | text: page.link 135 | width: parent.width 136 | wrapMode: Text.WordWrap 137 | font.pixelSize: 20 * pgst.scalef 138 | color: Constants.colors.placeholder 139 | } 140 | 141 | PLabel { 142 | text: 'Section: ' + page.section 143 | width: parent.width 144 | wrapMode: Text.WordWrap 145 | font.pixelSize: 20 * pgst.scalef 146 | color: Constants.colors.placeholder 147 | } 148 | 149 | PLabel { 150 | text: page.description 151 | width: parent.width 152 | font.pixelSize: 30 * pgst.scalef 153 | wrapMode: Text.WordWrap 154 | } 155 | } 156 | } 157 | } 158 | 159 | PScrollDecorator { flickable: flickable } 160 | } 161 | -------------------------------------------------------------------------------- /touch/PodcastsPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | import 'common/util.js' as Util 25 | import 'icons/icons.js' as Icons 26 | import 'common/constants.js' as Constants 27 | 28 | SlidePage { 29 | id: page 30 | 31 | canClose: false 32 | 33 | hasMenuButton: true 34 | menuButtonLabel: 'Settings' 35 | onMenuButtonClicked: { 36 | pgst.showSelection([ 37 | { 38 | label: 'Check for new episodes', 39 | callback: function () { 40 | py.call('main.check_for_episodes'); 41 | } 42 | }, 43 | { 44 | label: 'Filter episodes', 45 | callback: function () { 46 | pgst.loadPage('EpisodeQueryPage.qml'); 47 | } 48 | }, 49 | { 50 | label: 'Settings', 51 | callback: function () { 52 | pgst.loadPage('SettingsPage.qml'); 53 | }, 54 | }, 55 | { 56 | label: 'Add new podcast', 57 | callback: function () { 58 | var ctx = { py: py }; 59 | pgst.loadPage('TextInputDialog.qml', { 60 | buttonText: 'Subscribe', 61 | placeholderText: 'Feed URL', 62 | pasteOnLoad: true, 63 | callback: function (url) { 64 | ctx.py.call('main.subscribe', [url]); 65 | } 66 | }); 67 | }, 68 | }, 69 | { 70 | label: 'Add from OPML', 71 | callback: function () { 72 | var ctx = { py: py }; 73 | pgst.loadPage('TextInputDialog.qml', { 74 | buttonText: 'Subscribe', 75 | placeholderText: 'OPML URL', 76 | pasteOnLoad: true, 77 | callback: function (url) { 78 | ctx.py.call('main.import_opml', [url]); 79 | } 80 | }); 81 | }, 82 | }, 83 | { 84 | label: 'Discover new podcasts', 85 | callback: function () { 86 | py.call('main.get_directory_providers', [], function (result) { 87 | var items = []; 88 | for (var i=0; i 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common/constants.js' as Constants 24 | import 'common/util.js' as Util 25 | import 'icons/icons.js' as Icons 26 | 27 | Item { 28 | id: episodeItem 29 | property bool opened: episodeList.selectedIndex == index 30 | property bool isPlaying: ((player.episode == id) && player.isPlaying) 31 | 32 | width: parent.width 33 | height: (opened ? 2 : 1) * Constants.layout.item.height * pgst.scalef 34 | Behavior on height { PropertyAnimation { duration: 100 } } 35 | 36 | Item { 37 | clip: true 38 | anchors { 39 | left: parent.left 40 | right: parent.right 41 | bottom: parent.bottom 42 | } 43 | height: parent.height - 80 * pgst.scalef 44 | 45 | IconContextMenu { 46 | height: parent.height 47 | width: parent.width 48 | 49 | IconMenuItem { 50 | text: episodeItem.isPlaying ? 'Pause' : 'Play' 51 | color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.playback 52 | icon: episodeItem.isPlaying ? Icons.pause : Icons.play 53 | onClicked: { 54 | if (episodeItem.isPlaying) { 55 | player.pause(); 56 | } else { 57 | player.playbackEpisode(id); 58 | } 59 | } 60 | 61 | onPressAndHold: { 62 | player.enqueueEpisode(id, function () { 63 | if (!player.isPlaying) { 64 | player.jumpToQueueIndex(0); 65 | } 66 | pgst.loadPage('PlayerPage.qml'); 67 | }); 68 | } 69 | } 70 | 71 | IconMenuItem { 72 | text: 'Download' 73 | color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.download 74 | icon: Icons.cloud_download 75 | enabled: downloadState != Constants.state.downloaded 76 | onClicked: { 77 | episodeList.selectedIndex = -1; 78 | py.call('main.download_episode', [id]); 79 | } 80 | } 81 | 82 | IconMenuItem { 83 | text: 'Delete' 84 | color: (episodeItem.isPlaying || progress > 0) ? titleLabel.color : Constants.colors.destructive 85 | icon: Icons.trash 86 | enabled: downloadState != Constants.state.deleted 87 | onClicked: { 88 | var ctx = { py: py, id: id }; 89 | pgst.showConfirmation(title, 'Delete', 'Cancel', 'Delete this episode?', Icons.trash, function () { 90 | ctx.py.call('main.delete_episode', [ctx.id]); 91 | }); 92 | } 93 | } 94 | 95 | IconMenuItem { 96 | id: toggleNew 97 | color: (episodeItem.isPlaying || isNew || progress > 0) ? titleLabel.color : Constants.colors.text 98 | text: 'Toggle New' 99 | icon: Icons.star 100 | onClicked: Util.disableUntilReturn(toggleNew, py, 'main.toggle_new', [id]); 101 | } 102 | 103 | IconMenuItem { 104 | text: 'Shownotes' 105 | color: titleLabel.color 106 | icon: Icons.article 107 | onClicked: pgst.loadPage('EpisodeDetail.qml', {episode_id: id, title: title}); 108 | } 109 | } 110 | } 111 | 112 | ButtonArea { 113 | id: episodeItemArea 114 | 115 | opacity: (canHighlight || episodeList.selectedIndex == index) ? 1 : 0.2 116 | canHighlight: (episodeList.selectedIndex == -1) 117 | 118 | onClicked: { 119 | if (episodeList.selectedIndex == index) { 120 | episodeList.selectedIndex = -1; 121 | } else if (episodeList.selectedIndex != -1) { 122 | episodeList.selectedIndex = -1; 123 | } else { 124 | episodeList.selectedIndex = index; 125 | } 126 | } 127 | 128 | Rectangle { 129 | anchors.fill: parent 130 | color: titleLabel.color 131 | visible: (progress > 0) || isPlaying || episodeItem.opened 132 | opacity: 0.1 133 | } 134 | 135 | Rectangle { 136 | anchors { 137 | top: parent.top 138 | left: parent.left 139 | } 140 | 141 | height: Constants.layout.padding * pgst.scalef 142 | width: parent.width * progress 143 | color: Constants.colors.download 144 | } 145 | 146 | Rectangle { 147 | anchors { 148 | bottom: parent.bottom 149 | left: parent.left 150 | } 151 | 152 | height: Constants.layout.padding * pgst.scalef 153 | width: parent.width * playbackProgress 154 | color: titleLabel.color 155 | opacity: episodeItem.isPlaying ? 1 : .2 156 | } 157 | 158 | transparent: true 159 | height: Constants.layout.item.height * pgst.scalef 160 | 161 | anchors { 162 | left: parent.left 163 | right: parent.right 164 | } 165 | 166 | RectangleIndicator { 167 | id: downloadIndicator 168 | enabled: downloadState == Constants.state.downloaded 169 | color: titleLabel.color 170 | } 171 | 172 | Column { 173 | anchors { 174 | left: parent.left 175 | leftMargin: Constants.layout.padding * pgst.scalef 176 | right: downloadIndicator.left 177 | rightMargin: Constants.layout.padding * pgst.scalef 178 | verticalCenter: parent.verticalCenter 179 | } 180 | 181 | PLabel { 182 | id: titleLabel 183 | 184 | anchors { 185 | left: parent.left 186 | right: parent.right 187 | } 188 | 189 | elide: Text.ElideRight 190 | text: title 191 | 192 | color: { 193 | if (episodeItem.isPlaying) { 194 | return Constants.colors.playback; 195 | } else if (progress > 0) { 196 | return Constants.colors.download; 197 | } else if (episodeItem.opened) { 198 | return Constants.colors.highlight; 199 | } else if (isNew) { 200 | return Constants.colors.fresh; 201 | } else { 202 | return Constants.colors.text; 203 | } 204 | } 205 | 206 | opacity: { 207 | if (downloadState == Constants.state.deleted && !isNew && progress <= 0) { 208 | return 0.3; 209 | } else { 210 | return 1.0; 211 | } 212 | } 213 | } 214 | 215 | PLabel { 216 | id: subtitleLabel 217 | 218 | anchors { 219 | left: titleLabel.left 220 | right: titleLabel.right 221 | } 222 | 223 | text: subtitle 224 | textFormat: Text.PlainText 225 | font.pixelSize: 20 * pgst.scalef 226 | 227 | visible: subtitle !== '' 228 | elide: Text.ElideRight 229 | opacity: titleLabel.opacity 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /common/GPodderPlayback.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, 2014, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import QtMultimedia 5.0 23 | 24 | MediaPlayer { 25 | id: player 26 | 27 | property int episode: 0 28 | property string episode_title: '' 29 | property var episode_chapters: ([]) 30 | property string podcast_title: '' 31 | property string cover_art: '' 32 | property string episode_art: '' 33 | signal playerCreated() 34 | 35 | property var queue: ([]) 36 | signal queueUpdated() 37 | property bool isPlaying: playbackState == MediaPlayer.PlayingState 38 | property bool isPaused: playbackState == MediaPlayer.PausedState 39 | property bool isStopped: playbackState == MediaPlayer.StoppedState 40 | 41 | property bool inhibitPositionEvents: false 42 | property bool seekAfterPlay: false 43 | property int seekTargetSeconds: 0 44 | property int lastPosition: 0 45 | property int lastDuration: 0 46 | property int playedFrom: 0 47 | 48 | property var androidConnections: Connections { 49 | target: platform.android ? gpodderAndroid : null 50 | 51 | onAudioBecomingNoisy: { 52 | if (playbackState === MediaPlayer.PlayingState) { 53 | pause(); 54 | } 55 | } 56 | } 57 | 58 | function togglePause() { 59 | if (playbackState === MediaPlayer.PlayingState) { 60 | pause(); 61 | } else if (playbackState === MediaPlayer.PausedState) { 62 | play(); 63 | } 64 | } 65 | 66 | function enqueueEpisode(episode_id, callback) { 67 | py.call('main.show_episode', [episode_id], function (episode) { 68 | if (episode_id != player.episode && !queue.some(function (queued) { 69 | return queued.episode_id === episode_id; 70 | })) { 71 | queue.push({ 72 | episode_id: episode_id, 73 | title: episode.title, 74 | }); 75 | queueUpdated(); 76 | } 77 | 78 | if (callback !== undefined) { 79 | callback(); 80 | } 81 | }); 82 | } 83 | 84 | function clearQueue() { 85 | while (queue.length) { 86 | queue.shift() 87 | } 88 | queueUpdated() 89 | } 90 | 91 | function playbackEpisode(episode_id) { 92 | if (episode == episode_id) { 93 | // If the episode is already loaded, just start playing 94 | play(); 95 | return; 96 | } 97 | 98 | // First, make sure we stop any seeking / position update events 99 | sendPositionToCore(lastPosition); 100 | player.inhibitPositionEvents = true; 101 | player.stop(); 102 | 103 | py.call('main.play_episode', [episode_id], function (episode) { 104 | if (episode.video) { 105 | player.inhibitPositionEvents = false; 106 | Qt.openUrlExternally(episode.source); 107 | return; 108 | } 109 | 110 | // Load media / prepare and start playback 111 | var old_episode = player.episode; 112 | player.episode = episode_id; 113 | player.episode_title = episode.title; 114 | player.episode_chapters = episode.chapters; 115 | player.podcast_title = episode.podcast_title; 116 | player.cover_art = episode.cover_art; 117 | player.episode_art = episode.episode_art; 118 | var source = episode.source; 119 | if (source.indexOf('/') === 0) { 120 | player.source = 'file://' + source; 121 | } else { 122 | player.source = source; 123 | } 124 | player.seekTargetSeconds = episode.position; 125 | seekAfterPlay = true; 126 | 127 | // Notify interested parties that the player is now active 128 | if (old_episode == 0) { 129 | player.playerCreated(); 130 | } 131 | 132 | player.play(); 133 | }); 134 | } 135 | 136 | function seekAndSync(target_position) { 137 | sendPositionToCore(lastPosition); 138 | seek(target_position); 139 | playedFrom = target_position; 140 | savePlaybackAfterStopTimer.restart(); 141 | } 142 | 143 | onPlaybackStateChanged: { 144 | if (playbackState == MediaPlayer.PlayingState) { 145 | if (!seekAfterPlay) { 146 | player.playedFrom = position; 147 | } 148 | } else { 149 | sendPositionToCore(lastPosition); 150 | savePlaybackAfterStopTimer.restart(); 151 | } 152 | } 153 | 154 | function flushToDisk() { 155 | py.call('main.save_playback_state', []); 156 | } 157 | 158 | property var durationChoices: ([5, 15, 30, 45, 60]) 159 | 160 | function startSleepTimer(seconds) { 161 | sleepTimer.running = false; 162 | sleepTimer.secondsRemaining = seconds; 163 | sleepTimer.running = true; 164 | } 165 | 166 | function stopSleepTimer() { 167 | sleepTimer.running = false; 168 | sleepTimer.secondsRemaining = 0; 169 | } 170 | 171 | property bool sleepTimerRunning: sleepTimer.running 172 | property int sleepTimerRemaining: sleepTimer.secondsRemaining 173 | 174 | property var sleepTimer: Timer { 175 | property int secondsRemaining: 0 176 | 177 | interval: 1000 178 | repeat: true 179 | onTriggered: { 180 | secondsRemaining -= 1; 181 | 182 | if (secondsRemaining <= 0) { 183 | player.pause(); 184 | running = false; 185 | } 186 | } 187 | } 188 | 189 | property var nextInQueueTimer: Timer { 190 | interval: 500 191 | 192 | repeat: false 193 | 194 | onTriggered: { 195 | if (queue.length > 0) { 196 | playbackEpisode(queue.shift().episode_id); 197 | player.queueUpdated(); 198 | } 199 | } 200 | } 201 | 202 | function jumpToQueueIndex(index) { 203 | playbackEpisode(removeQueueIndex(index).episode_id); 204 | } 205 | 206 | function removeQueueIndex(index) { 207 | var result = queue.splice(index, 1)[0]; 208 | player.queueUpdated(); 209 | return result; 210 | } 211 | 212 | onStatusChanged: { 213 | if (status === MediaPlayer.EndOfMedia) { 214 | nextInQueueTimer.start(); 215 | } 216 | } 217 | 218 | property var savePlaybackPositionTimer: Timer { 219 | // Save position every minute during playback 220 | interval: 60 * 1000 221 | repeat: true 222 | running: player.isPlaying 223 | onTriggered: player.flushToDisk(); 224 | } 225 | 226 | property var savePlaybackAfterStopTimer: Timer { 227 | // Save position shortly after every seek and pause event 228 | interval: 5 * 1000 229 | repeat: false 230 | onTriggered: player.flushToDisk(); 231 | } 232 | 233 | property var seekAfterPlayTimer: Timer { 234 | interval: 100 235 | repeat: true 236 | running: player.isPlaying && player.seekAfterPlay 237 | 238 | onTriggered: { 239 | var targetPosition = player.seekTargetSeconds * 1000; 240 | if (Math.abs(player.position - targetPosition) < 10 * interval) { 241 | // We have seeked properly 242 | player.inhibitPositionEvents = false; 243 | player.seekAfterPlay = false; 244 | } else { 245 | // Try to seek to the target position 246 | player.seek(targetPosition); 247 | player.playedFrom = targetPosition; 248 | } 249 | } 250 | } 251 | 252 | function sendPositionToCore(positionToSend) { 253 | if (episode != 0 && !inhibitPositionEvents) { 254 | var begin = playedFrom / 1000; 255 | var end = positionToSend / 1000; 256 | var duration = ((lastDuration > 0) ? lastDuration : 0) / 1000; 257 | var diff = end - begin; 258 | 259 | // Only send playback events if they are 2 seconds or longer 260 | // (all other events might just be seeking events or wrong ones) 261 | if (diff >= 2) { 262 | py.call('main.report_playback_event', [episode, begin, end, duration]); 263 | } 264 | } 265 | } 266 | 267 | onPositionChanged: { 268 | if (isPlaying && !inhibitPositionEvents) { 269 | lastPosition = position; 270 | lastDuration = duration; 271 | 272 | // Directly update the playback progress in the episode list 273 | py.playbackProgress(episode, position / duration); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /touch/PlayerPage.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | 23 | import 'common' 24 | import 'common/util.js' as Util 25 | import 'common/constants.js' as Constants 26 | import 'icons/icons.js' as Icons 27 | 28 | SlidePage { 29 | id: page 30 | 31 | Component.onCompleted: pgst.havePlayer = true; 32 | Component.onDestruction: pgst.havePlayer = false; 33 | 34 | Flickable { 35 | id: flickable 36 | anchors.fill: parent 37 | boundsBehavior: Flickable.StopAtBounds 38 | 39 | contentWidth: column.width 40 | contentHeight: column.height + column.spacing 41 | 42 | Column { 43 | id: column 44 | 45 | width: flickable.width 46 | spacing: 10 * pgst.scalef 47 | 48 | Column { 49 | anchors { 50 | left: parent.left 51 | right: parent.right 52 | margins: 30 * pgst.scalef 53 | } 54 | 55 | SlidePageHeader { 56 | title: 'Now playing' 57 | width: parent.width 58 | } 59 | 60 | Item { width: parent.width; height: 20 * pgst.scalef } 61 | 62 | PLabel { 63 | anchors { 64 | left: parent.left 65 | right: parent.right 66 | } 67 | text: player.episode_title 68 | elide: Text.ElideRight 69 | color: Constants.colors.dialogText 70 | } 71 | 72 | PLabel { 73 | anchors { 74 | left: parent.left 75 | right: parent.right 76 | } 77 | text: player.podcast_title 78 | elide: Text.ElideRight 79 | color: Constants.colors.dialogText 80 | } 81 | } 82 | 83 | PLabel { 84 | anchors.horizontalCenter: parent.horizontalCenter 85 | text: Util.formatPosition(slider.displayedValue/1000, player.duration/1000) 86 | color: Constants.colors.dialogText 87 | } 88 | 89 | PSlider { 90 | id: slider 91 | width: flickable.width 92 | value: player.position 93 | min: 0 94 | max: player.duration 95 | color: Constants.colors.playback 96 | onValueChangeRequested: { 97 | player.seekAndSync(newValue); 98 | } 99 | } 100 | 101 | IconContextMenu { 102 | width: parent.width 103 | 104 | IconMenuItem { 105 | text: player.isPlaying ? 'Pause' : 'Play' 106 | color: Constants.colors.playback 107 | icon: player.isPlaying ? Icons.pause : Icons.play 108 | onClicked: { 109 | if (player.isPlaying) { 110 | player.pause(); 111 | } else { 112 | player.play(); 113 | } 114 | } 115 | } 116 | 117 | IconMenuItem { 118 | text: '-1m' 119 | color: Constants.colors.playback 120 | icon: Icons.first 121 | GPodderAutoFire { 122 | running: parent.pressed 123 | onFired: player.seekAndSync(player.position - 60 * 1000) 124 | } 125 | } 126 | 127 | IconMenuItem { 128 | text: '-10s' 129 | color: Constants.colors.playback 130 | icon: Icons.arrow_left 131 | GPodderAutoFire { 132 | running: parent.pressed 133 | onFired: player.seekAndSync(player.position - 10 * 1000) 134 | } 135 | } 136 | 137 | IconMenuItem { 138 | text: '+10s' 139 | color: Constants.colors.playback 140 | icon: Icons.arrow_right 141 | GPodderAutoFire { 142 | running: parent.pressed 143 | onFired: player.seekAndSync(player.position + 10 * 1000) 144 | } 145 | } 146 | 147 | IconMenuItem { 148 | text: '+1m' 149 | color: Constants.colors.playback 150 | icon: Icons.last 151 | GPodderAutoFire { 152 | running: parent.pressed 153 | onFired: player.seekAndSync(player.position + 60 * 1000) 154 | } 155 | } 156 | 157 | IconMenuItem { 158 | text: player.sleepTimerRunning ? Util.formatDuration(player.sleepTimerRemaining) : 'Sleep' 159 | alwaysShowText: player.sleepTimerRunning 160 | color: Constants.colors.playback 161 | icon: Icons.sleep 162 | onClicked: { 163 | if (player.sleepTimerRunning) { 164 | player.stopSleepTimer(); 165 | } else { 166 | var options = []; 167 | var durations_minutes = player.durationChoices; 168 | for (var i=0; i 0 188 | onClicked: { 189 | var items = []; 190 | 191 | for (var i in player.episode_chapters) { 192 | (function (items, chapter) { 193 | items.push({ 194 | label: chapter.title + ' (' + Util.formatDuration(chapter.start) + ')', 195 | callback: function () { 196 | player.seekAndSync(chapter.start * 1000); 197 | } 198 | }); 199 | })(items, player.episode_chapters[i]); 200 | } 201 | 202 | pgst.showSelection(items, 'Chapters'); 203 | } 204 | } 205 | } 206 | 207 | SectionHeader { 208 | text: 'Play queue' 209 | visible: playQueueRepeater.count > 0 210 | width: parent.width 211 | } 212 | 213 | Repeater { 214 | id: playQueueRepeater 215 | model: player.queue 216 | 217 | property var queueConnections: Connections { 218 | target: player 219 | 220 | onQueueUpdated: { 221 | playQueueRepeater.model = player.queue; 222 | } 223 | } 224 | 225 | ButtonArea { 226 | height: Constants.layout.item.height * pgst.scalef 227 | width: parent.width 228 | transparent: true 229 | 230 | PLabel { 231 | anchors { 232 | left: parent.left 233 | right: parent.right 234 | margins: Constants.layout.padding * pgst.scalef 235 | verticalCenter: parent.verticalCenter 236 | } 237 | 238 | text: modelData.title 239 | elide: Text.ElideRight 240 | } 241 | 242 | onClicked: { 243 | player.jumpToQueueIndex(index); 244 | } 245 | 246 | onPressAndHold: { 247 | pgst.showSelection([ 248 | { 249 | label: 'Shownotes', 250 | callback: function () { 251 | pgst.loadPage('EpisodeDetail.qml', { 252 | episode_id: modelData.episode_id, 253 | title: modelData.title 254 | }); 255 | }, 256 | }, 257 | { 258 | label: 'Remove from queue', 259 | callback: function () { 260 | player.removeQueueIndex(index); 261 | }, 262 | }, 263 | ]); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | 270 | PScrollDecorator { flickable: flickable } 271 | } 272 | -------------------------------------------------------------------------------- /touch/Main.qml: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * gPodder QML UI Reference Implementation 5 | * Copyright (c) 2013, Thomas Perl 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 12 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 14 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 16 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | */ 20 | 21 | import QtQuick 2.0 22 | import 'common' 23 | 24 | import 'common/util.js' as Util 25 | import 'common/constants.js' as Constants 26 | import 'icons/icons.js' as Icons 27 | 28 | Item { 29 | id: pgst 30 | 31 | GPodderCore { id: py } 32 | GPodderPlayback { id: player } 33 | GPodderPlatform { id: platform } 34 | 35 | GPodderPodcastListModel { id: podcastListModel } 36 | GPodderPodcastListModelConnections {} 37 | 38 | Keys.onPressed: { 39 | switch (event.key) { 40 | case Qt.Key_Space: 41 | player.togglePause(); 42 | break; 43 | case Qt.Key_Q: 44 | player.seekAndSync(player.position - 60 * 1000); 45 | break; 46 | case Qt.Key_W: 47 | player.seekAndSync(player.position - 10 * 1000); 48 | break; 49 | case Qt.Key_O: 50 | player.seekAndSync(player.position + 10 * 1000); 51 | break; 52 | case Qt.Key_P: 53 | player.seekAndSync(player.position + 60 * 1000); 54 | break; 55 | case Qt.Key_Escape: 56 | case Qt.Key_Backspace: 57 | case Qt.Key_Back: 58 | if (backButton.enabled) { 59 | backButton.clicked(); 60 | event.accepted = true; 61 | } 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | // Initial focus 69 | focus: true 70 | 71 | property real scalef: (width < height) ? (width / 480) : (height / 480) 72 | property int shorterSide: (width < height) ? width : height 73 | property int dialogsVisible: 0 74 | 75 | anchors.fill: parent 76 | 77 | function update(page, x) { 78 | var index = -1; 79 | for (var i=0; i 50) { 304 | if (throbber.enabled) { 305 | throbber.clicked(); 306 | } 307 | } 308 | } 309 | } 310 | } 311 | 312 | Rectangle { 313 | z: 190 314 | color: Constants.colors.playback 315 | visible: platform.floatingPlayButton && !pgst.havePlayer 316 | 317 | Behavior on opacity { NumberAnimation { } } 318 | opacity: (player.episode != 0) ? (player.isPlaying ? 1 : .5) : 0 319 | 320 | width: Constants.layout.item.height * 1.1 * pgst.scalef 321 | height: width 322 | radius: height / 2 323 | 324 | anchors { 325 | right: parent.right 326 | margins: Constants.layout.padding * 2 * pgst.scalef 327 | } 328 | 329 | y: pgst.height - height - anchors.margins 330 | 331 | PIcon { 332 | id: icon 333 | anchors.centerIn: parent 334 | icon: Icons.headphones 335 | size: 60 336 | color: Constants.colors.inverted.toolbarText 337 | } 338 | 339 | MouseArea { 340 | anchors.fill: parent 341 | onClicked: loadPage('PlayerPage.qml'); 342 | drag { 343 | target: parent 344 | axis: Drag.YAxis 345 | minimumY: pgst.bottomSpacing + parent.anchors.margins 346 | maximumY: pgst.height - parent.height - parent.anchors.margins 347 | } 348 | } 349 | } 350 | 351 | PodcastsPage { 352 | visible: py.ready 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /desktop/gpodder.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Dialogs 1.2 5 | 6 | import 'dialogs' 7 | 8 | import 'common' 9 | import 'common/util.js' as Util 10 | import 'common/constants.js' as Constants 11 | 12 | ApplicationWindow { 13 | id: appWindow 14 | 15 | width: 500 16 | height: 400 17 | 18 | title: 'gPodder' 19 | 20 | GPodderCore { 21 | id: py 22 | } 23 | 24 | function openDialog(filename, callback) { 25 | var component = Qt.createComponent(filename); 26 | 27 | function createDialog() { 28 | if (component.status === Component.Ready) { 29 | var dialog = component.createObject(appWindow, {}); 30 | dialog.visible = true; 31 | callback(dialog); 32 | } 33 | } 34 | 35 | if (component.status == Component.Ready) { 36 | createDialog(); 37 | } else { 38 | component.statusChanged.connect(createDialog); 39 | } 40 | } 41 | 42 | menuBar: MenuBar { 43 | Menu { 44 | title: 'File' 45 | 46 | MenuItem { 47 | text: 'Add podcast' 48 | onTriggered: { 49 | openDialog('dialogs/AddPodcastDialog.qml', function (dialog) { 50 | dialog.addUrl.connect(function (url) { 51 | py.call('main.subscribe', [url]); 52 | }); 53 | }); 54 | } 55 | } 56 | 57 | MenuItem { 58 | text: 'Add from OPML' 59 | onTriggered: { 60 | openDialog('dialogs/AddPodcastDialog.qml', function(dialog) { 61 | dialog.title = "Add from OPML" 62 | dialog.labelText = "OPML URL:" 63 | dialog.addUrl.connect(function (url) { 64 | py.call('main.import_opml', [url]); 65 | }) 66 | }) 67 | } 68 | } 69 | 70 | MenuItem { 71 | text: 'Quit' 72 | onTriggered: Qt.quit() 73 | } 74 | } 75 | } 76 | 77 | SplitView { 78 | anchors.fill: parent 79 | 80 | ColumnLayout { 81 | TableView { 82 | Layout.fillHeight: true 83 | Layout.fillWidth: true 84 | 85 | id: podcastListView 86 | 87 | model: GPodderPodcastListModel { id: podcastListModel } 88 | GPodderPodcastListModelConnections {} 89 | headerVisible: false 90 | alternatingRowColors: false 91 | horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff 92 | 93 | Menu { 94 | id: podcastContextMenu 95 | 96 | MenuItem { 97 | text: 'Unsubscribe' 98 | onTriggered: { 99 | var podcast_id = podcastListModel.get(podcastListView.currentRow).id; 100 | py.call('main.unsubscribe', [podcast_id]); 101 | } 102 | } 103 | 104 | MenuItem { 105 | text: 'Mark episodes as old' 106 | onTriggered: { 107 | var podcast_id = podcastListModel.get(podcastListView.currentRow).id; 108 | py.call('main.mark_episodes_as_old', [podcast_id]); 109 | } 110 | } 111 | } 112 | 113 | rowDelegate: Rectangle { 114 | height: 40 115 | color: styleData.selected ? Constants.colors.select : 'transparent' 116 | 117 | MouseArea { 118 | acceptedButtons: Qt.RightButton 119 | anchors.fill: parent 120 | onClicked: podcastContextMenu.popup() 121 | } 122 | } 123 | 124 | TableViewColumn { 125 | id: coverartColumn 126 | width: 40 127 | 128 | role: 'coverart' 129 | title: 'Image' 130 | delegate: Item { 131 | height: 32 132 | width: 32 133 | Image { 134 | mipmap: true 135 | source: styleData.value 136 | width: 32 137 | height: 32 138 | anchors.centerIn: parent 139 | } 140 | } 141 | } 142 | 143 | TableViewColumn { 144 | role: 'title' 145 | title: 'Podcast' 146 | width: podcastListView.width - coverartColumn.width - indicatorColumn.width - 2 * 5 147 | 148 | delegate: Item { 149 | property var row: podcastListModel.get(styleData.row) 150 | 151 | width: parent.width 152 | height: 40 153 | 154 | Column { 155 | anchors.verticalCenter: parent.verticalCenter 156 | anchors.left: parent.left 157 | anchors.right: parent.right 158 | spacing: -5 159 | 160 | Text { 161 | width: parent.width 162 | color: styleData.selected ? 'white' : 'black' 163 | 164 | font.bold: row.newEpisodes 165 | text: styleData.value 166 | elide: styleData.elideMode 167 | } 168 | 169 | Text { 170 | width: parent.width 171 | font.pointSize: 10 172 | color: styleData.selected ? 'white' : 'black' 173 | 174 | text: row.description 175 | elide: styleData.elideMode 176 | } 177 | } 178 | } 179 | } 180 | 181 | TableViewColumn { 182 | id: indicatorColumn 183 | width: 50 184 | 185 | role: 'indicator' 186 | title: 'Indicator' 187 | 188 | delegate: Item { 189 | height: 32 190 | width: 50 191 | property var row: podcastListModel.get(styleData.row) 192 | 193 | Rectangle { 194 | anchors.centerIn: parent 195 | visible: row.updating 196 | 197 | color: styleData.selected ? '#ffffff' : '#000000' 198 | 199 | property real phase: 0 200 | 201 | width: 15 + 3 * Math.sin(phase) 202 | height: width 203 | 204 | PropertyAnimation on phase { 205 | loops: Animation.Infinite 206 | duration: 2000 207 | running: parent.visible 208 | from: 0 209 | to: 2*Math.PI 210 | } 211 | } 212 | 213 | Pill { 214 | anchors.centerIn: parent 215 | 216 | visible: !row.updating 217 | 218 | leftCount: row.unplayed 219 | rightCount: row.downloaded 220 | } 221 | } 222 | } 223 | 224 | onCurrentRowChanged: { 225 | var id = podcastListModel.get(currentRow).id; 226 | episodeListModel.loadEpisodes(id); 227 | } 228 | } 229 | 230 | 231 | Button { 232 | Layout.fillWidth: true 233 | Layout.margins: 3 234 | text: 'Check for new episodes' 235 | onClicked: py.call('main.check_for_episodes'); 236 | enabled: !py.refreshing 237 | } 238 | } 239 | 240 | SplitView { 241 | orientation: Orientation.Vertical 242 | 243 | TableView { 244 | id: episodeListView 245 | 246 | Layout.fillWidth: true 247 | model: GPodderEpisodeListModel { id: episodeListModel } 248 | GPodderEpisodeListModelConnections {} 249 | selectionMode: SelectionMode.MultiSelection 250 | 251 | function forEachSelectedEpisode(callback) { 252 | episodeListView.selection.forEach(function(rowIndex) { 253 | var episode_id = episodeListModel.get(rowIndex).id; 254 | callback(episode_id); 255 | }); 256 | } 257 | 258 | Menu { 259 | id: episodeContextMenu 260 | 261 | MenuItem { 262 | text: 'Toggle new' 263 | onTriggered: { 264 | episodeListView.forEachSelectedEpisode(function (episode_id) { 265 | py.call('main.toggle_new', [episode_id]); 266 | }); 267 | } 268 | } 269 | 270 | MenuItem { 271 | text: 'Download' 272 | onTriggered: { 273 | episodeListView.forEachSelectedEpisode(function (episode_id) { 274 | py.call('main.download_episode', [episode_id]); 275 | }); 276 | } 277 | } 278 | 279 | MenuItem { 280 | text: 'Delete' 281 | onTriggered: { 282 | episodeListView.forEachSelectedEpisode(function (episode_id) { 283 | py.call('main.delete_episode', [episode_id]); 284 | }); 285 | } 286 | } 287 | } 288 | 289 | rowDelegate: Rectangle { 290 | height: 40 291 | color: styleData.selected ? Constants.colors.select : 'transparent' 292 | 293 | MouseArea { 294 | acceptedButtons: Qt.RightButton 295 | anchors.fill: parent 296 | onClicked: episodeContextMenu.popup() 297 | } 298 | } 299 | 300 | TableViewColumn { 301 | role: 'title' 302 | title: 'Episode' 303 | 304 | delegate: Row { 305 | property var row: episodeListModel.get(styleData.row) 306 | height: 32 307 | 308 | Item { 309 | width: 32 310 | height: 32 311 | 312 | Rectangle { 313 | anchors.centerIn: parent 314 | width: 10 315 | height: 10 316 | color: episodeTitle.color 317 | } 318 | 319 | anchors.verticalCenter: parent.verticalCenter 320 | opacity: row.downloadState === Constants.state.downloaded 321 | } 322 | 323 | Column { 324 | anchors.verticalCenter: parent.verticalCenter 325 | spacing: -5 326 | 327 | Text { 328 | id: episodeTitle 329 | text: styleData.value 330 | elide: styleData.elideMode 331 | color: styleData.selected ? 'white' : 'black' 332 | font.bold: row.isNew 333 | } 334 | 335 | Text { 336 | text: row.progress ? ('Downloading: ' + parseInt(100*row.progress) + '%') : row.subtitle 337 | elide: styleData.elideMode 338 | color: styleData.selected ? 'white' : 'black' 339 | } 340 | } 341 | } 342 | } 343 | 344 | onActivated: { 345 | var episode_id = episodeListModel.get(currentRow).id; 346 | 347 | openDialog('dialogs/EpisodeDetailsDialog.qml', function (dialog) { 348 | py.call('main.show_episode', [episode_id], function (episode) { 349 | dialog.episode = episode; 350 | }); 351 | }); 352 | } 353 | } 354 | 355 | Label { 356 | } 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /touch/icons/LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under Creative Commons' Attribution-ShareAlike 3.0 United States (CC BY-SA 3.0) - http://creativecommons.org/licenses/by-sa/3.0/us/ 2 | 3 | If you use these icons, please add a link to Iconic (http://somerandomdude.com/work/iconic/) somewhere on your site or in your app. 4 | 5 | 6 | 7 | LEGAL MUMBO-JUMBO 8 | 9 | You are free: 10 | 11 | to Share — to copy, distribute and transmit the work 12 | to Remix — to adapt the work 13 | to make commercial use of the work 14 | 15 | Under the following conditions: 16 | 17 | Attribution — You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). 18 | Share Alike — If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. 19 | With the understanding that: 20 | 21 | Waiver — Any of the above conditions can be waived if you get permission from the copyright holder. 22 | Public Domain — Where the work or any of its elements is in the public domain under applicable law, that status is in no way affected by the license. 23 | Other Rights — In no way are any of the following rights affected by the license: 24 | Your fair dealing or fair use rights, or other applicable copyright exceptions and limitations; 25 | Apart from the remix rights granted under this license, the author's moral rights; 26 | Rights other persons may have either in the work itself or in how the work is used, such as publicity or privacy rights. 27 | Notice — For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to this web page. 28 | 29 | 30 | Full License 31 | 32 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 33 | 34 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 35 | 36 | 1. Definitions 37 | 38 | "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with one or more other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. 39 | "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of derivatives of works made available under that license under this License or either a Creative Commons unported license or a Creative Commons jurisdiction license with the same License Elements as this License. 40 | "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License. 41 | "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. 42 | "Licensor" means the individual, individuals, entity or entities that offers the Work under the terms of this License. 43 | "Original Author" means the individual, individuals, entity or entities who created the Work. 44 | "Work" means the copyrightable work of authorship offered under the terms of this License. 45 | "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 46 | 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. 47 | 48 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 49 | 50 | to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; 51 | to create and reproduce Derivative Works provided that any such Derivative Work, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 52 | to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; 53 | to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works. 54 | For the avoidance of doubt, where the Work is a musical composition: 55 | 56 | Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or, in the event that Licensor is a member of a performance rights society (e.g. ASCAP, BMI, SESAC), via that society, royalties for the public performance or public digital performance (e.g. webcast) of the Work. 57 | Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights agency or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions). 58 | Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). 59 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved. 60 | 61 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 62 | 63 | You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of a recipient of the Work to exercise of the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. When You distribute, publicly display, publicly perform, or publicly digitally perform the Work, You may not impose any technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise of the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by Section 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by Section 4(c), as requested. 64 | You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under: (i) the terms of this License; (ii) a later version of this License with the same License Elements as this License; (iii) either the Creative Commons (Unported) license or a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g. Attribution-ShareAlike 3.0 (Unported)); (iv) a Creative Commons Compatible License. If you license the Derivative Work under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Derivative Work under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and with the following provisions: (I) You must include a copy of, or the Uniform Resource Identifier for, the Applicable License with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform; (II) You may not offer or impose any terms on the Derivative Works that restrict the terms of the Applicable License or the ability of a recipient of the Work to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties; and, (IV) when You distribute, publicly display, publicly perform, or publicly digitally perform the Work, You may not impose any technological measures on the Derivative Work that restrict the ability of a recipient of the Derivative Work from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of the Applicable License. 65 | If You distribute, publicly display, publicly perform, or publicly digitally perform the Work (as defined in Section 1 above) or any Derivative Works (as defined in Section 1 above) or Collective Works (as defined in Section 1 above), You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, consistent with Section 3(b) in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear, if a credit for all contributing authors of the Derivative Work or Collective Work appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 66 | 5. Representations, Warranties and Disclaimer 67 | 68 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND ONLY TO THE EXTENT OF ANY RIGHTS HELD IN THE LICENSED WORK BY THE LICENSOR. THE LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MARKETABILITY, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 69 | 70 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 71 | 72 | 7. Termination 73 | 74 | This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 75 | Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 76 | 8. Miscellaneous 77 | 78 | Each time You distribute or publicly digitally perform the Work (as defined in Section 1 above) or a Collective Work (as defined in Section 1 above), the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 79 | Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 80 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 81 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 82 | This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. --------------------------------------------------------------------------------