diff --git a/CMakeLists.txt b/CMakeLists.txt
index 519f36130f..666d891ea3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -179,6 +179,7 @@ if( NOT Qt5Core_DIR )
set(QT_USE_QTNETWORK TRUE)
set(QT_USE_QTXML TRUE)
set(QT_USE_QTWEBKIT TRUE)
+ set(QT_USE_QTDECLARATIVE TRUE)
include( ${QT_USE_FILE} )
endmacro()
diff --git a/TomahawkUse.cmake b/TomahawkUse.cmake
index 0d59a9f9d4..7bbc1b2e54 100644
--- a/TomahawkUse.cmake
+++ b/TomahawkUse.cmake
@@ -1,11 +1,11 @@
#FIXME: this only handles qt4 and duplicates top level cmakelists: how can we reduce code duplication?
-find_package(Qt4 COMPONENTS QtNetwork QtCore QtGui QtSql REQUIRED)
+find_package(Qt4 COMPONENTS QtNetwork QtCore QtGui QtSql QtDeclarative REQUIRED)
include( ${QT_USE_FILE} )
set(NEEDED_QT4_COMPONENTS "QtCore" "QtXml" "QtNetwork")
if(BUILD_GUI OR NOT DEFINED BUILD_GUI)
- list(APPEND NEEDED_QT4_COMPONENTS "QtGui" "QtWebkit" "QtUiTools" "QtSvg")
+ list(APPEND NEEDED_QT4_COMPONENTS "QtGui" "QtWebkit" "QtUiTools" "QtSvg" "QtDeclarative")
endif()
find_package(Qt4 4.7.0 COMPONENTS ${NEEDED_QT4_COMPONENTS})
diff --git a/data/images/inputfield-border.svg b/data/images/inputfield-border.svg
new file mode 100644
index 0000000000..3f3ff841ca
--- /dev/null
+++ b/data/images/inputfield-border.svg
@@ -0,0 +1,145 @@
+
+
diff --git a/data/images/station-artist.svg b/data/images/station-artist.svg
new file mode 100644
index 0000000000..43ed51948e
--- /dev/null
+++ b/data/images/station-artist.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/data/images/station-genre.svg b/data/images/station-genre.svg
new file mode 100644
index 0000000000..ba5c1157fe
--- /dev/null
+++ b/data/images/station-genre.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/data/images/station-year.svg b/data/images/station-year.svg
new file mode 100644
index 0000000000..a18fb23d33
--- /dev/null
+++ b/data/images/station-year.svg
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/data/qml/DeclarativeHeader.qml b/data/qml/DeclarativeHeader.qml
new file mode 100644
index 0000000000..5863a9d27f
--- /dev/null
+++ b/data/qml/DeclarativeHeader.qml
@@ -0,0 +1,21 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "tomahawkimports"
+
+// Only to be used together with DeclarativeHeader C++ class
+// If you want to use the header in QML, use FlexibleHeader
+
+Item {
+ anchors.fill: parent
+
+ FlexibleHeader {
+ anchors.fill: parent
+ icon: iconSource
+ title: caption
+ subtitle: description
+ buttonModel: buttonList
+
+ onSearchTextChanged: mainView.setFilterText(searchText)
+ onCurrentButtonIndexChanged: mainView.viewModeSelected(currentButtonIndex)
+ }
+}
diff --git a/data/qml/QmlGridView.qml b/data/qml/QmlGridView.qml
new file mode 100644
index 0000000000..1d27a2d5e2
--- /dev/null
+++ b/data/qml/QmlGridView.qml
@@ -0,0 +1,65 @@
+import QtQuick 1.1
+//import tomahawk 1.0
+import "tomahawkimports"
+
+Rectangle {
+ anchors.fill: parent
+ color: "black"
+
+ Text {
+ id: fontMetrics
+ text: "Here's some sample text"
+ opacity: 0
+ }
+
+ GridView {
+ id: gridView
+ anchors.fill: parent
+ //anchors.rightMargin: scrollBar.width
+
+ cellHeight: cellWidth
+ cellWidth: calculateCoverSize(gridView.width - 3)
+
+ cacheBuffer: cellHeight * 5
+
+ function calculateCoverSize(rectWidth) {
+ var itemWidth = fontMetrics.width;
+ var itemsPerRow = Math.max( 1, Math.floor( rectWidth / itemWidth ) );
+
+ var remSpace = rectWidth - ( itemsPerRow * itemWidth );
+ var extraSpace = remSpace / itemsPerRow;
+ return itemWidth + extraSpace;
+
+ }
+
+ model: mainModel
+
+ delegate: CoverImage {
+ height: gridView.cellHeight// * 0.95
+ width: gridView.cellWidth// * 0.95
+
+ showLabels: true
+ showMirror: false
+ artistName: model.artistName
+ trackName: model.trackName
+ artworkId: model.coverID
+ showPlayButton: true
+ currentlyPlaying: isPlaying
+ smooth: !gridView.moving
+
+ onClicked: {
+ rootView.onItemClicked(index)
+ }
+ onPlayClicked: {
+ rootView.onItemPlayClicked(index)
+ }
+ }
+ }
+
+ ScrollBar {
+ id: scrollBar
+ listView: gridView
+ orientation: Qt.Vertical
+ margin: -width
+ }
+}
diff --git a/data/qml/SpinnerTest.qml b/data/qml/SpinnerTest.qml
new file mode 100644
index 0000000000..aa11c4031d
--- /dev/null
+++ b/data/qml/SpinnerTest.qml
@@ -0,0 +1,35 @@
+import QtQuick 1.1
+import "tomahawkimports"
+
+Rectangle {
+width: 1400
+height: 900
+color: "black"
+
+BusyIndicator {
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: 200
+ height: 200
+ width: 200
+ count: 11
+}
+
+Image {
+ id: svgSpinner
+ source: "../images/loading-animation.svg"
+ smooth: true
+ height: 200
+ width: 200
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: -200
+
+ Timer {
+ running: true
+ repeat: true
+ interval: 200
+ onTriggered: svgSpinner.rotation += 360 / 12
+ }
+}
+
+
+}
diff --git a/data/qml/StationView.qml b/data/qml/StationView.qml
new file mode 100644
index 0000000000..4d772bae80
--- /dev/null
+++ b/data/qml/StationView.qml
@@ -0,0 +1,165 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "tomahawkimports"
+import "stations"
+
+Rectangle {
+ id: scene
+ color: "black"
+ anchors.fill: parent
+ state: "list"
+
+ FlexibleHeader {
+ id: header
+ anchors {
+ left: parent.left
+ top: parent.top
+ right: parent.right
+ }
+ height: defaultFontHeight * 4
+ width: parent.width
+ icon: "../images/station.svg"
+ title: mainView.title
+ subtitle: generator.summary
+ showSearchField: false
+ showBackButton: stationListView.currentIndex > 0
+ showNextButton: mainView.configured && stationListView.currentIndex == 2
+ nextButtonText: "Save"
+ backButtonText: "Back"
+
+ z: 1 //cover albumcovers that may leave their area
+
+ onBackPressed: {
+ inputBubble.opacity = 0
+ stationListView.decrementCurrentIndex()
+ switch (stationListView.currentIndex) {
+ case 0:
+ subtitle = ""
+ break;
+ case 1:
+ subtitle = modeModel.get(stationCreator.modeIndex).headerSubtitle + "..."
+ break;
+ }
+ }
+ // In our case the next button is the save button
+ onNextPressed: {
+ inputBubble.opacity = 1
+ inputBubble.forceActiveFocus();
+ }
+ }
+
+
+ ListModel {
+ id: modeModel
+ ListElement { label: "By Artist"; image: "../../images/station-artist.svg"; creatorContent: "stations/CreateByArtist.qml"; headerSubtitle: "Songs from" }
+ ListElement { label: "By Genre"; image: "../../images/station-genre.svg"; creatorContent: "stations/CreateByGenre.qml"; headerSubtitle: "Songs of genre" }
+ ListElement { label: "By Year"; image: "../../images/station-year.svg"; creatorContent: "stations/CreateByYear.qml"; headerSubtitle: "Songs from" }
+ }
+
+ VisualItemModel {
+ id: stationVisualModel
+
+ StationCreatorPage1 {
+ height: scene.height - header.height
+ width: scene.width
+ model: modeModel
+
+ onItemClicked: {
+ stationCreator.modeIndex = index
+
+ // FIXME: This is a workaround for the ListView scrolling too slow on the first time
+ // Lets reinitialize the current index to something invalid and back to 0 (what it already is) before scrolling over to page 1
+ stationListView.currentIndex = -1
+ stationListView.currentIndex = 0
+
+ stationListView.incrementCurrentIndex()
+ header.subtitle = modeModel.get(index).headerSubtitle + "..."
+ }
+ }
+
+ StationCreatorPage2 {
+ id: stationCreator
+ height: stationListView.height
+ width: stationListView.width
+
+ property int modeIndex
+
+ content: modeModel.get(modeIndex).creatorContent
+
+ onNext: {
+ stationListView.incrementCurrentIndex()
+ header.subtitle = modeModel.get(modeIndex).headerSubtitle + " " + text
+ }
+ }
+
+ StationItem {
+ id: stationItem
+ height: stationListView.height
+ width: stationListView.width
+ }
+ }
+
+
+ VisualItemModel {
+ id: configuredStationVisualModel
+
+
+ StationItem {
+ id: cfgstationItem
+ height: stationListView.height
+ width: stationListView.width
+ }
+ }
+
+ ListView {
+ id: stationListView
+ anchors {
+ left: parent.left
+ top: header.bottom
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ contentHeight: height
+ contentWidth: width
+ orientation: ListView.Horizontal
+ //model: mainView.configured ? configuredStationVisualModel : stationVisualModel
+ interactive: false
+ highlightMoveDuration: 300
+
+ onHeightChanged: {
+ contentHeight = scene.height
+ }
+ onWidthChanged: {
+ contentWidth = scene.width
+ }
+
+ Component.onCompleted: {
+ model = mainView.configured ? configuredStationVisualModel : stationVisualModel
+ }
+ onModelChanged: print("ccccccccccccc", mainView.configured)
+ }
+
+ InputBubble {
+ id: inputBubble
+ text: "Station name:"
+ width: defaultFontHeight * 18
+ anchors.top: header.bottom
+ anchors.right: parent.right
+ anchors.rightMargin: defaultFontHeight / 2
+ anchors.topMargin: -defaultFontHeight / 2
+ z: 2
+ opacity: 0
+ arrowPosition: 0.95
+
+ onAccepted: {
+ mainView.title = inputBubble.inputText
+ inputBubble.opacity = 0
+ header.showNextButton = false
+ header.showBackButton = false
+ inputBubble.opacity = 0
+ }
+
+ onRejected: inputBubble.opacity = 0
+ }
+}
diff --git a/data/qml/stations/CreateByArtist.qml b/data/qml/stations/CreateByArtist.qml
new file mode 100644
index 0000000000..78bb338530
--- /dev/null
+++ b/data/qml/stations/CreateByArtist.qml
@@ -0,0 +1,74 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+Item {
+ id: root
+ anchors.fill: parent
+
+ signal done(string text)
+
+ function createStation(artist) {
+ mainView.startStationFromArtist(artist)
+ root.done(artist)
+ }
+
+ Column {
+ id: upperColumn
+ anchors.fill: parent
+ anchors.margins: defaultFontHeight
+ spacing: defaultFontHeight
+
+ HeaderLabel {
+ id: headerText
+ text: "Pick one of your top artists,"
+ }
+
+ Item {
+ height: parent.height - headerText.height*2 - artistInputField.height - parent.spacing * 3
+ width: parent.width
+ ArtistView {
+ id: artistView
+ height: parent.height
+ width: parent.width
+ model: artistChartsModel
+ clip: true
+ cellWidth: defaultFontHeight * 12
+ cellHeight: defaultFontHeight * 12
+ spacing: defaultFontHeight / 2
+
+ onItemClicked: {
+ createStation(artistChartsModel.itemFromIndex(index).artistName);
+ }
+ }
+ ScrollBar {
+ listView: artistView
+ }
+ }
+
+ HeaderLabel {
+ text: "Or enter an artist name"
+ }
+
+ Row {
+ height: artistInputField.height
+ width: Math.min(defaultFontHeight * 30, parent.width)
+ spacing: defaultFontHeight * 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ InputField {
+ id: artistInputField
+ width: parent.width - createFromInputButton.width - parent.spacing
+
+ onAccepted: createStation(text)
+ }
+
+ PushButton {
+ id: createFromInputButton
+ text: "Create station"
+ enabled: artistInputField.text.length > 2
+ onClicked: createStation(artistInputField.text)
+ }
+ }
+ }
+}
diff --git a/data/qml/stations/CreateByGenre.qml b/data/qml/stations/CreateByGenre.qml
new file mode 100644
index 0000000000..2ba9ac8318
--- /dev/null
+++ b/data/qml/stations/CreateByGenre.qml
@@ -0,0 +1,94 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+Item {
+ id: root
+ anchors.fill: parent
+
+ signal done(string text)
+
+ function createStation(genre) {
+ mainView.startStationFromGenre(genre)
+ root.done(genre)
+ }
+
+ ListModel {
+ id: styleModel
+ ListElement { modelData: "acoustic" }
+ ListElement { modelData: "alternative" }
+ ListElement { modelData: "alternative rock" }
+ ListElement { modelData: "classic" }
+ ListElement { modelData: "folk" }
+ ListElement { modelData: "indie" }
+ ListElement { modelData: "pop" }
+ ListElement { modelData: "rock" }
+ ListElement { modelData: "hip-hop" }
+ ListElement { modelData: "punk" }
+ ListElement { modelData: "grunge" }
+ ListElement { modelData: "indie" }
+ ListElement { modelData: "electronic" }
+ ListElement { modelData: "country" }
+ ListElement { modelData: "jazz" }
+ ListElement { modelData: "psychodelic" }
+ ListElement { modelData: "soundtrack" }
+ ListElement { modelData: "reggae" }
+ ListElement { modelData: "house" }
+ ListElement { modelData: "drum and base" }
+ }
+
+ Column {
+ id: upperColumn
+ anchors.fill: parent
+ anchors.bottomMargin: defaultFontHeight
+ spacing: defaultFontHeight
+
+ HeaderLabel {
+ id: headerText
+ text: "Enter a genre,"
+ }
+
+ Row {
+ width: Math.min(defaultFontHeight * 30, parent.width)
+ height: parent.height * 0.2
+ spacing: defaultFontHeight * 0.5
+ anchors.horizontalCenter: parent.horizontalCenter
+ z: 2
+
+ InputField {
+ id: genreInputField
+ width: parent.width - createFromInputButton.width - parent.spacing
+ completionModel: allGenres
+
+ onAccepted: createStation(text);
+ }
+
+ PushButton {
+ id: createFromInputButton
+ text: "Create station"
+ height: genreInputField.height
+ enabled: genreInputField.text.length > 2
+ onClicked: createStation(genreInputField.text)
+ }
+ }
+
+ HeaderLabel {
+ text: "Or, pick one of your most listened genres"
+ }
+
+ Item {
+ height: parent.height - y
+ width: parent.width
+ clip: true
+ TagCloud {
+ anchors.fill: parent
+ anchors.margins: parent.width / 6
+ model: styleModel
+
+ onTagClicked: {
+ root.createStation(tag);
+ }
+ }
+ }
+ }
+}
diff --git a/data/qml/stations/CreateByYear.qml b/data/qml/stations/CreateByYear.qml
new file mode 100644
index 0000000000..ebd0a8c3a3
--- /dev/null
+++ b/data/qml/stations/CreateByYear.qml
@@ -0,0 +1,89 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+Item {
+ id: root
+ anchors.fill: parent
+
+ signal done(string text)
+
+ function createStationFromYear(year) {
+ mainView.startStationFromYear(year)
+ root.done(year)
+ }
+
+ function createStationFromTo(yearFrom, yearTo) {
+ mainView.startStationFromTo(yearFrom, yearTo)
+ root.done(yearFrom + " to " + yearTo)
+ }
+
+ Column {
+ id: upperColumn
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: parent.height
+ width: defaultFontHeight * 30
+ anchors.bottomMargin: defaultFontHeight
+ spacing: defaultFontHeight
+
+ HeaderLabel {
+ id: headerText
+ text: "Enter a year or pick a range"
+ }
+
+ Row {
+ height: yearInputField.height
+ width: parent.width
+ spacing: defaultFontHeight * 0.5
+
+ Text {
+ text: "Year:"
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ InputField {
+ id: yearInputField
+ width: parent.width - createFromInputButton.width - parent.spacing
+
+ onAccepted: createStation(text)
+ }
+ }
+
+ DoubleSlider {
+ id: yearSlider
+ width: parent.width
+ height: defaultFontHeight * 4
+ min: 1960
+ max: new Date().getFullYear()
+ lowerSliderPos: 1990
+ upperSliderPos: 2010
+ minMaxLabelsVisible: false
+ opacity: yearInputField.text.length > 0 ? 0.3 : 1
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+ }
+
+ PushButton {
+ id: createFromInputButton
+ text: "Go!"
+ enabled: yearInputField.text.length == 0 || (yearInputField.text >= yearSlider.min && yearInputField.text <= yearSlider.max)
+ anchors.horizontalCenter: parent.horizontalCenter
+ onClicked: {
+ if (yearInputField.text.length > 0) {
+ createStationFromYear(yearInputField.text)
+ } else {
+ createStationFromTo(yearSlider.lowerSliderPos, yearSlider.upperSliderPos)
+ }
+ }
+
+ // TODO: move some disabled look/animation to the button itself
+ opacity: enabled ? 1 : 0.3
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+ }
+ }
+}
diff --git a/data/qml/stations/StationConfig.qml b/data/qml/stations/StationConfig.qml
new file mode 100644
index 0000000000..d6f0ce11df
--- /dev/null
+++ b/data/qml/stations/StationConfig.qml
@@ -0,0 +1,79 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "tomahawkimports"
+
+Item {
+ id: fineTuneView
+
+ property color textColor: "white"
+
+ signal done();
+
+ Grid {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: 50
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: scene.width / 2
+ spacing: 50
+ columns: 2
+
+ Text {
+ color: fineTuneView.textColor
+ text: "Name:"
+
+ }
+ InputField {
+ text: echonestStation.name
+
+ onAccepted: {
+ print("text changed!!!")
+ echonestStation.name = text;
+ }
+ }
+
+ Text {
+ id: tempoText
+ text: "Tempo:"
+ color: "white"
+ }
+ DoubleSlider {
+ width: 500
+ height: tempoText.height
+ min: 0
+ max: 500
+ lowerSliderPos: echonestStation.minTempo
+ upperSliderPos: echonestStation.maxTempo
+ onValueChanged: echonestStation.setTempo( lowerSliderPos, upperSliderPos )
+ }
+
+ Text {
+ id: hotnessText
+ text: "Hotness:"
+ color: "white"
+ }
+ DoubleSlider {
+ width: 500
+ height: hotnessText.height
+ min: 0
+ max: 100
+ minLabel: "Less"
+ maxLabel: "More"
+ showFloatingLabel: false
+ lowerSliderPos: echonestStation.minHotttness * 100
+ upperSliderPos: echonestStation.maxHotttness * 100
+ onValueChanged: echonestStation.setHotttness( 1.0 * lowerSliderPos / 100, 1.0 * upperSliderPos / 100 )
+ }
+ }
+
+
+ Button {
+ id: configureButton
+ onClicked: fineTuneView.done();
+ text: "configure"
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+}
diff --git a/data/qml/stations/StationCreatorPage1.qml b/data/qml/stations/StationCreatorPage1.qml
new file mode 100644
index 0000000000..ca1aed727e
--- /dev/null
+++ b/data/qml/stations/StationCreatorPage1.qml
@@ -0,0 +1,65 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+
+Item {
+ id: root
+ property alias model: gridView.model
+ property int spacing: 10
+
+ signal itemClicked(int index)
+
+ GridView {
+ id: gridView
+ anchors.centerIn: parent
+ width: root.width * 9 / 10
+ height: cellHeight
+ interactive: false
+
+ cellWidth: (width - 1) / 3
+ cellHeight: cellWidth //* 10 / 16
+
+ delegate: Image {
+ width: gridView.cellWidth - root.spacing
+ height: gridView.cellHeight - root.spacing
+ source: image
+ smooth: true
+
+ Rectangle {
+ id: textBackground
+ anchors {
+ left: parent.left
+ bottom: parent.bottom
+ right: parent.right
+ }
+ height: parent.height / 5
+ color: "black"
+ opacity: .5
+
+ }
+ Text {
+ anchors.centerIn: textBackground
+ text: label
+ color: "white"
+ font.bold: true
+ }
+ Rectangle {
+ id: hoverShade
+ anchors.fill: parent
+ color: "white"
+ opacity: mouseArea.containsMouse ? .2 : 0
+
+ Behavior on opacity {
+ NumberAnimation { easing.type: Easing.Linear; duration: 300 }
+ }
+ }
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: root.itemClicked(index)
+ }
+ }
+ }
+}
diff --git a/data/qml/stations/StationCreatorPage2.qml b/data/qml/stations/StationCreatorPage2.qml
new file mode 100644
index 0000000000..295069aa66
--- /dev/null
+++ b/data/qml/stations/StationCreatorPage2.qml
@@ -0,0 +1,25 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+Item {
+ id: root
+
+ property int margins: defaultFontHeight * 2
+ property alias content: contentLoader.source
+
+ signal next(string text)
+
+ Loader {
+ id: contentLoader
+ anchors.fill: parent
+ anchors.margins: root.margins
+ }
+
+ Connections {
+ target: contentLoader.item
+
+ onDone: root.next(text)
+ }
+
+}
diff --git a/data/qml/stations/StationItem.qml b/data/qml/stations/StationItem.qml
new file mode 100644
index 0000000000..16021e37bb
--- /dev/null
+++ b/data/qml/stations/StationItem.qml
@@ -0,0 +1,37 @@
+import QtQuick 1.1
+import tomahawk 1.0
+import "../tomahawkimports"
+
+Item {
+ id: stationItem
+
+ CoverFlow {
+ id: coverView
+ interactive: false
+ anchors.fill: parent
+
+ backgroundColor: scene.color
+
+ model: dynamicModel
+ currentIndex: currentlyPlayedIndex
+
+ onItemPlayPauseClicked: {
+ mainView.playItem(index)
+ }
+
+ onItemClicked: {
+ mainView.playItem(index)
+ }
+ }
+ BusyIndicator {
+ id: busyIndicator
+ anchors.centerIn: parent
+ height: defaultFontHeight * 4
+ width: height
+// count: 12
+
+ opacity: mainView.loading ? 1 : 0
+ running: mainView.loading
+ }
+
+}
diff --git a/data/qml/tomahawkimports/ArtistView.qml b/data/qml/tomahawkimports/ArtistView.qml
new file mode 100644
index 0000000000..6c0d764270
--- /dev/null
+++ b/data/qml/tomahawkimports/ArtistView.qml
@@ -0,0 +1,58 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+GridView {
+ id: root
+ signal itemClicked(int index)
+ property int spacing
+
+ delegate: Item {
+ width: root.cellWidth - root.spacing / 2
+ height: root.cellHeight - root.spacing / 2
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ radius: defaultFontHeight / 2
+ opacity: 0.5
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#00FFFFFF" }
+ GradientStop { position: 1.0; color: "#AAFFFFFF" }
+ }
+
+ states: [
+ State {
+ name: "hovered"; when: mouseArea.containsMouse
+ PropertyChanges { target: background; opacity: 1 }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "*"; to: "hovered"
+ NumberAnimation { properties: "opacity"; duration: 100 }
+ },
+ Transition {
+ from: "hovered"; to: "*"
+ NumberAnimation { properties: "opacity"; duration: 600 }
+ }
+ ]
+ }
+
+ CoverImage {
+ id: coverImage
+ height: parent.height
+ width: height
+ showLabels: true
+ artworkId: model.coverID
+ artistName: model.artistName
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: root.itemClicked(index)
+ hoverEnabled: true
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/BusyIndicator.qml b/data/qml/tomahawkimports/BusyIndicator.qml
new file mode 100644
index 0000000000..52b6e0fba3
--- /dev/null
+++ b/data/qml/tomahawkimports/BusyIndicator.qml
@@ -0,0 +1,52 @@
+import QtQuick 1.1
+
+Item {
+ id: busyIndicator
+ width: 100
+ height: width
+ property int barWidth: width / 10
+ property int barHeight: height / 4
+ property int count: 12
+ property color color: "white"
+ property int currentHighlight: 0
+ property bool running: true
+ property int interval: 200
+
+ Behavior on opacity {
+ NumberAnimation { duration: 500 }
+ }
+
+ Repeater {
+ model: busyIndicator.count
+
+
+ Item {
+ height: parent.height
+ width: busyIndicator.barWidth
+ anchors.centerIn: parent
+ Rectangle {
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ height: busyIndicator.barHeight
+ radius: width / 2
+
+ color: busyIndicator.color
+ }
+ rotation: 360 / busyIndicator.count * index
+ opacity: 1 - ((index > busyIndicator.currentHighlight ? busyIndicator.currentHighlight + busyIndicator.count : busyIndicator.currentHighlight) - index) / busyIndicator.count
+ Behavior on opacity {
+ NumberAnimation { duration: busyIndicator.interval }
+ }
+ }
+ }
+
+ Timer {
+ interval: busyIndicator.interval
+ running: busyIndicator.running
+ repeat: true
+ onTriggered: parent.currentHighlight = (parent.currentHighlight + 1) % busyIndicator.count
+ }
+}
diff --git a/data/qml/tomahawkimports/Button.qml b/data/qml/tomahawkimports/Button.qml
new file mode 100644
index 0000000000..344b1403d8
--- /dev/null
+++ b/data/qml/tomahawkimports/Button.qml
@@ -0,0 +1,44 @@
+import QtQuick 1.1
+
+Rectangle {
+ id: root
+ height: contentRow.height + defaultFontHeight / 2
+ width: contentRow.width + defaultFontHeight / 2
+
+ property alias text: buttonText.text
+ property alias imageSource: image.source
+ property bool enabled: true
+
+ color: "transparent"
+ border.width: defaultFontHeight / 16
+ border.color: buttonMouseArea.containsMouse ? "lightblue" : "transparent"
+ radius: defaultFontHeight / 4
+
+ signal clicked()
+
+ Row {
+ id: contentRow
+ spacing: defaultFontHeight / 4
+ width: childrenRect.width
+ height: childrenRect.height
+ anchors.centerIn: parent
+ Image {
+ id: image
+ height: defaultFontHeight
+ width: source.length == 0 ? 0 : height
+ }
+
+ Text {
+ id: buttonText
+ color: root.enabled ? "black" : "grey"
+ }
+ }
+
+ MouseArea {
+ id: buttonMouseArea
+ anchors.fill: parent
+ hoverEnabled: root.enabled
+ enabled: root.enabled
+ onClicked: root.clicked();
+ }
+}
diff --git a/data/qml/tomahawkimports/CoverFlip.qml b/data/qml/tomahawkimports/CoverFlip.qml
new file mode 100644
index 0000000000..742dcce719
--- /dev/null
+++ b/data/qml/tomahawkimports/CoverFlip.qml
@@ -0,0 +1,135 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+PathView {
+ id: coverView
+
+ // The start coordinates for the covers
+ // Default is left, centered in height
+ property int pathStartX: 0
+ property int pathStartY: height
+
+ // The size of the covers in the path
+ property int coverSize: height
+
+ property color backgroundColor: "black"
+
+ // emitted when a cover is clicked
+ signal itemClicked(int index)
+
+ // emitted when a cover is clicked
+ signal itemPlayPauseClicked(int index)
+
+ preferredHighlightBegin: 0.2 // scene.width / 11000
+ preferredHighlightEnd: preferredHighlightBegin
+ pathItemCount: 5
+ //highlightMoveDuration: 500
+
+ property bool itemHovered: false
+
+ delegate: Item {
+ id: delegateItem
+ height: coverView.coverSize
+ width: coverView.coverSize
+
+ scale: PathView.itemScale
+ // itemBrightness: PathView.itemBrightness - ((coverView.itemHovered && !coverDelegate.containsMouse) ? .4 : 0)
+ property double itemBrightness: PathView.itemBrightness
+ property double itemOpacity: PathView.itemOpacity
+ property int _origZ
+
+ z: coverView.width - x
+
+ CoverImage {
+ id: coverDelegate
+ height: coverView.coverSize
+ width: coverView.coverSize
+ anchors {
+ top: parent.top
+ right: parent.right
+ }
+
+ showLabels: true
+ showMirror: true
+ artistName: model.artistName
+ trackName: model.trackName
+ artworkId: model.coverID
+ showPlayButton: true
+ currentlyPlaying: isPlaying
+ smooth: true
+
+ // itemBrightness: PathView.itemBrightness - ((coverView.itemHovered && !coverDelegate.containsMouse) ? .4 : 0)
+ itemBrightness: coverDelegate.containsMouse ? 1 : parent.itemBrightness * (coverView.itemHovered ? .5 : 1)
+ opacity: parent.itemOpacity
+ z: coverView.width - x
+
+ onPlayClicked: {
+ console.log("***************")
+ coverView.itemPlayPauseClicked(index)
+ }
+
+ onClicked: {
+ coverView.itemClicked(index)
+ }
+
+ onContainsMouseChanged: {
+ if (containsMouse) {
+ delegateItem._origZ = delegateItem.z;
+ coverView.itemHovered = true
+ } else {
+ coverView.itemHovered = false
+ }
+ }
+
+
+ }
+ states: [
+ State {
+ name: "hovered"; when: coverDelegate.containsMouse && !coverView.moving && index !== currentIndex
+ PropertyChanges {
+ target: delegateItem
+ width: coverView.coverSize * 2
+ z: delegateItem._origZ
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ NumberAnimation {
+ properties: "width"
+ duration: 300
+ easing.type: Easing.InOutSine
+ }
+
+ }
+ ]
+ }
+
+ path: Path {
+ startX: coverView.pathStartX
+ startY: coverView.pathStartY
+
+ PathAttribute { name: "itemOpacity"; value: 0 }
+ PathAttribute { name: "itemBrightness"; value: 0 }
+ PathAttribute { name: "itemScale"; value: 1.3 }
+
+ PathLine { x: coverView.width / 4; y: coverView.height / 4 * 3}
+ PathPercent { value: 0.1 }
+ PathAttribute { name: "itemOpacity"; value: 0 }
+ PathAttribute { name: "itemBrightness"; value: 1 }
+ PathAttribute { name: "itemScale"; value: 1.0 }
+
+ PathLine { x: coverView.width / 2; y: coverView.height / 2}
+ PathPercent { value: 0.2 }
+ PathAttribute { name: "itemOpacity"; value: 1 }
+ PathAttribute { name: "itemBrightness"; value: 1 }
+ PathAttribute { name: "itemScale"; value: 0.5 }
+
+ PathLine { x: coverView.width; y: 0 }
+ PathPercent { value: 1 }
+ PathAttribute { name: "itemOpacity"; value: 1 }
+ PathAttribute { name: "itemBrightness"; value: 0 }
+ PathAttribute { name: "itemScale"; value: 0.1 }
+ }
+
+}
diff --git a/data/qml/tomahawkimports/CoverFlow.qml b/data/qml/tomahawkimports/CoverFlow.qml
new file mode 100644
index 0000000000..7f9fa00469
--- /dev/null
+++ b/data/qml/tomahawkimports/CoverFlow.qml
@@ -0,0 +1,84 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+ListView {
+ id: coverView
+
+ property color backgroundColor: "black"
+ property int coverSize: height / 2
+
+ // emitted when a cover is clicked
+ signal itemClicked(int index)
+
+ // emitted when a cover is clicked
+ signal itemPlayPauseClicked(int index)
+
+ preferredHighlightBegin: (width / 2) - (coverSize / 4)
+ preferredHighlightEnd: preferredHighlightBegin + coverSize / 2
+ snapMode: ListView.SnapToItem
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ highlightMoveDuration: 200
+
+ property bool itemHovered: false
+ orientation: ListView.Horizontal
+
+ delegate: Item {
+ id: delegateItem
+ height: parent.height
+ width: coverView.coverSize / 2
+ anchors.verticalCenter: ListView.view.verticalCenter
+
+ property real distanceFromLeftEdge: -coverView.contentX + index*width
+ property real distanceFromRightEdge: coverView.contentX + coverView.width - (index+1)*width
+ property real distanceFromEdge: Math.max(distanceFromLeftEdge, distanceFromRightEdge)
+
+ scale: 2 - (distanceFromEdge / (coverView.width))
+
+ property double itemBrightness: (1.3 - (distanceFromEdge / (coverView.width))) - ((coverView.itemHovered && !coverDelegate.containsMouse) ? .4 : 0)
+ property double itemOpacity: coverView.itemHovered && !coverDelegate.containsMouse ? 0.4 : 1
+ property int _origZ
+
+ z: -Math.abs(currentIndex - index)
+
+ CoverImage {
+ id: coverDelegate
+ height: coverView.coverSize / 2
+ width: parent.width
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ }
+
+ showLabels: true
+ showMirror: false
+ artistName: model.artistName
+ trackName: model.trackName
+ artworkId: model.coverID
+ showPlayButton: true
+ currentlyPlaying: isPlaying
+ smooth: true
+
+ itemBrightness: coverDelegate.containsMouse ? 1 : parent.itemBrightness * (coverView.itemHovered ? .5 : 1)
+ opacity: parent.itemOpacity
+ z: coverView.width - x
+
+ onPlayClicked: {
+ console.log("***************")
+ coverView.itemPlayPauseClicked(index)
+ }
+
+ onClicked: {
+ coverView.itemClicked(index)
+ }
+
+ onContainsMouseChanged: {
+ if (containsMouse) {
+ delegateItem._origZ = delegateItem.z;
+ coverView.itemHovered = true
+ } else {
+ coverView.itemHovered = false
+ }
+ }
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/CoverImage.qml b/data/qml/tomahawkimports/CoverImage.qml
new file mode 100644
index 0000000000..e3f2c1d306
--- /dev/null
+++ b/data/qml/tomahawkimports/CoverImage.qml
@@ -0,0 +1,194 @@
+import QtQuick 1.1
+
+Item {
+ id: root
+
+ // Should the artist + track labels be painted
+ property bool showLabels: true
+
+ // Should the play button be painted on mouse hover?
+ property bool showPlayButton: false
+
+ // if this is true, the play button will be swapped by a pause button
+ property bool currentlyPlaying: false
+
+ // Should the mirror be painted?
+ property bool showMirror: false
+
+ // Labels & Cover
+ property string artistName
+ property string trackName
+ property string artworkId
+
+ // The border color for the cover image
+ property color borderColor: "black"
+ // The border width for the cover image
+ property int borderWidth: 2
+
+ // sets the brightness for the item and its mirror (1: brightest, 0: darkest)
+ property double itemBrightness: 1
+ property double mirrorBrightness: .5
+
+ // set this to true if you want to smoothly scale the cover (be aware of performance impacts)
+ property bool smooth: false
+
+ // will be emitted when the on hower play button is clicked
+ signal playClicked()
+ // will be emitted when the cover is clicked
+ signal clicked()
+ // will be emitted when the cover is hovered by the mouse
+ property alias containsMouse: mouseArea.containsMouse
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+
+ onClicked: {
+ print("Cover clicked");
+ root.clicked();
+ }
+ }
+
+ Rectangle {
+ id: itemShadow
+ color: "black"
+ anchors.fill: parent
+
+ //opacity: 1 - itemBrightness
+
+ Behavior on opacity {
+ NumberAnimation { easing.type: Easing.Linear; duration: 300 }
+ }
+ }
+
+ Component {
+ id: coverImage
+
+ Item {
+ property bool isMirror: false
+
+ Image {
+ anchors.fill: parent
+ source: "image://albumart/" + artworkId + (isMirror ? "-mirror" : "") + (showLabels ? "-labels" : "")
+ smooth: root.smooth
+ opacity: itemBrightness
+ Behavior on opacity {
+ NumberAnimation { duration: 300 }
+ }
+ }
+
+ Rectangle {
+ id: itemGlow
+ anchors.fill: parent
+ anchors.topMargin: isMirror ? parent.height / 2 : 0
+
+ opacity: (mouseArea.containsMouse ? .2 : 0)
+
+ Gradient {
+ id: glowGradient
+ GradientStop { position: 0.0; color: "white" }
+ GradientStop { position: 0.7; color: "white" }
+ GradientStop { position: 0.8; color: "#00000000" }
+ GradientStop { position: 1.0; color: "#00000000" }
+ }
+ Gradient {
+ id: mirrorGlowGradient
+ GradientStop { position: 0.0; color: "#00000000" }
+ GradientStop { position: 0.5; color: "#00000000" }
+ GradientStop { position: 1.0; color: "#44FFFFFF" }
+ }
+
+ states: [
+ State {
+ name: "mirrored"; when: isMirror
+ PropertyChanges {
+ target: itemGlow
+ gradient: mirrorGlowGradient
+ }
+ },
+ State {
+ name: "normal"; when: !isMirror
+ PropertyChanges {
+ target: itemGlow
+ gradient: glowGradient
+ }
+ }
+ ]
+
+ Behavior on opacity {
+ NumberAnimation { easing.type: Easing.Linear; duration: 300 }
+ }
+ }
+
+ Text {
+ id: trackText
+ color: "white"
+ font.bold: true
+ text: trackName
+ anchors { left: parent.left; right: parent.right; bottom: artistText.top }
+ anchors.margins: 2
+ horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
+ opacity: showLabels ? itemBrightness * (isMirror ? 0.5 : 1): 0
+ font.pixelSize: root.height / 15
+ Behavior on opacity {
+ NumberAnimation { duration: 300 }
+ }
+ }
+ Text {
+ id: artistText
+ color: "white"
+ font.bold: trackText.text.length == 0
+ text: artistName
+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
+ anchors.margins: root.height / 20
+ horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
+ opacity: showLabels ? itemBrightness * (isMirror ? 0.5 : 1) : 0
+ font.pixelSize: trackText.text.length == 0 ? root.height / 10 : root.height / 15
+ Behavior on opacity {
+ NumberAnimation { duration: 300 }
+ }
+ }
+ }
+
+ }
+ Loader {
+ sourceComponent: coverImage
+ anchors.fill: parent
+ }
+
+ Loader {
+ id: mirroredCover
+ sourceComponent: parent.showMirror ? coverImage : undefined
+ anchors.fill: parent
+ onLoaded: {
+ item.isMirror = true
+ }
+ transform : [
+ Rotation {
+ angle: 180; origin.y: root.height
+ axis.x: 1; axis.y: 0; axis.z: 0
+ }
+ ]
+ }
+
+ Image {
+ id: playButton
+ visible: showPlayButton ? (mouseArea.containsMouse || currentlyPlaying) : false
+ source: currentlyPlaying ? "../../images/pause-rest.svg" : "../../images/play-rest.svg"
+ anchors.centerIn: parent
+ height: mirroredCover.height / 5
+ width: height
+ smooth: root.smooth
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ print("Play button clicked");
+ root.playClicked();
+ }
+ }
+ }
+
+}
diff --git a/data/qml/tomahawkimports/DoubleSlider.qml b/data/qml/tomahawkimports/DoubleSlider.qml
new file mode 100644
index 0000000000..2ac2c64a8d
--- /dev/null
+++ b/data/qml/tomahawkimports/DoubleSlider.qml
@@ -0,0 +1,225 @@
+import QtQuick 1.1
+
+Item {
+ id: root
+
+ property int min: 0
+ property int max: 100
+
+ /** The labels next to the slider
+ * if empty, min and max values are used
+ */
+ property string minLabel: ""
+ property string maxLabel: ""
+
+ /** Should the floating label indicating the current position be shown? */
+ property bool showFloatingLabel: true
+ property bool minMaxLabelsVisible: true
+
+ property int lowerSliderPos: 25
+ property int upperSliderPos: 75
+
+ onUpperSliderPosChanged: print("fooooooooo", upperSliderPos)
+
+ signal valueChanged()
+
+ QtObject {
+ id: priv
+
+ property int steps: root.max - root.min + 1
+
+ property int sliderHeight: root.height / 3
+ property int sliderWidth: sliderHeight / 2
+ }
+
+ Row {
+ anchors.fill: parent
+ anchors.topMargin: defaultFontHeight * 1.2
+ anchors.bottomMargin: defaultFontHeight * 1.2
+ spacing: 10
+
+ Text {
+ id: minText
+ text: root.minLabel.length > 0 ? root.minLabel : min
+ color: "white"
+ visible: root.minMaxLabelsVisible
+ }
+
+ Item {
+ id: sliderRect
+ height: root.height / 4
+ property int maxWidth: parent.width - (minText.visible ? minText.width : 0) - (maxText.visible ? maxText.width : 0) - parent.spacing * 2
+ width: maxWidth - (maxWidth % priv.steps)
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ function sliderPosToValue( sliderPos ) {
+ var percent = sliderPos * 100 / (sliderRect.width - priv.sliderWidth/2);
+ return Math.floor(percent * (priv.steps-1) / 100) + root.min
+ }
+
+ function valueToSloderPos( value ) {
+ var percent = (value - root.min) * 100 / (priv.steps-1)
+ return percent * (sliderRect.width - priv.sliderWidth/2) / 100
+ }
+
+ Rectangle {
+ id: sliderBase
+ height: parent.height
+ width: parent.width + defaultFontHeight * 1.5
+ color: "white"
+ radius: height / 2
+ anchors.centerIn: parent
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#ffffffff" }
+ GradientStop { position: 1.0; color: "#aaffffff" }
+ }
+
+ Rectangle {
+ anchors.fill: sliderBase
+ anchors.leftMargin: lowerSlider.x + priv.sliderWidth
+ anchors.rightMargin: sliderBase.width - upperSlider.x - priv.sliderWidth
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#aa962c26" }
+ GradientStop { position: 1.0; color: "#962c26" }
+ }
+ }
+
+ Row {
+ id: stepRow
+ anchors.fill: parent
+ anchors.leftMargin: defaultFontHeight - lineWidth/2
+ anchors.rightMargin: defaultFontHeight - lineWidth/2
+ property int stepCount: root.max - root.min + 1
+ property int lineHeight: height
+ property int lineWidth: lineHeight / 15
+ spacing: (width - (stepCount * lineWidth)) / stepCount
+
+ Repeater {
+ model: stepRow.stepCount
+
+ Rectangle {
+ id: marker
+ height: stepRow.lineHeight * (isHighlight ? 1.2 : 1)
+ width: stepRow.lineWidth
+ color: "black"
+
+ property bool isHighlight: index % 10 === 0
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: marker.isHighlight ? "white" : "black" }
+ GradientStop { position: 1.0; color: marker.isHighlight ? "#aaffffff" : "black" }
+ }
+
+ Text {
+ text: root.min + index
+ visible: marker.isHighlight
+ anchors.horizontalCenter: marker.horizontalCenter
+ anchors.top: marker.bottom
+ anchors.topMargin: defaultFontHeight / 2
+ color: "white"
+ }
+
+ }
+
+ }
+ }
+
+ }
+
+ Rectangle {
+ id: lowerSlider
+ height: priv.sliderHeight
+ width: priv.sliderWidth
+ anchors.verticalCenter: sliderBase.verticalCenter
+ radius: height/4
+ border.color: "black"
+ border.width: 2
+ x: sliderRect.valueToSloderPos(root.lowerSliderPos) - priv.sliderWidth/2
+
+ Rectangle {
+ id: lowerFloatingRect
+ color: "white"
+ anchors.bottom: lowerSlider.top
+ anchors.bottomMargin: 10
+// visible: root.showFloatingLabel && lowerSliderMouseArea.pressed
+ width: lowerFloatingText.width * 1.2
+ height: lowerFloatingText.height + height * 1.2
+ x: -(width - priv.sliderWidth) / 2
+ radius: height / 8
+
+ Text {
+ id: lowerFloatingText
+ anchors.centerIn: parent
+ text: sliderRect.sliderPosToValue(lowerSlider.x + priv.sliderWidth/2)
+ }
+ }
+ }
+ MouseArea {
+ id: lowerSliderMouseArea
+ anchors.fill: lowerSlider
+ drag.target: lowerSlider
+ drag.axis: "XAxis"
+ drag.minimumX: -priv.sliderWidth / 2
+ drag.maximumX: upperSlider.x - priv.sliderWidth
+ onReleased: {
+ root.lowerSliderPos = sliderRect.sliderPosToValue( lowerSlider.x + priv.sliderWidth/2 );
+ root.valueChanged();
+ }
+ }
+
+ Rectangle {
+ id: upperSlider
+ height: priv.sliderHeight
+ width: priv.sliderWidth
+ anchors.verticalCenter: sliderBase.verticalCenter
+ radius: height / 4
+ border.color: "black"
+ border.width: 2
+ x: sliderRect.valueToSloderPos(root.upperSliderPos)
+ Rectangle {
+ id: upperFloatingRect
+ color: "white"
+ anchors.bottom: upperSlider.top
+ anchors.bottomMargin: 10
+// visible: root.showFloatingLabel && upperSliderMouseArea.pressed
+ width: upperFloatingText.width * 1.2
+ height: upperFloatingText.height + height * 1.2
+ radius: height / 4
+ x: -(width - priv.sliderWidth) / 2
+
+ Text {
+ id: upperFloatingText
+ anchors.centerIn: parent
+ text: sliderRect.sliderPosToValue(upperSlider.x + priv.sliderWidth/2)
+ }
+ }
+
+ }
+ MouseArea {
+ id: upperSliderMouseArea
+ anchors.fill: upperSlider
+ onClicked: print("button pressed")
+ drag.target: upperSlider
+ drag.axis: "XAxis"
+ drag.minimumX: lowerSlider.x + priv.sliderWidth
+ drag.maximumX: parent.width - priv.sliderWidth
+ onReleased: {
+ root.upperSliderPos = sliderRect.sliderPosToValue( upperSlider.x + priv.sliderWidth/2 );
+ root.valueChanged();
+ }
+
+ }
+
+
+ }
+
+
+ Text {
+ id: maxText
+ text: root.maxLabel.length > 0 ? root.maxLabel : max
+ color: "white"
+ visible: root.minMaxLabelsVisible
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/FlexibleHeader.qml b/data/qml/tomahawkimports/FlexibleHeader.qml
new file mode 100644
index 0000000000..4822b76f4c
--- /dev/null
+++ b/data/qml/tomahawkimports/FlexibleHeader.qml
@@ -0,0 +1,223 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+Rectangle {
+ id: root
+
+ // The icon
+ property alias icon: iconImage.source
+
+ // The title
+ property alias title: titleItem.titleText
+
+ // The subtitle/description
+ property alias subtitle: subtitleText.text
+
+ // The model for the ToggleViewButtons.
+ // "modelData" role name holds the iconSource
+ // => You can use a QStringList or StandardListModel here
+ property alias buttonModel: toggleViewButtons.model
+
+ // The index of the currently selected item
+ property alias currentButtonIndex: toggleViewButtons.currentIndex
+
+ // Should we show the searchfield?
+ property bool showSearchField: true
+
+ // The SearchFields text
+ property alias searchText: searchField.text
+
+ property bool showBackButton: false
+ property bool showNextButton: false
+
+ property string backButtonText: "Back"
+ property string nextButtonText: "Next"
+
+ // Layout spacing
+ property int spacing: defaultFontHeight * 0.5
+
+ signal backPressed()
+ signal nextPressed()
+ signal savePressed()
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#615858" }
+ GradientStop { position: 1.0; color: "#231F1F" }
+ }
+
+ Row {
+ id: leftRow
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ right: rightRow.left
+ }
+
+ anchors.margins: root.spacing
+ spacing: root.spacing
+
+ Image {
+ id: iconImage
+ height: parent.height * 0.8
+ width: height
+ anchors.verticalCenter: parent.verticalCenter
+ smooth: true
+ }
+
+ Column {
+ height: childrenRect.height
+ width: parent.width - iconImage.width - parent.spacing
+ anchors.verticalCenter: parent.verticalCenter
+
+ Item {
+ id: titleItem
+ height: captionText1.height
+ width: parent.width
+ clip: true
+
+ property string titleText
+
+ onTitleTextChanged: {
+ if(captionText1.text.length > 0) {
+ captionText2.text = titleText;
+ renewTitleAnimation.start();
+ } else {
+ captionText1.text = titleText;
+ }
+ }
+
+ ParallelAnimation {
+ id: renewTitleAnimation
+ property int duration: 500
+ property variant easingType: Easing.OutBounce;
+
+ NumberAnimation { target: captionText2; property: "anchors.topMargin"; to: 0; duration: renewTitleAnimation.duration; easing.type: renewTitleAnimation.easingType }
+ NumberAnimation { target: captionText1; property: "anchors.topMargin"; to: captionText1.height * 2; duration: renewTitleAnimation.duration; easing.type: renewTitleAnimation.easingType }
+
+ onCompleted: {
+ captionText1.text = titleItem.titleText
+ captionText2.anchors.topMargin = -captionText2.height * 2
+ captionText1.anchors.topMargin = 0
+ }
+ }
+
+ Text {
+ id: captionText1
+ color: "white"
+ anchors.left: parent.left
+ anchors.top: parent.top
+
+ font.pointSize: defaultFontSize * 1.5
+ font.bold: true
+ width: parent.width
+ elide: Text.ElideRight
+ }
+ Text {
+ id: captionText2
+ color: "white"
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: -height * 2
+ font.pointSize: defaultFontSize * 1.5
+ font.bold: true
+ width: parent.width
+ elide: Text.ElideRight
+ }
+
+ }
+ Text {
+ id: subtitleText
+ color: "white"
+ font.pointSize: defaultFontSize * 1.2
+ width: parent.width
+ elide: Text.ElideRight
+ height: text.length > 0 ? defaultFontHeight : 0
+ opacity: text.length > 0 ? 1 : 0
+ Behavior on height {
+ NumberAnimation { duration: 200 }
+ }
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+
+ onTextChanged: {
+ if (text.length > 0) {
+ animationText.text = text
+ }
+ }
+
+ Text {
+ id: animationText
+ color: parent.color
+ font: parent.font
+ elide: parent.elide
+ anchors.fill: parent
+ }
+ }
+
+ }
+
+ }
+
+ Row {
+ id: rightRow
+ anchors {
+ top: parent.top
+ right: parent.right
+ rightMargin: -backButton.width - root.spacing - nextButton.width
+ bottom: parent.bottom
+ margins: root.spacing
+ }
+ width: childrenRect.width
+ spacing: root.spacing
+ layoutDirection: Qt.RightToLeft
+
+ states: [
+ State {
+ name: "oneVisible"; when: root.showBackButton && !root.showNextButton
+ PropertyChanges {
+ target: rightRow
+ anchors.rightMargin: -nextButton.width
+ }
+ },
+ State {
+ name: "bothVisible"; when: root.showBackButton && root.showNextButton
+ PropertyChanges {
+ target: rightRow
+ anchors.rightMargin: root.spacing
+ }
+ }
+
+ ]
+
+ Behavior on anchors.rightMargin {
+ NumberAnimation { duration: 200 }
+ }
+
+ PushButton {
+ id: nextButton
+ anchors.verticalCenter: parent.verticalCenter
+ text: root.nextButtonText
+ onClicked: root.nextPressed();
+ }
+ PushButton {
+ id: backButton
+ anchors.verticalCenter: parent.verticalCenter
+ text: root.backButtonText
+ onClicked: root.backPressed();
+ }
+ InputField {
+ id: searchField
+ visible: root.showSearchField
+ anchors.verticalCenter: parent.verticalCenter
+ placeholderText: "Search..."
+ showSearchIcon: true
+ }
+ ToggleViewButtons {
+ id: toggleViewButtons
+ anchors.verticalCenter: parent.verticalCenter
+ height: defaultFontHeight * 1.5
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/HeaderLabel.qml b/data/qml/tomahawkimports/HeaderLabel.qml
new file mode 100644
index 0000000000..ebffedbd48
--- /dev/null
+++ b/data/qml/tomahawkimports/HeaderLabel.qml
@@ -0,0 +1,7 @@
+import QtQuick 1.1
+
+Text {
+ color: "white"
+ font.pointSize: defaultFontSize + 5
+ font.bold: true
+}
diff --git a/data/qml/tomahawkimports/InputBubble.qml b/data/qml/tomahawkimports/InputBubble.qml
new file mode 100644
index 0000000000..188de2245a
--- /dev/null
+++ b/data/qml/tomahawkimports/InputBubble.qml
@@ -0,0 +1,114 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+FocusScope {
+ id: root
+
+ property alias text: messageText.text
+ property alias inputText: inputField.text
+
+ property real arrowPosition: 1
+
+ height: contentColumn.height + defaultFontHeight * 2
+
+ signal accepted()
+ signal rejected()
+
+ onFocusChanged: {
+ if (focus) {
+ inputField.forceActiveFocus()
+ }
+ }
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ anchors.margins: -999999999
+ hoverEnabled: root.opacity > 0
+ enabled: root.opacity > 0
+ onClicked: root.rejected();
+ }
+
+ Item {
+ id: backgroundItem
+ anchors.fill: parent
+ opacity: 0.9
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ color: "white"
+ border.color: "black"
+ border.width: defaultFontHeight / 10
+ radius: defaultFontHeight / 2
+ anchors.topMargin: defaultFontHeight / 4
+ }
+
+ Item {
+ clip: true
+ anchors.bottom: backgroundItem.top
+ anchors.bottomMargin: -background.border.width*3
+ height: defaultFontHeight
+ width: height
+ x: defaultFontHeight - arrowRect.width/2 + (root.width - defaultFontHeight*2) * arrowPosition
+ Rectangle {
+ id: arrowRect
+ height: defaultFontHeight / 1.8
+ width: height
+ rotation: 45
+ color: "white"
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: parent.height/2
+ border.color: "black"
+ border.width: defaultFontHeight / 10
+ }
+ }
+ }
+
+ Column {
+ id: contentColumn
+ width: parent.width - defaultFontHeight
+ height: childrenRect.height
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: defaultFontHeight / 4
+ spacing: defaultFontHeight / 2
+
+ Row {
+ width: parent.width
+ height: childrenRect.height
+ spacing: defaultFontHeight / 2
+ Text {
+ id: messageText
+ wrapMode: Text.WordWrap
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ InputField {
+ id: inputField
+ width: parent.width - x
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ Row {
+ height: childrenRect.height
+ anchors.right: parent.right
+ spacing: defaultFontHeight
+ Button {
+ text: "OK"
+ imageSource: "qrc:///data/images/ok.svg"
+ enabled: inputField.text.length > 0
+ onClicked: root.accepted()
+ }
+ Button {
+ text: "Cancel"
+ imageSource: "qrc:///data/images/cancel.svg"
+ onClicked: {
+ inputField.text = ""
+ root.rejected()
+ }
+ }
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/InputField.qml b/data/qml/tomahawkimports/InputField.qml
new file mode 100644
index 0000000000..1212f239ad
--- /dev/null
+++ b/data/qml/tomahawkimports/InputField.qml
@@ -0,0 +1,162 @@
+import QtQuick 1.1
+
+Rectangle {
+ id: root
+ color: "white"
+ border.color: "black"
+ border.width: defaultFontHeight * 0.1
+ radius: defaultFontHeight * 0.25
+
+ height: textInput.height * 1.4
+ width: 300
+
+ property bool showSearchIcon: false
+ property string text: ""
+ property string placeholderText: ""
+ property variant completionModel
+
+ property int spacing: defaultFontHeight * 0.2
+ signal accepted( string text )
+
+ onFocusChanged: {
+ if(focus) {
+ textInput.forceActiveFocus();
+ }
+ }
+
+ Image {
+ id: searchIcon
+ anchors {
+ left: parent.left
+ leftMargin: root.spacing
+ verticalCenter: parent.verticalCenter
+ }
+ height: parent.height * 0.6
+ width: root.showSearchIcon ? height : 1
+ opacity: root.showSearchIcon ? 1 : 0
+ smooth: true
+ source: "../../images/search-icon.svg"
+ }
+
+ Item {
+ id: textItem
+ anchors.left: searchIcon.right
+ anchors.leftMargin: root.spacing
+ anchors.right: clearIcon.right
+ anchors.rightMargin: root.spacing
+ height: textInput.height
+ anchors.verticalCenter: parent.verticalCenter
+
+ TextInput {
+ id: textInput
+ width: parent.width
+ anchors.centerIn: parent
+ text: root.text
+ font.pointSize: defaultFontSize
+
+ onAccepted: root.accepted( text );
+ onTextChanged: {
+ root.text = text;
+ realCompletionListModel.clear();
+ for (var i in completionModel) {
+ if (completionModel[i].indexOf(text) == 0) {
+ realCompletionListModel.append({modelData: completionModel[i]})
+ }
+ }
+ }
+ }
+ Text {
+ width: parent.width
+ anchors.centerIn: parent
+ text: root.text.length === 0 ? root.placeholderText : ""
+ color: "lightgray"
+ font.pointSize: defaultFontSize
+ }
+ }
+
+ Image {
+ id: clearIcon
+ anchors {
+ right: parent.right
+ rightMargin: root.spacing
+ verticalCenter: parent.verticalCenter
+ }
+ height: parent.height * 0.8
+ width: (root.showSearchIcon && root.text.length > 0) ? height : 1
+ opacity: (root.showSearchIcon && root.text.length > 0) ? 1 : 0
+ smooth: true
+ source: "../../images/search-box-dismiss-x.svg"
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: textInput.text = ""
+ }
+ }
+
+
+ Image {
+// source: "../../images/inputfield-border.svg"
+ anchors.fill: parent
+ anchors.margins: root.radius * 0.1
+ clip: true
+ }
+
+ Rectangle {
+ anchors {
+ top: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: Math.min(completionListView.count, 10) * completionListView.delegateHeight
+ color: "white"
+ ListView {
+ id: completionListView
+ anchors.fill: parent
+ anchors.rightMargin: scrollBar.width + scrollBar.margin
+ clip: true
+ model: ListModel {
+ id: realCompletionListModel
+ }
+
+ property int delegateHeight: defaultFontHeight * 1.25
+ delegate: Rectangle {
+ height: completionListView.delegateHeight
+ color: delegateMouseArea.containsMouse ? "lightblue" : "transparent"
+ width: parent.width
+ Text {
+ anchors {
+ left: parent.left
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ margins: defaultFontHeight / 4
+ }
+ text: modelData
+ }
+ MouseArea {
+ id: delegateMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: {
+ textInput.text = modelData
+ realCompletionListModel.clear();
+ }
+ }
+ }
+ }
+ ScrollBar {
+ id: scrollBar
+ listView: completionListView
+ color: "black"
+ margin: 0
+ }
+ }
+ MouseArea {
+ anchors.fill: parent
+ anchors.margins: -99999999
+ z: -1
+ enabled: completionListView.count > 0
+ onClicked: {
+ realCompletionListModel.clear();
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/PushButton.qml b/data/qml/tomahawkimports/PushButton.qml
new file mode 100644
index 0000000000..25dc864001
--- /dev/null
+++ b/data/qml/tomahawkimports/PushButton.qml
@@ -0,0 +1,35 @@
+import QtQuick 1.1
+//import tomahawk 1.0
+
+Rectangle {
+ id: root
+ height: buttonText.height * 1.4
+ width: buttonText.width + (spacing * 2)
+ radius: defaultFontHeight * 0.25
+// border.width: defaultFontHeight * 0.05
+// border.color: "#a7a7a7"
+
+ color: "white"
+/* gradient: Gradient {
+ GradientStop { position: 0.0; color: mouseArea.pressed ? "#040404" : "#fbfbfb" }
+ GradientStop { position: 1.0; color: mouseArea.pressed ? "#8e8f8e" : "#787878" }
+ }*/
+
+ property int spacing: defaultFontHeight * 0.5
+ property alias text: buttonText.text
+
+ signal clicked()
+
+ Text {
+ id: buttonText
+ anchors.centerIn: root
+ font.pointSize: defaultFontSize
+ color: mouseArea.pressed ? "grey" : "black"
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: root.clicked()
+ }
+}
diff --git a/data/qml/tomahawkimports/RoundedButton.qml b/data/qml/tomahawkimports/RoundedButton.qml
new file mode 100644
index 0000000000..790afe227a
--- /dev/null
+++ b/data/qml/tomahawkimports/RoundedButton.qml
@@ -0,0 +1,43 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+Rectangle {
+ id: root
+ border.width: 4
+ border.color: enabled ? "white" : "grey"
+ radius: height / 2
+ color: (buttonMouseArea.containsMouse && enabled) ? "#22ffffff" : "black"
+ opacity: hidden ? 0 : 1
+
+ height: defaultFontHeight * 2
+ width: height
+
+ property string text
+ property bool enabled: true
+ property bool hidden: false
+
+ signal clicked()
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+
+ Behavior on color {
+ ColorAnimation { duration: 200 }
+ }
+
+ Text {
+ anchors.centerIn: parent
+ text: parent.text
+ color: root.border.color
+ font.pixelSize: parent.height * .75
+ font.bold: true
+ }
+ MouseArea {
+ id: buttonMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: root.enabled
+ onClicked: parent.clicked()
+ }
+}
diff --git a/data/qml/tomahawkimports/ScrollBar.qml b/data/qml/tomahawkimports/ScrollBar.qml
new file mode 100644
index 0000000000..83867b53f9
--- /dev/null
+++ b/data/qml/tomahawkimports/ScrollBar.qml
@@ -0,0 +1,71 @@
+import QtQuick 1.1
+
+Item {
+ id: scrollBar
+ width: defaultFontHeight / 2
+
+ // the ListView where to attach this scrollbar
+ property variant listView
+ // the orientation of the scrollbar
+ property variant orientation : Qt.Vertical
+
+ property int margin: defaultFontHeight * 0.25
+
+ property color color: "white"
+
+ states: [
+ State {
+ name: "hidden"; when: !listView.moving
+ PropertyChanges { target: scrollBar; opacity: 0 }
+ },
+ State {
+ name: "visible"; when: listView.moving
+ PropertyChanges { target: scrollBar; opacity: 1 }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "hidden"
+ to: "visible"
+ NumberAnimation { properties: "opacity"; duration: 200 }
+ },
+ Transition {
+ from: "visible"
+ to: "hidden"
+ NumberAnimation { properties: "opacity"; duration: 2000 }
+ }
+ ]
+
+ anchors {
+ left: orientation == Qt.Vertical ? listView.right : listView.left
+ leftMargin: orientation == Qt.Vertical ? scrollBar.margin : 0
+ top: orientation == Qt.Vertical ? listView.top : listView.bottom
+ topMargin: orientation == Qt.Vertical ? 0 : scrollBar.margin
+ bottom: orientation == Qt.Vertical ? listView.bottom : undefined
+ right: orientation == Qt.Vertical ? undefined : listView.right
+ }
+
+ // A light, semi-transparent background
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1)
+ color: scrollBar.color
+ opacity: 0.2
+ clip: true
+ // Size the bar to the required size, depending upon the orientation.
+ Rectangle {
+ property real position: orientation == Qt.Vertical ? (listView.contentY / listView.contentHeight) : (listView.contentX / listView.contentWidth)
+ property real pageSize: orientation == Qt.Vertical ? (listView.height / listView.contentHeight) : (listView.width / listView.contentWidth)
+
+ x: orientation == Qt.Vertical ? 1 : (position * (scrollBar.width-2) + 1)
+ y: orientation == Qt.Vertical ? (position * (scrollBar.height-2) + 1) : 1
+ width: orientation == Qt.Vertical ? (parent.width-2) : (pageSize * (scrollBar.width-2))
+ height: orientation == Qt.Vertical ? (pageSize * (scrollBar.height-2)) : (parent.height-2)
+ radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1)
+ color: scrollBar.color
+ opacity: 1
+ }
+ }
+
+}
diff --git a/data/qml/tomahawkimports/TagCloud.qml b/data/qml/tomahawkimports/TagCloud.qml
new file mode 100644
index 0000000000..3ab6828aea
--- /dev/null
+++ b/data/qml/tomahawkimports/TagCloud.qml
@@ -0,0 +1,80 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+Item {
+ id: tagCloud
+
+ property variant model: 10
+
+ signal tagClicked( string tag )
+
+ function randomNumber(min, max) {
+ var date = new Date();
+ return (max - min) * Math.random(date.getSeconds()) + min
+ }
+
+ Flow {
+ anchors.centerIn: parent
+ width: parent.width
+ spacing: defaultFontSize
+
+ Repeater {
+ id: cloudRepeater
+ model: tagCloud.model
+
+ delegate: Item {
+ id: cloudItem
+ width: delegateText.width * 1.1
+ height: delegateText.height
+ property double itemScale: tagCloud.randomNumber(0.5, 1.2)
+ scale: itemScale
+ Text {
+ id: delegateText
+ color: "gray"
+ //text: controlModel.controlAt( index ).summary
+ text: modelData
+ font.pixelSize: defaultFontHeight * 1.8
+ anchors.verticalCenter: parent.verticalCenter
+ //anchors.verticalCenterOffset: tagCloud.randomNumber(0, 15)
+
+ states: [
+ State {
+ name: "hovered"; when: cloudItemMouseArea.containsMouse
+ PropertyChanges {
+ target: delegateText
+ color: "white"
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "*"
+ to: "hovered"
+ ColorAnimation {
+ duration: 200
+ }
+ },
+ Transition {
+ from: "hovered"
+ to: "*"
+ ColorAnimation {
+ duration: 1000
+ }
+ }
+ ]
+
+ }
+ MouseArea {
+ id: cloudItemMouseArea
+ hoverEnabled: true
+ anchors.fill: parent
+ onClicked: tagCloud.tagClicked( modelData )
+ }
+
+ Behavior on scale {
+ NumberAnimation { easing: Easing.Linear; duration: 1000 }
+ }
+ }
+ }
+ }
+}
diff --git a/data/qml/tomahawkimports/ToggleViewButtons.qml b/data/qml/tomahawkimports/ToggleViewButtons.qml
new file mode 100644
index 0000000000..066d5f4c9e
--- /dev/null
+++ b/data/qml/tomahawkimports/ToggleViewButtons.qml
@@ -0,0 +1,34 @@
+import QtQuick 1.1
+import tomahawk 1.0
+
+Row {
+ id: root
+ width: repeater.width
+
+ property alias model: repeater.model
+ property int currentIndex: 0
+
+ Repeater {
+ id: repeater
+ height: root.height
+ width: count * height
+
+
+ delegate: Image {
+ height: repeater.height
+ width: height
+
+ source: "../../images/view-toggle-" + (index === root.currentIndex ? "active-" : "inactive-" ) + (index === 0 ? "left" : ( index === repeater.count - 1 ? "right" : "centre" )) + ".svg"
+ smooth: true
+ Image {
+ anchors.fill: parent
+ source: "../../images/" + modelData + (index === root.currentIndex ? "-active.svg" : "-inactive.svg")
+ }
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: root.currentIndex = index
+ }
+ }
+ }
+}
diff --git a/resources.qrc b/resources.qrc
index 23d22bcd08..9c29f523a5 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -90,6 +90,32 @@
data/images/star-unstarred.svg
data/images/apply-check.svg
data/stylesheets/topbar-radiobuttons.css
+ data/qml/tomahawkimports/CoverImage.qml
+ data/qml/tomahawkimports/ArtistView.qml
+ data/qml/tomahawkimports/HeaderLabel.qml
+ data/qml/tomahawkimports/TagCloud.qml
+ data/qml/tomahawkimports/ScrollBar.qml
+ data/qml/tomahawkimports/InputField.qml
+ data/qml/tomahawkimports/Button.qml
+ data/qml/tomahawkimports/DoubleSlider.qml
+ data/qml/tomahawkimports/RoundedButton.qml
+ data/qml/tomahawkimports/PushButton.qml
+ data/qml/tomahawkimports/CoverFlip.qml
+ data/qml/tomahawkimports/CoverFlow.qml
+ data/qml/tomahawkimports/BusyIndicator.qml
+ data/qml/tomahawkimports/InputBubble.qml
+ data/qml/StationView.qml
+ data/qml/stations/StationItem.qml
+ data/qml/stations/StationCreatorPage1.qml
+ data/qml/stations/StationCreatorPage2.qml
+ data/qml/stations/CreateByArtist.qml
+ data/qml/stations/CreateByYear.qml
+ data/qml/stations/StationConfig.qml
+ data/qml/QmlGridView.qml
+ data/qml/stations/CreateByGenre.qml
+ data/qml/tomahawkimports/FlexibleHeader.qml
+ data/qml/tomahawkimports/ToggleViewButtons.qml
+ data/qml/DeclarativeHeader.qml
data/icons/tomahawk-icon-16x16.png
data/icons/tomahawk-icon-32x32.png
data/icons/tomahawk-icon-64x64.png
@@ -151,6 +177,12 @@
data/images/refresh.svg
data/images/inbox.svg
data/images/new-inbox.svg
+ data/images/inputfield-border.svg
+ data/images/search-box-dismiss-x.svg
+ data/images/loading-animation.svg
+ data/images/station-artist.svg
+ data/images/station-genre.svg
+ data/images/station-year.svg
data/images/outbox.svg
data/images/inbox-512x512.png
data/images/network-activity.svg
diff --git a/src/libtomahawk/Album.cpp b/src/libtomahawk/Album.cpp
index c182efb18b..d5a6d5faeb 100644
--- a/src/libtomahawk/Album.cpp
+++ b/src/libtomahawk/Album.cpp
@@ -37,6 +37,7 @@ using namespace Tomahawk;
QHash< QString, album_wptr > Album::s_albumsByName = QHash< QString, album_wptr >();
QHash< unsigned int, album_wptr > Album::s_albumsById = QHash< unsigned int, album_wptr >();
+QHash< QString, album_ptr > Album::s_albumsByCoverId = QHash< QString, album_ptr >();
static QMutex s_nameCacheMutex;
static QReadWriteLock s_idMutex;
@@ -79,6 +80,7 @@ Album::get( const Tomahawk::artist_ptr& artist, const QString& name, bool autoCr
album->setWeakRef( album.toWeakRef() );
album->loadId( autoCreate );
s_albumsByName.insert( key, album );
+ s_albumsByCoverId.insert( album->coverId(), album );
return album;
}
@@ -110,6 +112,7 @@ Album::get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& ar
album_ptr a = album_ptr( new Album( id, name, artist ), &Album::deleteLater );
a->setWeakRef( a.toWeakRef() );
s_albumsByName.insert( key, a );
+ s_albumsByCoverId.insert( a->coverId(), a );
if ( id > 0 )
{
@@ -122,6 +125,18 @@ Album::get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& ar
}
+album_ptr
+Album::getByCoverId( const QString& uuid )
+{
+ QMutexLocker lock( &s_nameCacheMutex );
+
+ if ( s_albumsByCoverId.contains( uuid ) )
+ return s_albumsByCoverId.value( uuid );
+
+ return album_ptr();
+}
+
+
Album::Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist )
: QObject()
, m_waitingForId( false )
@@ -327,6 +342,10 @@ Album::infoSystemInfo( const Tomahawk::InfoSystem::InfoRequestData& requestData,
m_coverBuffer = ba;
}
+ s_albumsByCoverId.remove( coverId() );
+ m_coverId = uuid();
+ s_albumsByCoverId.insert( m_coverId, m_ownRef.toStrongRef() );
+
m_coverLoaded = true;
emit coverChanged();
}
@@ -383,3 +402,13 @@ Album::infoid() const
return m_uuid;
}
+
+
+QString
+Album::coverId() const
+{
+ if ( m_coverId.isEmpty() )
+ m_coverId = uuid();
+
+ return m_coverId;
+}
diff --git a/src/libtomahawk/Album.h b/src/libtomahawk/Album.h
index a137f6b9f1..dd164259d5 100644
--- a/src/libtomahawk/Album.h
+++ b/src/libtomahawk/Album.h
@@ -45,12 +45,14 @@ Q_OBJECT
public:
static album_ptr get( const Tomahawk::artist_ptr& artist, const QString& name, bool autoCreate = false );
static album_ptr get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist );
+ static album_ptr getByCoverId( const QString& uuid );
Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist );
Album( const QString& name, const Tomahawk::artist_ptr& artist );
virtual ~Album();
unsigned int id() const;
+ QString coverId() const;
QString name() const { return m_name; }
QString sortname() const { return m_sortname; }
@@ -98,6 +100,7 @@ private slots:
mutable bool m_coverLoaded;
mutable bool m_coverLoading;
mutable QString m_uuid;
+ mutable QString m_coverId;
mutable QByteArray m_coverBuffer;
#ifndef ENABLE_HEADLESS
@@ -110,6 +113,7 @@ private slots:
static QHash< QString, album_wptr > s_albumsByName;
static QHash< unsigned int, album_wptr > s_albumsById;
+ static QHash< QString, album_ptr > s_albumsByCoverId;
friend class ::IdThreadWorker;
};
diff --git a/src/libtomahawk/Artist.cpp b/src/libtomahawk/Artist.cpp
index 9a07233d72..60b3bebe18 100644
--- a/src/libtomahawk/Artist.cpp
+++ b/src/libtomahawk/Artist.cpp
@@ -40,6 +40,7 @@ using namespace Tomahawk;
QHash< QString, artist_wptr > Artist::s_artistsByName = QHash< QString, artist_wptr >();
QHash< unsigned int, artist_wptr > Artist::s_artistsById = QHash< unsigned int, artist_wptr >();
+QHash< QString, artist_ptr > Artist::s_artistsByCoverId = QHash< QString, artist_ptr >();
static QMutex s_nameCacheMutex;
static QReadWriteLock s_idMutex;
@@ -79,6 +80,7 @@ Artist::get( const QString& name, bool autoCreate )
artist->setWeakRef( artist.toWeakRef() );
artist->loadId( autoCreate );
s_artistsByName.insert( key, artist );
+ s_artistsByCoverId.insert( artist->coverId(), artist );
return artist;
}
@@ -112,6 +114,7 @@ Artist::get( unsigned int id, const QString& name )
artist_ptr a = artist_ptr( new Artist( id, name ), &Artist::deleteLater );
a->setWeakRef( a.toWeakRef() );
s_artistsByName.insert( key, a );
+ s_artistsByCoverId.insert( a->coverId(), a );
if ( id > 0 )
{
@@ -124,6 +127,18 @@ Artist::get( unsigned int id, const QString& name )
}
+artist_ptr
+Artist::getByCoverId( const QString& uuid )
+{
+ QMutexLocker lock( &s_nameCacheMutex );
+
+ if ( s_artistsByCoverId.contains( uuid ) )
+ return s_artistsByCoverId.value( uuid );
+
+ return artist_ptr();
+}
+
+
Artist::Artist( unsigned int id, const QString& name )
: QObject()
, m_waitingForFuture( false )
@@ -525,6 +540,10 @@ Artist::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVari
m_coverBuffer = ba;
}
+ s_artistsByCoverId.remove( coverId() );
+ m_coverId = uuid();
+ s_artistsByCoverId.insert( m_coverId, m_ownRef.toStrongRef() );
+
m_coverLoaded = true;
emit coverChanged();
}
@@ -686,3 +705,13 @@ Artist::infoid() const
return m_uuid;
}
+
+
+QString
+Artist::coverId() const
+{
+ if ( m_coverId.isEmpty() )
+ m_coverId = uuid();
+
+ return m_coverId;
+}
diff --git a/src/libtomahawk/Artist.h b/src/libtomahawk/Artist.h
index 4903889c74..3f51d47ea6 100644
--- a/src/libtomahawk/Artist.h
+++ b/src/libtomahawk/Artist.h
@@ -44,12 +44,14 @@ Q_OBJECT
public:
static artist_ptr get( const QString& name, bool autoCreate = false );
static artist_ptr get( unsigned int id, const QString& name );
+ static artist_ptr getByCoverId( const QString& uuid );
Artist( unsigned int id, const QString& name );
explicit Artist( const QString& name );
virtual ~Artist();
unsigned int id() const;
+ QString coverId() const;
QString name() const { return m_name; }
QString sortname() const { return m_sortname; }
@@ -122,6 +124,7 @@ private slots:
bool m_biographyLoaded;
mutable QString m_uuid;
+ mutable QString m_coverId;
mutable int m_infoJobs;
QList m_databaseAlbums;
@@ -144,6 +147,7 @@ private slots:
static QHash< QString, artist_wptr > s_artistsByName;
static QHash< unsigned int, artist_wptr > s_artistsById;
+ static QHash< QString, artist_ptr > s_artistsByCoverId;
friend class ::IdThreadWorker;
};
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index a168b34834..54c07c6fc2 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -80,19 +80,21 @@ set( libGuiSources
playlist/PlayableItem.cpp
playlist/SingleTrackPlaylistInterface.cpp
+ playlist/dynamic/GeneratorInterface.cpp
playlist/dynamic/DynamicPlaylist.cpp
playlist/dynamic/DynamicView.cpp
playlist/dynamic/DynamicModel.cpp
playlist/dynamic/echonest/EchonestGenerator.cpp
playlist/dynamic/echonest/EchonestControl.cpp
- playlist/dynamic/echonest/EchonestSteerer.cpp
- playlist/dynamic/widgets/DynamicWidget.cpp
+# playlist/dynamic/echonest/EchonestSteerer.cpp
+# playlist/dynamic/widgets/DynamicWidget.cpp
+ playlist/dynamic/widgets/DynamicQmlWidget.cpp
playlist/dynamic/widgets/DynamicControlWrapper.cpp
- playlist/dynamic/widgets/DynamicControlList.cpp
+# playlist/dynamic/widgets/DynamicControlList.cpp
playlist/dynamic/widgets/ReadOrWriteWidget.cpp
playlist/dynamic/widgets/MiscControlWidgets.cpp
- playlist/dynamic/widgets/CollapsibleControls.cpp
- playlist/dynamic/widgets/DynamicSetupWidget.cpp
+# playlist/dynamic/widgets/CollapsibleControls.cpp
+# playlist/dynamic/widgets/DynamicSetupWidget.cpp
resolvers/ExternalResolverGui.cpp
resolvers/ScriptResolver.cpp
@@ -126,6 +128,8 @@ set( libGuiSources
utils/ResultUrlChecker.cpp
utils/NetworkReply.cpp
+ widgets/DeclarativeCoverArtProvider.cpp
+ widgets/DeclarativeView.cpp
widgets/AnimatedCounterLabel.cpp
widgets/BasicHeader.cpp
widgets/FilterHeader.cpp
@@ -329,7 +333,7 @@ list(APPEND libSources
playlist/dynamic/GeneratorInterface.cpp
playlist/dynamic/DynamicPlaylistRevision.cpp
playlist/XspfUpdater.cpp
- playlist/dynamic/database/DatabaseGenerator.cpp
+# playlist/dynamic/database/DatabaseGenerator.cpp
playlist/dynamic/database/DatabaseControl.cpp
playlist/dynamic/DynamicControl.cpp
@@ -519,6 +523,7 @@ TARGET_LINK_LIBRARIES( tomahawklib
${QT_QTXML_LIBRARY}
${QT_QTSVG_LIBRARY}
${QT_QTCORE_LIBRARY}
+ ${QT_QTDECLARATIVE_LIBRARY}
${OS_SPECIFIC_LINK_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${LINK_LIBRARIES}
diff --git a/src/libtomahawk/GlobalActionManager.cpp b/src/libtomahawk/GlobalActionManager.cpp
index 397892e619..94fea71975 100644
--- a/src/libtomahawk/GlobalActionManager.cpp
+++ b/src/libtomahawk/GlobalActionManager.cpp
@@ -228,7 +228,10 @@ GlobalActionManager::copyPlaylistToClipboard( const dynplaylist_ptr& playlist )
TomahawkUtils::urlAddQueryItem( link, "type", "echonest" );
TomahawkUtils::urlAddQueryItem( link, "title", playlist->title() );
- QList< dyncontrol_ptr > controls = playlist->generator()->controls();
+ Q_ASSERT( false );
+ //FIXME
+/*
+ QVariantList controls = playlist->generator()->controls();
foreach ( const dyncontrol_ptr& c, controls )
{
if ( c->selectedType() == "Artist" )
@@ -255,7 +258,7 @@ GlobalActionManager::copyPlaylistToClipboard( const dynplaylist_ptr& playlist )
TomahawkUtils::urlAddQueryItem( link, name, c->input() );
}
- }
+ }*/
QClipboard* cb = QApplication::clipboard();
QByteArray data = percentEncode( link );
@@ -889,7 +892,8 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
return Tomahawk::dynplaylist_ptr();
}
- if ( parts[ 0 ] == "create" )
+ Q_ASSERT( false );
+/* if ( parts[ 0 ] == "create" )
{
if ( !urlHasQueryItem( url, "title" ) || !urlHasQueryItem( url, "type" ) )
{
@@ -1060,7 +1064,7 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
ViewManager::instance()->show( pl );
return pl;
- }
+ }*/
return Tomahawk::dynplaylist_ptr();
}
diff --git a/src/libtomahawk/Track.cpp b/src/libtomahawk/Track.cpp
index df899d1649..cc04f335ef 100644
--- a/src/libtomahawk/Track.cpp
+++ b/src/libtomahawk/Track.cpp
@@ -528,10 +528,25 @@ Track::coverLoaded() const
return m_artistPtr->coverLoaded();
}
-
#endif
+QString
+Track::coverId() const
+{
+ if ( m_albumPtr && m_albumPtr->coverLoaded() && !m_albumPtr->cover( QSize( 0, 0 ) ).isNull() )
+ {
+ return m_albumPtr->coverId();
+ }
+ else if ( m_artistPtr )
+ {
+ return m_artistPtr->coverId();
+ }
+
+ return QString();
+}
+
+
QList
Track::similarTracks() const
{
diff --git a/src/libtomahawk/Track.h b/src/libtomahawk/Track.h
index a5b05093af..2bb82557e1 100644
--- a/src/libtomahawk/Track.h
+++ b/src/libtomahawk/Track.h
@@ -86,6 +86,7 @@ friend class ::DatabaseCommand_LoadInboxEntries; // for setAllSocialActions
QPixmap cover( const QSize& size, bool forceLoad = true ) const;
#endif
bool coverLoaded() const;
+ QString coverId() const;
void setLoved( bool loved, bool postToInfoSystem = true );
bool loved();
diff --git a/src/libtomahawk/ViewManager.cpp b/src/libtomahawk/ViewManager.cpp
index 7655d54eaa..4899b9e6f1 100644
--- a/src/libtomahawk/ViewManager.cpp
+++ b/src/libtomahawk/ViewManager.cpp
@@ -44,7 +44,8 @@
#include "playlist/InboxView.h"
#include "playlist/PlaylistLargeItemDelegate.h"
#include "playlist/RecentlyPlayedModel.h"
-#include "playlist/dynamic/widgets/DynamicWidget.h"
+//#include "playlist/dynamic/widgets/DynamicWidget.h"
+#include "playlist/dynamic/widgets/DynamicQmlWidget.h"
#include "widgets/NewReleasesWidget.h"
#include "widgets/Dashboard.h"
@@ -85,6 +86,7 @@ ViewManager::ViewManager( QObject* parent )
, m_newReleasesWidget( 0 )
, m_recentPlaysWidget( 0 )
, m_inboxWidget( 0 )
+ , m_radioView( 0 )
, m_networkActivityWidget( 0 )
, m_currentPage( 0 )
{
@@ -195,8 +197,8 @@ ViewManager::playlistForPage( ViewPage* page ) const
{
p = dynamic_cast< PlaylistView* >( page )->playlistModel()->playlist();
}
- else if ( dynamic_cast< DynamicWidget* >( page ) )
- p = dynamic_cast< DynamicWidget* >( page )->playlist();
+ else if ( dynamic_cast< DynamicQmlWidget* >( page ) )
+ p = dynamic_cast< DynamicQmlWidget* >( page )->playlist();
return p;
}
@@ -230,7 +232,7 @@ ViewManager::show( const Tomahawk::dynplaylist_ptr& playlist )
{
if ( !m_dynamicWidgets.contains( playlist ) || m_dynamicWidgets.value( playlist ).isNull() )
{
- m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicWidget( playlist, m_stack );
+ m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicQmlWidget( playlist, m_stack );
playlist->resolve();
}
@@ -387,6 +389,22 @@ ViewManager::showSuperCollection()
}
+Tomahawk::ViewPage*
+ViewManager::showRadioPage()
+{
+ if ( !m_radioView )
+ {
+ dynplaylist_ptr playlist = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), QString(), "", SourceList::instance()->getLocal()->friendlyName(), OnDemand, false, QString(), false, true );
+ playlist->setMode( OnDemand );
+
+ m_radioView = new Tomahawk::DynamicQmlWidget( playlist, m_stack );
+ }
+
+ setPage( m_radioView );
+ return m_radioView;
+}
+
+
void
ViewManager::playlistInterfaceChanged( Tomahawk::playlistinterface_ptr interface )
{
@@ -830,7 +848,7 @@ ViewManager::playlistForInterface( Tomahawk::playlistinterface_ptr interface ) c
Tomahawk::dynplaylist_ptr
ViewManager::dynamicPlaylistForInterface( Tomahawk::playlistinterface_ptr interface ) const
{
- foreach ( QPointer view, m_dynamicWidgets.values() )
+ foreach ( QPointer view, m_dynamicWidgets.values() )
{
if ( !view.isNull() && view.data()->playlistInterface() == interface )
{
diff --git a/src/libtomahawk/ViewManager.h b/src/libtomahawk/ViewManager.h
index 3857844bfd..7bdd4db48d 100644
--- a/src/libtomahawk/ViewManager.h
+++ b/src/libtomahawk/ViewManager.h
@@ -64,7 +64,7 @@ class NetworkActivityWidget;
namespace Tomahawk
{
- class DynamicWidget;
+ class DynamicQmlWidget;
}
class DLLEXPORT ViewManager : public QObject
@@ -139,6 +139,7 @@ Q_OBJECT
void viewPageAdded( const QString& pageName, const QString& text, const QIcon& icon );
public slots:
+ Tomahawk::ViewPage* showRadioPage();
Tomahawk::ViewPage* showSuperCollection();
Tomahawk::ViewPage* showDashboard();
Tomahawk::ViewPage* showWhatsHotPage();
@@ -200,6 +201,7 @@ private slots:
NewReleasesWidget* m_newReleasesWidget;
Tomahawk::ViewPage* m_recentPlaysWidget;
Tomahawk::ViewPage* m_inboxWidget;
+ Tomahawk::DynamicQmlWidget* m_radioView;
InboxModel* m_inboxModel;
NetworkActivityWidget* m_networkActivityWidget;
@@ -208,7 +210,7 @@ private slots:
QList< Tomahawk::collection_ptr > m_superCollections;
- QHash< Tomahawk::dynplaylist_ptr, QPointer > m_dynamicWidgets;
+ QHash< Tomahawk::dynplaylist_ptr, QPointer > m_dynamicWidgets;
QHash< Tomahawk::collection_ptr, QPointer > m_collectionViews;
QHash< Tomahawk::artist_ptr, QPointer > m_artistViews;
QHash< Tomahawk::album_ptr, QPointer > m_albumViews;
diff --git a/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.cpp b/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.cpp
index c429f48464..5812178768 100644
--- a/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.cpp
+++ b/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.cpp
@@ -52,7 +52,7 @@ DatabaseCommand_LoadDynamicPlaylistEntries::exec( DatabaseImpl* dbi )
QString type;
GeneratorMode mode;
- QList< QVariantMap > controls;
+ QVariantList controls;
QString playlist_guid;
// qDebug() << "Loading controls..." << revisionGuid();
// qDebug() << "SELECT playlist_revision.playlist, controls, plmode, pltype "
@@ -72,7 +72,7 @@ DatabaseCommand_LoadDynamicPlaylistEntries::exec( DatabaseImpl* dbi )
mode = static_cast( controlsQuery.value( 2 ).toInt() );
QStringList controlIds = v.toStringList();
-// qDebug() << "Got controls in dynamic playlist, loading:" << controlIds << controlsQuery.value(1);
+ tDebug() << "Got controls in dynamic playlist, loading:" << controlIds << controlsQuery.value(1);
foreach( const QString& controlId, controlIds )
{
TomahawkSqlQuery controlQuery = dbi->newquery();
diff --git a/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.h b/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.h
index c15adc0b6e..b812cd63c3 100644
--- a/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.h
+++ b/src/libtomahawk/database/DatabaseCommand_LoadDynamicPlaylistEntries.h
@@ -47,14 +47,14 @@ class DatabaseCommand_LoadDynamicPlaylistEntries : public DatabaseCommand_LoadPl
void done( QString,
bool,
QString,
- QList< QVariantMap>,
+ QVariantList,
bool );
// used when loading a static playlist
void done( QString,
QList< QString >,
QList< QString >,
QString,
- QList< QVariantMap>,
+ QVariantList,
bool,
QMap< QString, Tomahawk::plentry_ptr >,
bool );
diff --git a/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.cpp b/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.cpp
index e79ba39084..15f80f1922 100644
--- a/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.cpp
+++ b/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.cpp
@@ -41,7 +41,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRe
const QList& entries,
const QString& type,
GeneratorMode mode,
- const QList< dyncontrol_ptr >& controls )
+ const QVariantList& controls )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, orderedguids, addedentries, entries )
, m_type( type )
, m_mode( mode )
@@ -58,7 +58,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRe
const QString& oldrev,
const QString& type,
GeneratorMode mode,
- const QList< dyncontrol_ptr >& controls )
+ const QVariantList& controls )
: DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, QStringList(), QList< plentry_ptr >(), QList< plentry_ptr >() )
, m_type( type )
, m_mode( mode )
@@ -72,18 +72,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRe
QVariantList
DatabaseCommand_SetDynamicPlaylistRevision::controlsV()
{
- if ( m_controls.isEmpty() )
- return m_controlsV;
-
- if ( !m_controls.isEmpty() && m_controlsV.isEmpty() )
- {
- foreach ( const dyncontrol_ptr& control, m_controls )
- {
- m_controlsV << QJson::QObjectHelper::qobject2qvariant( control.data() );
- }
- }
-
- return m_controlsV;
+ return m_controls;
}
@@ -126,7 +115,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::postCommitHook()
return;
}
- if ( !m_controlsV.isEmpty() && m_controls.isEmpty() )
+/* if ( !m_controlsV.isEmpty() && m_controls.isEmpty() )
{
QList controlMap;
foreach( const QVariant& v, m_controlsV )
@@ -148,7 +137,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::postCommitHook()
m_addedmap,
m_applied );
}
- else
+ else*/
{
if ( m_mode == OnDemand )
rawPl->setRevision( newrev(),
@@ -180,19 +169,9 @@ DatabaseCommand_SetDynamicPlaylistRevision::exec( DatabaseImpl* lib )
return;
QVariantList newcontrols;
- if ( m_controlsV.isEmpty() && !m_controls.isEmpty() )
+ foreach( const QVariant& v, m_controls )
{
- foreach( const dyncontrol_ptr& control, m_controls )
- {
- newcontrols << control->id();
- }
- }
- else if( !m_controlsV.isEmpty() )
- {
- foreach( const QVariant& v, m_controlsV )
- {
- newcontrols << v.toMap().value( "id" );
- }
+ newcontrols << v.toMap().value( "id" );
}
QJson::Serializer ser;
@@ -220,34 +199,18 @@ DatabaseCommand_SetDynamicPlaylistRevision::exec( DatabaseImpl* lib )
TomahawkSqlQuery controlsQuery = lib->newquery();
controlsQuery.prepare( "INSERT INTO dynamic_playlist_controls( id, playlist, selectedType, match, input ) "
"VALUES( ?, ?, ?, ?, ? )" );
- if ( m_controlsV.isEmpty() && !m_controls.isEmpty() )
- {
- foreach ( const dyncontrol_ptr& control, m_controls )
- {
- qDebug() << "inserting dynamic control:" << control->id() << m_playlistguid << control->selectedType() << control->match() << control->input();
- controlsQuery.addBindValue( control->id() );
- controlsQuery.addBindValue( m_playlistguid );
- controlsQuery.addBindValue( control->selectedType() );
- controlsQuery.addBindValue( control->match() );
- controlsQuery.addBindValue( control->input() );
-
- controlsQuery.exec();
- }
- }
- else
+
+ foreach ( const QVariant& v, m_controls )
{
- foreach ( const QVariant& v, m_controlsV )
- {
- QVariantMap control = v.toMap();
- qDebug() << "inserting dynamic control from JSON:" << control.value( "id" ) << m_playlistguid << control.value( "selectedType" ) << control.value( "match" ) << control.value( "input" );
- controlsQuery.addBindValue( control.value( "id" ) );
- controlsQuery.addBindValue( m_playlistguid );
- controlsQuery.addBindValue( control.value( "selectedType" ) );
- controlsQuery.addBindValue( control.value( "match" ) );
- controlsQuery.addBindValue( control.value( "input" ) );
-
- controlsQuery.exec();
- }
+ QVariantMap control = v.toMap();
+ qDebug() << "inserting dynamic control from JSON:" << control.value( "id" ) << m_playlistguid << control.value( "selectedType" ) << control.value( "match" ) << control.value( "input" );
+ controlsQuery.addBindValue( control.value( "id" ) );
+ controlsQuery.addBindValue( m_playlistguid );
+ controlsQuery.addBindValue( control.value( "selectedType" ) );
+ controlsQuery.addBindValue( control.value( "match" ) );
+ controlsQuery.addBindValue( control.value( "input" ) );
+
+ controlsQuery.exec();
}
if ( m_applied )
@@ -263,6 +226,7 @@ DatabaseCommand_SetDynamicPlaylistRevision::exec( DatabaseImpl* lib )
}
}
+
void
DatabaseCommand_SetDynamicPlaylistRevision::setPlaylist( DynamicPlaylist* pl )
{
diff --git a/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.h b/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.h
index a23f39ddda..6a81d98b6a 100644
--- a/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.h
+++ b/src/libtomahawk/database/DatabaseCommand_SetDynamicPlaylistRevision.h
@@ -45,7 +45,7 @@ class DatabaseCommand_SetDynamicPlaylistRevision : public DatabaseCommand_SetPla
const QList& entries,
const QString& type,
GeneratorMode mode,
- const QList< dyncontrol_ptr >& controls );
+ const QVariantList& controls );
explicit DatabaseCommand_SetDynamicPlaylistRevision( const source_ptr& s,
const QString& playlistguid,
@@ -53,7 +53,7 @@ class DatabaseCommand_SetDynamicPlaylistRevision : public DatabaseCommand_SetPla
const QString& oldrev,
const QString& type,
GeneratorMode mode,
- const QList< dyncontrol_ptr >& controls );
+ const QVariantList& controls );
QString commandname() const { return "setdynamicplaylistrevision"; }
@@ -64,7 +64,7 @@ class DatabaseCommand_SetDynamicPlaylistRevision : public DatabaseCommand_SetPla
void setControlsV( const QVariantList& vlist )
{
- m_controlsV = vlist;
+ m_controls = vlist;
}
QVariantList controlsV();
@@ -80,8 +80,7 @@ class DatabaseCommand_SetDynamicPlaylistRevision : public DatabaseCommand_SetPla
private:
QString m_type;
GeneratorMode m_mode;
- QList< dyncontrol_ptr > m_controls;
- QList< QVariant > m_controlsV;
+ QVariantList m_controls;
// ARG i hate sharedpointers sometimes
DynamicPlaylist* m_playlist; // Only used if setting revision of a non-autoloaded playlist, as those aren't able to be looked up by guid
diff --git a/src/libtomahawk/playlist/PlayableItem.cpp b/src/libtomahawk/playlist/PlayableItem.cpp
index 465575d4c7..dde8371007 100644
--- a/src/libtomahawk/playlist/PlayableItem.cpp
+++ b/src/libtomahawk/playlist/PlayableItem.cpp
@@ -207,6 +207,14 @@ PlayableItem::artistName() const
{
return m_query->track()->artist();
}
+ else if ( !m_album.isNull() )
+ {
+ return m_album->artist()->name();
+ }
+ else if ( !m_artist.isNull() )
+ {
+ return m_artist->name();
+ }
return QString();
}
diff --git a/src/libtomahawk/playlist/PlayableItem.h b/src/libtomahawk/playlist/PlayableItem.h
index b1020074f7..a22c948fb0 100644
--- a/src/libtomahawk/playlist/PlayableItem.h
+++ b/src/libtomahawk/playlist/PlayableItem.h
@@ -31,6 +31,10 @@
class DLLEXPORT PlayableItem : public QObject
{
Q_OBJECT
+Q_PROPERTY(QString name READ name NOTIFY dataChanged)
+Q_PROPERTY(QString artistName READ artistName NOTIFY dataChanged)
+Q_PROPERTY(QString albumName READ albumName NOTIFY dataChanged)
+Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY dataChanged)
public:
~PlayableItem();
diff --git a/src/libtomahawk/playlist/PlayableModel.cpp b/src/libtomahawk/playlist/PlayableModel.cpp
index e6b27596d4..b67c9f269d 100644
--- a/src/libtomahawk/playlist/PlayableModel.cpp
+++ b/src/libtomahawk/playlist/PlayableModel.cpp
@@ -46,6 +46,13 @@ PlayableModel::PlayableModel( QObject* parent, bool loading )
, m_readOnly( true )
, m_loading( loading )
{
+ QHash roleNames;
+ roleNames.insert( ArtistRole, "artistName" );
+ roleNames.insert( TrackRole, "trackName" );
+ roleNames.insert( CoverIDRole, "coverID" );
+ roleNames.insert( IsPlayingRole, "isPlaying" );
+ setRoleNames( roleNames );
+
connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( onPlaybackStarted( Tomahawk::result_ptr ) ), Qt::DirectConnection );
connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ), Qt::DirectConnection );
@@ -143,6 +150,12 @@ PlayableModel::parent( const QModelIndex& child ) const
QVariant
PlayableModel::artistData( const artist_ptr& artist, int role ) const
{
+ if ( role == CoverIDRole )
+ {
+ artist->cover( QSize( 0, 0 ) );
+ return artist->coverId();
+ }
+
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
@@ -153,6 +166,12 @@ PlayableModel::artistData( const artist_ptr& artist, int role ) const
QVariant
PlayableModel::albumData( const album_ptr& album, int role ) const
{
+ if ( role == CoverIDRole )
+ {
+ album->cover( QSize( 0, 0 ) );
+ return album->coverId();
+ }
+
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
@@ -163,6 +182,13 @@ PlayableModel::albumData( const album_ptr& album, int role ) const
QVariant
PlayableModel::queryData( const query_ptr& query, int column, int role ) const
{
+ if ( role == CoverIDRole )
+ {
+ query->track()->cover( QSize( 0, 0 ) );
+ return query->track()->coverId();
+ }
+
+ tDebug() << Q_FUNC_INFO << role;
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
@@ -300,6 +326,11 @@ PlayableModel::data( const QModelIndex& index, int role ) const
switch ( role )
{
+ case IsPlayingRole:
+ {
+ return entry->isPlaying();
+ break;
+ }
case Qt::TextAlignmentRole:
{
return QVariant( columnAlignment( index.column() ) );
@@ -809,6 +840,13 @@ PlayableModel::finishLoading()
}
+PlayableItem*
+PlayableModel::itemFromIndex( int itemIndex ) const
+{
+ return itemFromIndex( index( itemIndex, 0, QModelIndex() ) );
+}
+
+
PlayableItem*
PlayableModel::itemFromIndex( const QModelIndex& index ) const
{
diff --git a/src/libtomahawk/playlist/PlayableModel.h b/src/libtomahawk/playlist/PlayableModel.h
index c148a76f8a..c4968efb9e 100644
--- a/src/libtomahawk/playlist/PlayableModel.h
+++ b/src/libtomahawk/playlist/PlayableModel.h
@@ -120,6 +120,7 @@ Q_OBJECT
virtual void ensureResolved();
+ Q_INVOKABLE PlayableItem* itemFromIndex( int itemIndex ) const;
virtual PlayableItem* itemFromIndex( const QModelIndex& index ) const;
virtual PlayableItem* itemFromQuery( const Tomahawk::query_ptr& query ) const;
virtual PlayableItem* itemFromResult( const Tomahawk::result_ptr& result ) const;
diff --git a/src/libtomahawk/playlist/PlayableProxyModel.cpp b/src/libtomahawk/playlist/PlayableProxyModel.cpp
index 0db565111b..b75c102d76 100644
--- a/src/libtomahawk/playlist/PlayableProxyModel.cpp
+++ b/src/libtomahawk/playlist/PlayableProxyModel.cpp
@@ -627,6 +627,15 @@ PlayableProxyModel::setFilter( const QString& pattern )
}
+PlayableItem*
+PlayableProxyModel::itemFromIndex( int itemIndex ) const
+{
+ // qDebug() << "returning item" << sourceModel()->itemFromIndex( itemIndex )->name();
+ QModelIndex modelIndex = index( itemIndex, 0 );
+ return sourceModel()->itemFromIndex( mapToSource( modelIndex ) );
+}
+
+
void
PlayableProxyModel::setCurrentIndex( const QModelIndex& index )
{
diff --git a/src/libtomahawk/playlist/PlayableProxyModel.h b/src/libtomahawk/playlist/PlayableProxyModel.h
index c3a0d7be07..b49cde20cd 100644
--- a/src/libtomahawk/playlist/PlayableProxyModel.h
+++ b/src/libtomahawk/playlist/PlayableProxyModel.h
@@ -36,7 +36,7 @@ Q_OBJECT
{ Detailed = 0, Short = 1, ShortWithAvatars = 2, Large = 3, Collection = 4 };
enum PlayableProxyModelRole
- { StyleRole = Qt::UserRole + 1, TypeRole };
+ { StyleRole = Qt::UserRole + 100, TypeRole };
explicit PlayableProxyModel ( QObject* parent = 0 );
virtual ~PlayableProxyModel() {}
@@ -68,6 +68,7 @@ Q_OBJECT
virtual int maxVisibleItems() const { return m_maxVisibleItems; }
virtual void setMaxVisibleItems( int items );
+ Q_INVOKABLE virtual PlayableItem* itemFromIndex( int itemIndex ) const;
virtual PlayableItem* itemFromIndex( const QModelIndex& index ) const { return sourceModel()->itemFromIndex( index ); }
virtual PlayableItem* itemFromQuery( const Tomahawk::query_ptr& query ) const { return sourceModel()->itemFromQuery( query ); }
virtual PlayableItem* itemFromResult( const Tomahawk::result_ptr& result ) const { return sourceModel()->itemFromResult( result ); }
diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp
index 57f61dd6fb..bdac2772fe 100644
--- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp
+++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp
@@ -151,17 +151,22 @@ DynamicPlaylist::create( const Tomahawk::source_ptr& author,
GeneratorMode mode,
bool shared,
const QString& type,
- bool autoLoad
+ bool autoLoad,
+ bool temporary
)
{
dynplaylist_ptr dynplaylist = Tomahawk::dynplaylist_ptr( new DynamicPlaylist( author, guid, title, info, creator, type, mode, shared, autoLoad ), &QObject::deleteLater );
dynplaylist->setWeakSelf( dynplaylist.toWeakRef() );
- DatabaseCommand_CreateDynamicPlaylist* cmd = new DatabaseCommand_CreateDynamicPlaylist( author, dynplaylist, autoLoad );
- connect( cmd, SIGNAL(finished()), dynplaylist.data(), SIGNAL(created()) );
- Database::instance()->enqueue( QSharedPointer(cmd) );
- if ( autoLoad )
- dynplaylist->reportCreated( dynplaylist );
+ if ( !temporary )
+ {
+ DatabaseCommand_CreateDynamicPlaylist* cmd = new DatabaseCommand_CreateDynamicPlaylist( author, dynplaylist, autoLoad );
+ connect( cmd, SIGNAL(finished()), dynplaylist.data(), SIGNAL(created()) );
+ Database::instance()->enqueue( QSharedPointer(cmd) );
+ if ( autoLoad )
+ dynplaylist->reportCreated( dynplaylist );
+ }
+
return dynplaylist;
}
@@ -185,7 +190,7 @@ void
DynamicPlaylist::createNewRevision( const QString& newrev,
const QString& oldrev,
const QString& type,
- const QList< dyncontrol_ptr>& controls,
+ const QVariantList& controls,
const QList< plentry_ptr >& entries )
{
Q_ASSERT( m_source->isLocal() || newrev == oldrev );
@@ -233,7 +238,7 @@ void
DynamicPlaylist::createNewRevision( const QString& newrev,
const QString& oldrev,
const QString& type,
- const QList< dyncontrol_ptr>& controls )
+ const QVariantList& controls )
{
Q_ASSERT( m_source->isLocal() || newrev == oldrev );
@@ -271,23 +276,26 @@ DynamicPlaylist::loadRevision( const QString& rev )
setBusy( true );
DatabaseCommand_LoadDynamicPlaylistEntries* cmd = new DatabaseCommand_LoadDynamicPlaylistEntries( rev.isEmpty() ? currentrevision() : rev );
- if ( m_generator->mode() == OnDemand ) {
+ if ( m_generator->mode() == OnDemand )
+ {
connect( cmd, SIGNAL( done( QString,
bool,
QString,
- QList< QVariantMap >,
+ QVariantList,
bool ) ),
SLOT( setRevision( QString,
bool,
QString,
- QList< QVariantMap >,
+ QVariantList,
bool) ) );
- } else if ( m_generator->mode() == Static ) {
+ }
+ else if ( m_generator->mode() == Static )
+ {
connect( cmd, SIGNAL( done( QString,
QList< QString >,
QList< QString >,
QString,
- QList< QVariantMap >,
+ QVariantList,
bool,
QMap< QString, Tomahawk::plentry_ptr >,
bool ) ),
@@ -295,7 +303,7 @@ DynamicPlaylist::loadRevision( const QString& rev )
QList< QString >,
QList< QString >,
QString,
- QList< QVariantMap >,
+ QVariantList,
bool,
QMap< QString, Tomahawk::plentry_ptr >,
bool ) ) );
@@ -376,7 +384,7 @@ DynamicPlaylist::setRevision( const QString& rev,
const QList< QString >& neworderedguids,
const QList< QString >& oldorderedguids,
const QString& type,
- const QList< dyncontrol_ptr >& controls,
+ const QVariantList& controls,
bool is_newest_rev,
const QMap< QString, plentry_ptr >& addedmap,
bool applied )
@@ -391,7 +399,7 @@ DynamicPlaylist::setRevision( const QString& rev,
Q_ARG( QList , neworderedguids ),
Q_ARG( QList , oldorderedguids ),
Q_ARG( QString , type ),
- QGenericArgument( "QList< Tomahawk::dyncontrol_ptr > " , (const void*)&controls ),
+ Q_ARG( QVariantList , controls ),
Q_ARG( bool, is_newest_rev ),
QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr > " , (const void*)&addedmap ),
Q_ARG( bool, applied ) );
@@ -402,6 +410,7 @@ DynamicPlaylist::setRevision( const QString& rev,
m_generator = GeneratorFactory::create( type );
}
+ tDebug() << Q_FUNC_INFO << controls;
m_generator->setControls( controls );
m_generator->setMode( Static );
@@ -421,42 +430,11 @@ DynamicPlaylist::setRevision( const QString& rev,
}
-void
-DynamicPlaylist::setRevision( const QString& rev,
- const QList< QString >& neworderedguids,
- const QList< QString >& oldorderedguids,
- const QString& type,
- const QList< QVariantMap>& controlsV,
- bool is_newest_rev,
- const QMap< QString, Tomahawk::plentry_ptr >& addedmap,
- bool applied )
-{
- if ( QThread::currentThread() != thread() )
- {
- QMetaObject::invokeMethod( this,
- "setRevision",
- Qt::BlockingQueuedConnection,
- Q_ARG( QString, rev ),
- Q_ARG( QList , neworderedguids ),
- Q_ARG( QList , oldorderedguids ),
- Q_ARG( QString , type ),
- QGenericArgument( "QList< QVariantMap > " , (const void*)&controlsV ),
- Q_ARG( bool, is_newest_rev ),
- QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr > " , (const void*)&addedmap ),
- Q_ARG( bool, applied ) );
- return;
- }
-
- QList controls = variantsToControl( controlsV );
- setRevision( rev, neworderedguids, oldorderedguids, type, controls, is_newest_rev, addedmap, applied );
-}
-
-
void
DynamicPlaylist::setRevision( const QString& rev,
bool is_newest_rev,
const QString& type,
- const QList< dyncontrol_ptr >& controls,
+ const QVariantList& controls,
bool applied )
{
if ( QThread::currentThread() != thread() )
@@ -467,7 +445,7 @@ DynamicPlaylist::setRevision( const QString& rev,
Q_ARG( QString, rev ),
Q_ARG( bool, is_newest_rev ),
Q_ARG( QString, type ),
- QGenericArgument( "QList< Tomahawk::dyncontrol_ptr >" , (const void*)&controls ),
+ Q_ARG( QVariantList, controls ),
Q_ARG( bool, applied ) );
return;
}
@@ -478,6 +456,7 @@ DynamicPlaylist::setRevision( const QString& rev,
m_generator = geninterface_ptr( GeneratorFactory::create( type ) );
}
+ tDebug() << Q_FUNC_INFO << controls;
m_generator->setControls( controls );
m_generator->setMode( OnDemand );
@@ -498,32 +477,7 @@ DynamicPlaylist::setRevision( const QString& rev,
}
-void
-DynamicPlaylist::setRevision( const QString& rev,
- bool is_newest_rev,
- const QString& type,
- const QList< QVariantMap >& controlsV,
- bool applied )
-{
- if ( QThread::currentThread() != thread() )
- {
- QMetaObject::invokeMethod( this,
- "setRevision",
- Qt::BlockingQueuedConnection,
- Q_ARG( QString, rev ),
- Q_ARG( bool, is_newest_rev ),
- Q_ARG( QString, type ),
- QGenericArgument( "QList< QVariantMap >" , (const void*)&controlsV ),
- Q_ARG( bool, applied ) );
- return;
- }
-
- QList controls = variantsToControl( controlsV );
- setRevision( rev, is_newest_rev, type, controls, applied );
-}
-
-
-QList< dyncontrol_ptr >
+/*QList< dyncontrol_ptr >
DynamicPlaylist::variantsToControl( const QList< QVariantMap >& controlsV )
{
QList realControls;
@@ -534,7 +488,7 @@ DynamicPlaylist::variantsToControl( const QList< QVariantMap >& controlsV )
realControls << control;
}
return realControls;
-}
+}*/
void
diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h
index 2a86bbb022..5e576c84b3 100644
--- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h
+++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h
@@ -49,10 +49,10 @@ class DatabaseCommand_LoadDynamicPlaylist;
struct DynQueueItem : RevisionQueueItem
{
QString type;
- QList controls;
+ QVariantList controls;
int mode;
- DynQueueItem( const QString& nRev, const QString& oRev, const QString& typ, const QList< dyncontrol_ptr >& ctrls, int m, const QList< plentry_ptr >& e, bool latest ) :
+ DynQueueItem( const QString& nRev, const QString& oRev, const QString& typ, const QVariantList& ctrls, int m, const QList< plentry_ptr >& e, bool latest ) :
RevisionQueueItem( nRev, oRev, e, latest ), type( typ ), controls( ctrls ), mode( m ) {}
};
@@ -85,7 +85,8 @@ class DLLEXPORT DynamicPlaylist : public Tomahawk::Playlist
GeneratorMode mode,
bool shared,
const QString& type = QString(),
- bool autoLoad = true
+ bool autoLoad = true,
+ bool temporary = false
);
static void remove( const dynplaylist_ptr& playlist );
@@ -125,9 +126,9 @@ public slots:
// want to update the playlist from the model?
// generate a newrev using uuid() and call this:
// if this is a static playlist, pass it a new list of entries. implicitly sets mode to static
- void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QList< dyncontrol_ptr>& controls, const QList< plentry_ptr >& entries );
+ void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QVariantList& controls, const QList< plentry_ptr >& entries );
// if it is ondemand, no entries are needed implicitly sets mode to ondemand
- void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QList< dyncontrol_ptr>& controls );
+ void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QVariantList& controls );
void reportCreated( const Tomahawk::dynplaylist_ptr& self );
void reportDeleted( const Tomahawk::dynplaylist_ptr& self );
@@ -142,15 +143,7 @@ public slots:
const QList& neworderedguids,
const QList& oldorderedguids,
const QString& type,
- const QList< QVariantMap >& controls,
- bool is_newest_rev,
- const QMap< QString, Tomahawk::plentry_ptr >& addedmap,
- bool applied );
- void setRevision( const QString& rev,
- const QList& neworderedguids,
- const QList& oldorderedguids,
- const QString& type,
- const QList< Tomahawk::dyncontrol_ptr >& controls,
+ const QVariantList& controls,
bool is_newest_rev,
const QMap< QString, Tomahawk::plentry_ptr >& addedmap,
bool applied );
@@ -158,13 +151,9 @@ public slots:
void setRevision( const QString& rev,
bool is_newest_rev,
const QString& type,
- const QList< QVariantMap>& controls,
- bool applied );
- void setRevision( const QString& rev,
- bool is_newest_rev,
- const QString& type,
- const QList< Tomahawk::dyncontrol_ptr>& controls,
+ const QVariantList& controls,
bool applied );
+
private:
// called from loadAllPlaylists DB cmd via databasecollection (in GUI thread)
explicit DynamicPlaylist( const source_ptr& src,
@@ -192,8 +181,6 @@ public slots:
void checkRevisionQueue();
- QList< dyncontrol_ptr > variantsToControl( const QList< QVariantMap >& controlsV );
-
geninterface_ptr m_generator;
bool m_autoLoad;
diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.cpp
index d14737b07f..ea7048eb30 100644
--- a/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.cpp
+++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.cpp
@@ -23,7 +23,7 @@
using namespace Tomahawk;
-DynamicPlaylistRevision::DynamicPlaylistRevision(const PlaylistRevision &other)
+DynamicPlaylistRevision::DynamicPlaylistRevision( const PlaylistRevision &other )
{
revisionguid = other.revisionguid;
oldrevisionguid = other.oldrevisionguid;
diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h
index 1001da59fa..96af9273b9 100644
--- a/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h
+++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h
@@ -29,7 +29,7 @@ struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision
{
public:
- QList< dyncontrol_ptr > controls;
+ QVariantList controls;
Tomahawk::GeneratorMode mode;
QString type;
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp
index 75838998e0..d5ed4effcd 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp
+++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp
@@ -40,14 +40,14 @@ GeneratorFactory::create ( const QString& type )
}
-dyncontrol_ptr
+/*dyncontrol_ptr
GeneratorFactory::createControl( const QString& generatorType, const QString& controlType )
{
if( generatorType.isEmpty() || !s_factories.contains( generatorType ) )
return dyncontrol_ptr();
return s_factories.value( generatorType )->createControl( controlType );
-}
+}*/
void
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h
index 3cbb309688..421c39e7af 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h
+++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h
@@ -46,7 +46,7 @@ class DLLEXPORT GeneratorFactoryInterface
* Create a control for this generator, not tied to this generator itself. Used when loading dynamic
* playlists from a dbcmd.
*/
- virtual dyncontrol_ptr createControl( const QString& controlType = QString() ) = 0;
+// virtual dyncontrol_ptr createControl( const QString& controlType = QString() ) = 0;
virtual QStringList typeSelectors() const = 0;
};
@@ -59,7 +59,7 @@ class DLLEXPORT GeneratorFactory
public:
static geninterface_ptr create( const QString& type );
// only used when loading from dbcmd
- static dyncontrol_ptr createControl( const QString& generatorType, const QString& controlType = QString() );
+// static dyncontrol_ptr createControl( const QString& generatorType, const QString& controlType = QString() );
static void registerFactory( const QString& type, GeneratorFactoryInterface* interface );
static QStringList types();
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
index 9cfb12c76f..13805b0c2d 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
+++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
@@ -34,17 +34,6 @@ Tomahawk::GeneratorInterface::~GeneratorInterface()
}
-QList< Tomahawk::dyncontrol_ptr >
-Tomahawk::GeneratorInterface::controls()
-{
-// if( m_controls.isEmpty() ) { // return a default control (so the user can add more)
-// return QList< Tomahawk::dyncontrol_ptr >() << createControl();
-// }
-
- return m_controls;
-}
-
-
QPixmap
Tomahawk::GeneratorInterface::logo()
{
@@ -52,38 +41,15 @@ Tomahawk::GeneratorInterface::logo()
}
-void
-Tomahawk::GeneratorInterface::addControl( const Tomahawk::dyncontrol_ptr& control )
-{
- m_controls << control;
-}
-
-
-void
-Tomahawk::GeneratorInterface::clearControls()
+QVariantList
+Tomahawk::GeneratorInterface::controls() const
{
- m_controls.clear();
+ return m_controls;
}
void
-Tomahawk::GeneratorInterface::setControls( const QList< Tomahawk::dyncontrol_ptr >& controls )
+Tomahawk::GeneratorInterface::setControls( const QVariantList& controls )
{
m_controls = controls;
}
-
-
-void
-Tomahawk::GeneratorInterface::removeControl( const Tomahawk::dyncontrol_ptr& control )
-{
- m_controls.removeAll( control );
-}
-
-
-Tomahawk::dyncontrol_ptr
-Tomahawk::GeneratorInterface::createControl( const QString& type )
-{
- Q_UNUSED( type );
- Q_ASSERT( false );
- return dyncontrol_ptr();
-}
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
index fd0746a5b9..4eb217ee45 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
+++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
@@ -47,6 +47,7 @@ class DLLEXPORT GeneratorInterface : public QObject
{
Q_OBJECT
Q_PROPERTY( QString type READ type )
+ Q_PROPERTY( QString summary READ sentenceSummary)
/// oh qjson.
Q_PROPERTY( int mode READ mode WRITE setMode )
@@ -55,12 +56,6 @@ class DLLEXPORT GeneratorInterface : public QObject
explicit GeneratorInterface( QObject* parent = 0 );
virtual ~GeneratorInterface();
- // Can't make it pure otherwise we can't shove it in QVariants :-/
- // empty QString means use default
- /// The generator will keep track of all the controls it creates. No need to tell it about controls
- /// you ask it to create
- virtual dyncontrol_ptr createControl( const QString& type = QString() );
-
/// A logo to display for this generator, if it has one
virtual QPixmap logo();
@@ -98,17 +93,17 @@ class DLLEXPORT GeneratorInterface : public QObject
*/
virtual bool onDemandSteerable() const { return false; }
+
/**
- * Returns a widget used to steer the OnDemand dynamic playlist.
- * If this generator doesn't support this (and returns false for
- * \c onDemandSteerable) this will be null. The generator is responsible
- * for reacting to changes in the widget.
- *
- * Steering widgets may emit a \c steeringChanged() signal, which will cause the model to toss any
- * upcoming tracks and re-fetch them.
- *
+ * Returns the controls for this station.
*/
- virtual QWidget* steeringWidget() { return 0; }
+ virtual QVariantList controls() const;
+
+ /**
+ * Sets the controls (for example when loaded from database)
+ */
+ virtual void setControls( const QVariantList& controls );
+
/// The type of this generator
QString type() const { return m_type; }
@@ -116,22 +111,22 @@ class DLLEXPORT GeneratorInterface : public QObject
int mode() const { return (int)m_mode; }
void setMode( int mode ) { m_mode = (GeneratorMode)mode; }
- // control functions
- QList< dyncontrol_ptr > controls();
- void addControl( const dyncontrol_ptr& control );
- void clearControls();
- void setControls( const QList< dyncontrol_ptr>& controls );
- void removeControl( const dyncontrol_ptr& control );
+ virtual bool startFromTrack( const Tomahawk::query_ptr& query ) = 0;
+ virtual bool startFromArtist( const Tomahawk::artist_ptr& artist ) = 0;
+ virtual bool startFromGenre( const QString& genre ) = 0;
+ virtual bool startFromYear( int year ) = 0;
+ virtual bool startFromTo( int yearFrom, int yearTo) = 0;
signals:
void error( const QString& title, const QString& body);
void generated( const QList< Tomahawk::query_ptr>& queries );
void nextTrackGenerated( const Tomahawk::query_ptr& track );
+ void summaryChanged();
protected:
QString m_type;
GeneratorMode m_mode;
- QList< dyncontrol_ptr > m_controls;
+ QVariantList m_controls;
};
typedef QSharedPointer geninterface_ptr;
diff --git a/src/libtomahawk/playlist/dynamic/database/DatabaseGenerator.h b/src/libtomahawk/playlist/dynamic/database/DatabaseGenerator.h
index 7513763dc8..ce991026b6 100644
--- a/src/libtomahawk/playlist/dynamic/database/DatabaseGenerator.h
+++ b/src/libtomahawk/playlist/dynamic/database/DatabaseGenerator.h
@@ -36,10 +36,10 @@ namespace Tomahawk
DatabaseFactory() {}
virtual GeneratorInterface* create();
- virtual dyncontrol_ptr createControl( const QString& controlType = QString() );
+// virtual dyncontrol_ptr createControl( const QString& controlType = QString() );
// TO create a special SQL resolver that consists of a pre-baked SQL query and a description of it
- virtual dyncontrol_ptr createControl( const QString& sql, DatabaseCommand_GenericSelect::QueryType type, const QString& summary );
+// virtual dyncontrol_ptr createControl( const QString& sql, DatabaseCommand_GenericSelect::QueryType type, const QString& summary );
virtual QStringList typeSelectors() const;
};
@@ -55,8 +55,8 @@ namespace Tomahawk
explicit DatabaseGenerator( QObject* parent = 0 );
virtual ~DatabaseGenerator();
- virtual dyncontrol_ptr createControl( const QString& type = QString() );
- virtual dyncontrol_ptr createControl( const QString& sql, DatabaseCommand_GenericSelect::QueryType type, const QString& summary );
+/* virtual dyncontrol_ptr createControl( const QString& type = QString() );
+ virtual dyncontrol_ptr createControl( const QString& sql, DatabaseCommand_GenericSelect::QueryType type, const QString& summary );*/
virtual QPixmap logo();
virtual void generate ( int number = -1 );
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
index fdaf4df40f..8a703458f9 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
@@ -61,11 +61,11 @@ EchonestFactory::create()
}
-dyncontrol_ptr
+/*dyncontrol_ptr
EchonestFactory::createControl( const QString& controlType )
{
return dyncontrol_ptr( new EchonestControl( controlType, typeSelectors() ) );
-}
+}*/
QStringList
@@ -160,12 +160,12 @@ EchonestGenerator::setupCatalogs()
// qDebug() << "ECHONEST:" << m_logo.size();
}
-dyncontrol_ptr
+/*dyncontrol_ptr
EchonestGenerator::createControl( const QString& type )
{
m_controls << dyncontrol_ptr( new EchonestControl( type, GeneratorFactory::typeSelectors( m_type ) ) );
return m_controls.last();
-}
+}*/
QPixmap EchonestGenerator::logo()
@@ -176,11 +176,11 @@ QPixmap EchonestGenerator::logo()
void
EchonestGenerator::knownCatalogsChanged()
{
- // Refresh all contrls
+/* // Refresh all contrls
foreach( const dyncontrol_ptr& control, m_controls )
{
control.staticCast< EchonestControl >()->updateWidgetsFromData();
- }
+ }*/
}
@@ -188,10 +188,7 @@ void
EchonestGenerator::generate( int number )
{
// convert to an echonest query, and fire it off
- qDebug() << Q_FUNC_INFO;
- qDebug() << "Generating playlist with" << m_controls.size();
- foreach( const dyncontrol_ptr& ctrl, m_controls )
- qDebug() << ctrl->selectedType() << ctrl->match() << ctrl->input();
+ tDebug() << "Generating playlist with" << m_controls.size();
setProperty( "number", number ); //HACK
@@ -226,6 +223,126 @@ EchonestGenerator::startOnDemand()
}
+bool
+EchonestGenerator::startFromTrack( const Tomahawk::query_ptr& query )
+{
+ tDebug() << "Generating station content by query:" << query->toString();
+
+ Echonest::DynamicPlaylist::PlaylistParamData data;
+ data.first = Echonest::DynamicPlaylist::SongId;
+ data.second = query->track()->artist() + " " + query->track()->track();
+
+ Echonest::DynamicPlaylist::PlaylistParams params;
+ params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::SongRadioType ) );
+ params << data;
+
+ // FIXME!
+
+ return true;
+}
+
+
+bool
+EchonestGenerator::startFromArtist( const Tomahawk::artist_ptr& artist )
+{
+ tDebug() << "Generating station content by artist:" << artist->name();
+
+ if ( !m_dynPlaylist->sessionId().isNull() )
+ {
+ // Running session, delete it
+ QNetworkReply* deleteReply = m_dynPlaylist->deleteSession();
+ connect( deleteReply, SIGNAL( finished() ), deleteReply, SLOT( deleteLater() ) );
+ }
+
+ connect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doStartOnDemand( Echonest::DynamicPlaylist::PlaylistParams ) ) );
+
+ Echonest::DynamicPlaylist::PlaylistParamData data;
+ data.first = Echonest::DynamicPlaylist::Artist;
+ data.second = artist->name();
+
+ Echonest::DynamicPlaylist::PlaylistParams params;
+ params << data;
+
+/* Q_PROPERTY( QString type READ type WRITE setType ) // the generator type associated with this control
+ Q_PROPERTY( QString id READ id WRITE setId )
+ Q_PROPERTY( QString selectedType READ selectedType WRITE setSelectedType )
+ Q_PROPERTY( QString match READ match WRITE setMatch )
+ Q_PROPERTY( QString input READ input WRITE setInput )
+ Q_PROPERTY( QString summary READ summary ) // a summary of the control in phrase form*/
+
+ QVariantMap controlsList;
+ controlsList[ "id" ] = uuid();
+ controlsList[ "selectedType" ] = "echonest";
+ controlsList[ "match" ] = QString::number( data.first );
+ controlsList[ "input" ] = data.second;
+ controlsList[ "summary" ] = tr("Songs from %1").arg(data.second.toString());
+ setControls( QVariantList() << controlsList );
+
+ // params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::SongRadioType ) );
+ emit paramsGenerated( params );
+
+ return true;
+}
+
+
+bool
+EchonestGenerator::startFromGenre( const QString& genre )
+{
+ tDebug() << "Generating station content by genre:" << genre;
+
+ if ( !m_dynPlaylist->sessionId().isNull() )
+ {
+ // Running session, delete it
+ QNetworkReply* deleteReply = m_dynPlaylist->deleteSession();
+ connect( deleteReply, SIGNAL( finished() ), deleteReply, SLOT( deleteLater() ) );
+ }
+
+ connect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doGenerate( Echonest::DynamicPlaylist::PlaylistParams ) ) );
+
+ setProperty( "number", 20 );
+
+ Echonest::DynamicPlaylist::PlaylistParamData data;
+ data.first = Echonest::DynamicPlaylist::Description;
+ data.second = genre;
+
+ Echonest::DynamicPlaylist::PlaylistParams params;
+ params << data;
+
+ QVariantList controlsList;
+ QVariantMap controlsMap;
+
+ controlsMap[ "id" ] = uuid();
+ controlsMap[ "selectedType" ] = "echonest";
+ controlsMap[ "match" ] = QString::number( data.first );
+ controlsMap[ "input" ] = data.second;
+ controlsMap[ "summary" ] = tr("Songs of genre %1").arg(data.second.toString());
+ controlsList << controlsMap;
+
+ params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) );
+ controlsMap[ "id" ] = uuid();
+ controlsMap[ "match" ] = QString::number( Echonest::DynamicPlaylist::Type );
+ controlsMap[ "input" ] = QString::number( Echonest::DynamicPlaylist::ArtistDescriptionType );
+ controlsList << controlsMap;
+
+ setControls( controlsList );
+ emit paramsGenerated( params );
+
+ return true;
+}
+
+bool EchonestGenerator::startFromYear(int year)
+{
+ //TODO: libechonest doesn't support filtering for year yet...
+ return false;
+}
+
+bool EchonestGenerator::startFromTo(int yearFrom, int yearTo)
+{
+ //TODO: libechonest doesn't support filtering for year yet...
+ return false;
+}
+
+
void
EchonestGenerator::doGenerate( const Echonest::DynamicPlaylist::PlaylistParams& paramsIn )
{
@@ -307,22 +424,40 @@ EchonestGenerator::staticFinished()
void
EchonestGenerator::getParams() throw( std::runtime_error )
{
+ /*Echonest::DynamicPlaylist::PlaylistParamData data;
+ data.first = Echonest::DynamicPlaylist::Artist;
+ data.second = artist->name();
+
Echonest::DynamicPlaylist::PlaylistParams params;
- foreach( const dyncontrol_ptr& control, m_controls ) {
- params.append( control.dynamicCast()->toENParam() );
+ params << data;
+ */
+
+ Echonest::DynamicPlaylist::PlaylistParams params;
+ foreach( const QVariant& control, m_controls )
+ {
+ QVariantMap controlMap = control.toMap();
+ Echonest::DynamicPlaylist::PlaylistParamData data;
+ data.first = (Echonest::DynamicPlaylist::PlaylistParam)controlMap[ "match" ].toUInt();
+ data.second = controlMap[ "input" ].toString();
+
+ params.append( data );
}
- if( appendRadioType( params ) == Echonest::DynamicPlaylist::SongRadioType ) {
+ if ( appendRadioType( params ) == Echonest::DynamicPlaylist::SongRadioType )
+ {
// we need to do another pass, converting all song queries to song-ids.
m_storedParams = params;
qDeleteAll( m_waiting );
m_waiting.clear();
// one query per track
- for( int i = 0; i < params.count(); i++ ) {
+ for( int i = 0; i < params.count(); i++ )
+ {
const Echonest::DynamicPlaylist::PlaylistParamData param = params.value( i );
- if( param.first == Echonest::DynamicPlaylist::SongId ) { // this is a song type enum
+ if ( param.first == Echonest::DynamicPlaylist::SongId )
+ {
+ // this is a song type enum
QString text = param.second.toString();
Echonest::Song::SearchParams q;
@@ -336,12 +471,14 @@ EchonestGenerator::getParams() throw( std::runtime_error )
}
}
- if( m_waiting.isEmpty() ) {
+ if ( m_waiting.isEmpty() )
+ {
m_storedParams.clear();
emit paramsGenerated( params );
}
-
- } else {
+ }
+ else
+ {
emit paramsGenerated( params );
}
}
@@ -440,7 +577,7 @@ EchonestGenerator::userCatalogs()
return s_catalogs->catalogs().keys();
}
-bool
+/*bool
EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error )
{
bool only = true;
@@ -460,7 +597,7 @@ EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum
}
return false;
-}
+}*/
Echonest::DynamicPlaylist::ArtistTypeEnum
@@ -477,7 +614,7 @@ EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& p
/// 3. artist-description: If all the artist entries are Description. If some were but not all, error out.
/// 4. artist-radio: If all the artist entries are Similar To. If some were but not all, error out.
/// 5. song-radio: If all the artist entries are Similar To. If some were but not all, error out.
- bool someCatalog = false;
+/* bool someCatalog = false;
bool genreType = false;
foreach( const dyncontrol_ptr& control, m_controls ) {
if ( control->selectedType() == "User Radio" )
@@ -498,7 +635,7 @@ EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& p
else if( onlyThisArtistType( Echonest::DynamicPlaylist::SongRadioType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::SongRadioType ) );
else // no artist or song or description types. default to artist-description
- params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) );
+ params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) );*/
return static_cast< Echonest::DynamicPlaylist::ArtistTypeEnum >( params.last().second.toInt() );
}
@@ -529,7 +666,10 @@ EchonestGenerator::sentenceSummary()
* NOTE / TODO: In order for the sentence to be grammatically correct, we must follow the EN API rules. That means we can't have multiple of some types of filters,
* and all Artist types must be the same. The filters aren't checked at the moment until Generate / Play is pressed. Consider doing a check on hide as well.
*/
- QList< dyncontrol_ptr > allcontrols = m_controls;
+
+ // Keeping this for now to make stuff backwards compatible
+
+/* QList< dyncontrol_ptr > allcontrols = m_controls;
QString sentence = "Songs ";
/// 1. Collect all required filters
@@ -612,7 +752,12 @@ EchonestGenerator::sentenceSummary()
sentence += "and " + sorting.dynamicCast< EchonestControl >()->summary() + ".";
}
- return sentence;
+ return sentence;*/
+
+ if (m_controls.isEmpty()) {
+ return "";
+ }
+ return m_controls.first().toMap().value("summary").toString();
}
void
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
index 5f8777abb0..7fb77cd432 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
@@ -60,7 +60,7 @@ class DLLEXPORT EchonestFactory : public GeneratorFactoryInterface
EchonestFactory();
virtual GeneratorInterface* create();
- virtual dyncontrol_ptr createControl( const QString& controlType = QString() );
+// virtual dyncontrol_ptr createControl( const QString& controlType = QString() );
virtual QStringList typeSelectors() const;
};
@@ -71,7 +71,7 @@ class DLLEXPORT EchonestGenerator : public GeneratorInterface
explicit EchonestGenerator( QObject* parent = 0 );
virtual ~EchonestGenerator();
- virtual dyncontrol_ptr createControl( const QString& type = QString() );
+// virtual dyncontrol_ptr createControl( const QString& type = QString() );
virtual QPixmap logo();
virtual void generate ( int number = -1 );
virtual void startOnDemand();
@@ -80,6 +80,12 @@ class DLLEXPORT EchonestGenerator : public GeneratorInterface
virtual bool onDemandSteerable() const { return false; }
virtual QWidget* steeringWidget() { return 0; }
+ virtual bool startFromTrack( const Tomahawk::query_ptr& query );
+ virtual bool startFromArtist( const Tomahawk::artist_ptr& artist );
+ virtual bool startFromGenre( const QString& genre );
+ virtual bool startFromYear( int year );
+ virtual bool startFromTo( int yearFrom, int yearTo );
+
static QStringList styles();
static QStringList moods();
static QStringList genres();
@@ -118,7 +124,7 @@ private slots:
query_ptr queryFromSong( const Echonest::Song& song );
Echonest::DynamicPlaylist::ArtistTypeEnum appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error );
- bool onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error );
+// bool onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error );
void loadStylesMoodsAndGenres();
diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp
new file mode 100644
index 0000000000..95fbca06a0
--- /dev/null
+++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp
@@ -0,0 +1,268 @@
+#include "DynamicQmlWidget.h"
+
+#include "playlist/dynamic/DynamicModel.h"
+#include "playlist/PlayableProxyModel.h"
+#include "playlist/dynamic/DynamicModel.h"
+#include "playlist/dynamic/echonest/EchonestControl.h"
+#include "playlist/dynamic/GeneratorInterface.h"
+#include "playlist/PlayableItem.h"
+#include "Source.h"
+#include "SourceList.h"
+#include "audio/AudioEngine.h"
+#include "database/Database.h"
+#include "database/DatabaseCommand_CreateDynamicPlaylist.h"
+#include "database/DatabaseCommand_PlaybackCharts.h"
+#include "widgets/DeclarativeCoverArtProvider.h"
+#include "utils/TomahawkUtilsGui.h"
+#include "utils/Logger.h"
+#include "utils/TomahawkCache.h"
+
+#include
+#include
+#include
+#include
+
+namespace Tomahawk
+{
+
+DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* parent )
+ : DeclarativeView( parent )
+ , m_playlist( playlist )
+ , m_playNextResolved( false )
+{
+ m_model = new DynamicModel( this );
+
+ m_proxyModel = new PlayableProxyModel( this );
+ m_proxyModel->setSourcePlayableModel( m_model );
+ m_proxyModel->setShowOfflineResults( false );
+
+ m_model->loadPlaylist( m_playlist );
+
+ m_artistChartsModel = new PlayableModel( this );
+
+
+ qmlRegisterUncreatableType("tomahawk", 1, 0, "Generator", "you cannot create it on your own - should be set in context");
+
+ rootContext()->setContextProperty( "dynamicModel", m_proxyModel );
+ rootContext()->setContextProperty( "artistChartsModel", m_artistChartsModel );
+ rootContext()->setContextProperty( "generator", m_playlist->generator().data() );
+ rootContext()->setContextProperty( "currentlyPlayedIndex", QVariant::fromValue( 0 ) );
+
+ setSource( QUrl( "qrc" RESPATH "qml/StationView.qml" ) );
+
+ connect( m_model, SIGNAL( currentIndexChanged()), SLOT( currentIndexChanged() ) );
+ connect( m_model, SIGNAL( changed() ), SIGNAL( titleChanged() ) );
+ connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) );
+ connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( nextTrackGenerated( Tomahawk::query_ptr ) ) );
+ connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) );
+ connect( m_playlist->generator().data(), SIGNAL( error( QString, QString )), SLOT( error(QString,QString) ) );
+
+ if (configured()) {
+ m_playlist->generator()->generate( 20 );
+ } else {
+ // TODO: only load if needed, i.e. the user clicks on start station by artist
+ loadArtistCharts();
+
+ rootContext()->setContextProperty("allGenres", TomahawkUtils::Cache::instance()->getData( "EchonesGenerator", "genres"));
+ }
+}
+
+
+DynamicQmlWidget::~DynamicQmlWidget()
+{
+}
+
+
+Tomahawk::playlistinterface_ptr
+DynamicQmlWidget::playlistInterface() const
+{
+ return m_proxyModel->playlistInterface();
+}
+
+
+QString
+DynamicQmlWidget::title() const
+{
+ if ( !m_playlist->title().isEmpty() ) {
+ return m_playlist->title();
+ }
+ return "Listen to radio";
+}
+
+
+void
+DynamicQmlWidget::setTitle( const QString& title )
+{
+ m_model->setTitle( title );
+ m_playlist->setTitle( title );
+ m_model->playlist()->setTitle( title );
+
+ if ( !m_playlist->loaded() )
+ {
+ tDebug() << "CONTROLS ARE SAVED:" << m_playlist->generator()->controls();
+ DatabaseCommand_CreateDynamicPlaylist* cmd = new DatabaseCommand_CreateDynamicPlaylist( SourceList::instance()->getLocal(), m_playlist, true );
+ Database::instance()->enqueue( QSharedPointer(cmd) );
+
+ m_playlist->reportCreated( m_playlist );
+ m_playlist->createNewRevision( uuid(), m_playlist->currentrevision(), m_playlist->type(), m_playlist->generator()->controls() );
+
+ emit titleChanged();
+ }
+}
+
+
+QString
+DynamicQmlWidget::description() const
+{
+ return m_model->description();
+}
+
+
+QString
+DynamicQmlWidget::iconSource() const
+{
+ return QLatin1String( RESPATH "images/station.png" );
+}
+
+
+bool
+DynamicQmlWidget::jumpToCurrentTrack()
+{
+ return true;
+}
+
+playlist_ptr DynamicQmlWidget::playlist() const
+{
+ return m_model->playlist();
+}
+
+bool DynamicQmlWidget::loading()
+{
+ // Why does isLoading() not reset to true when cleared and station started again?
+// return m_model->isLoading();
+ return m_playNextResolved && m_proxyModel->rowCount() == 0;
+}
+
+bool DynamicQmlWidget::configured()
+{
+// return true;
+ return !m_playlist->generator()->controls().isEmpty();
+}
+
+void DynamicQmlWidget::playItem(int index)
+{
+ tDebug() << "playItem called for cover" << index;
+ AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), m_proxyModel->itemFromIndex( index )->result() );
+}
+
+void DynamicQmlWidget::pause()
+{
+ AudioEngine::instance()->pause();
+}
+
+void DynamicQmlWidget::startStationFromArtist(const QString &artist)
+{
+ m_model->clear();
+ m_playNextResolved = true;
+ m_playlist->generator()->startFromArtist(Artist::get(artist));
+ emit loadingChanged();
+ emit configuredChanged();
+}
+
+void DynamicQmlWidget::startStationFromGenre(const QString &genre)
+{
+ tDebug() << "should start startion from genre" << genre;
+ m_model->clear();
+ m_playNextResolved = true;
+ m_playlist->generator()->startFromGenre( genre );
+ emit loadingChanged();
+ emit configuredChanged();
+}
+
+void DynamicQmlWidget::startStationFromYear(int year)
+{
+ tDebug() << "should start startion from year" << year;
+ m_model->clear();
+ m_playNextResolved = true;
+ m_playlist->generator()->startFromYear( year );
+ emit loadingChanged();
+ emit configuredChanged();
+}
+
+void DynamicQmlWidget::startStationFromTo(int yearFrom, int yearTo)
+{
+ tDebug() << "should start startion from years" << yearFrom << "to" << yearTo;
+ m_model->clear();
+ m_playNextResolved = true;
+ m_playlist->generator()->startFromTo( yearFrom, yearTo );
+ emit loadingChanged();
+ emit configuredChanged();
+}
+
+void DynamicQmlWidget::currentIndexChanged()
+{
+ tDebug() << "current index is" << m_model->currentItem().row();
+ rootContext()->setContextProperty( "currentlyPlayedIndex", m_proxyModel->mapFromSource( m_model->currentItem() ).row() );
+}
+
+void
+DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries )
+{
+ m_model->tracksGenerated( queries, queries.count() );
+ m_playlist->resolve();
+}
+
+void DynamicQmlWidget::nextTrackGenerated(const query_ptr &track)
+{
+ tDebug() << Q_FUNC_INFO << track->toString();
+ m_model->tracksGenerated( QList() << track );
+ m_playlist->resolve();
+
+ connect( track.data(), SIGNAL( resolvingFinished( bool )), SLOT( resolvingFinished( bool ) ) );
+
+}
+
+void DynamicQmlWidget::error(const QString &title, const QString &body)
+{
+ tDebug() << "got a generator error:" << title << body;
+}
+
+void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision)
+{
+ m_playlist->resolve();
+}
+
+void DynamicQmlWidget::resolvingFinished(bool hasResults)
+{
+ Q_UNUSED(hasResults)
+ tDebug() << "next track generated" << m_proxyModel->rowCount() << m_proxyModel->currentIndex().row();
+ if( m_proxyModel->rowCount() <= m_proxyModel->currentIndex().row() + 8 ) {
+ tDebug() << "fetching next one";
+ m_playlist->generator()->fetchNext();
+ }
+
+ if( m_playNextResolved && m_proxyModel->rowCount() > 0 ) {
+ emit loadingChanged();
+ playItem( 0 );
+ m_playNextResolved = false;
+ }
+}
+
+void
+DynamicQmlWidget::loadArtistCharts()
+{
+ DatabaseCommand_PlaybackCharts* cmd = new DatabaseCommand_PlaybackCharts( SourceList::instance()->getLocal(), this );
+ cmd->setLimit( 15 );
+ connect( cmd, SIGNAL( artists( QList ) ), SLOT( onArtistCharts( QList< Tomahawk::artist_ptr > ) ), Qt::UniqueConnection );
+ Database::instance()->enqueue( QSharedPointer( cmd ) );
+}
+
+
+void
+DynamicQmlWidget::onArtistCharts( const QList< Tomahawk::artist_ptr >& artists )
+{
+ m_artistChartsModel->clear();
+ m_artistChartsModel->appendArtists( artists );
+
+}
+}
diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h
new file mode 100644
index 0000000000..397ee47430
--- /dev/null
+++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h
@@ -0,0 +1,105 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Michael Zanetti
+ *
+ * Tomahawk is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tomahawk is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tomahawk. If not, see .
+ */
+
+#ifndef DYNAMIC_QML_WIDGET_H
+#define DYNAMIC_QML_WIDGET_H
+
+#include "ViewPage.h"
+#include "Typedefs.h"
+#include "widgets/DeclarativeView.h"
+
+#include
+
+class PlayableModel;
+class PlayableProxyModel;
+
+namespace Tomahawk
+{
+
+class DynamicModel;
+
+class DynamicQmlWidget : public DeclarativeView, public Tomahawk::ViewPage
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
+ Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
+ Q_PROPERTY(bool configured READ configured NOTIFY configuredChanged)
+
+public:
+ explicit DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* parent = 0 );
+ virtual ~DynamicQmlWidget();
+
+ virtual QWidget* widget() { return this; }
+ virtual Tomahawk::playlistinterface_ptr playlistInterface() const;
+
+ virtual QString title() const;
+ virtual void setTitle(const QString &title);
+ virtual QString description() const;
+ virtual QString iconSource() const;
+
+ virtual bool showInfoBar() const { return false; }
+ virtual bool showModes() const { return false; }
+ virtual bool showFilter() const { return false; }
+
+ virtual bool jumpToCurrentTrack();
+
+ playlist_ptr playlist() const;
+
+ bool loading();
+ bool configured();
+
+signals:
+ void loadingChanged();
+ void configuredChanged();
+ void titleChanged();
+
+public slots:
+ void playItem(int index);
+ void pause();
+ void startStationFromArtist(const QString &artist);
+ void startStationFromGenre(const QString &genre);
+ void startStationFromYear(int year);
+ void startStationFromTo(int yearFrom, int yearTo);
+
+private slots:
+ void currentIndexChanged();
+ void tracksGenerated( const QList< Tomahawk::query_ptr>& queries );
+ void nextTrackGenerated( const Tomahawk::query_ptr& track );
+ void error( const QString& title, const QString& body);
+
+ void onRevisionLoaded( Tomahawk::DynamicPlaylistRevision );
+
+ void resolvingFinished( bool hasResults );
+
+ void loadArtistCharts();
+ void onArtistCharts( const QList< Tomahawk::artist_ptr >& artists );
+
+private:
+ DynamicModel* m_model;
+ PlayableProxyModel* m_proxyModel;
+ dynplaylist_ptr m_playlist;
+
+ PlayableModel* m_artistChartsModel;
+
+ bool m_playNextResolved;
+};
+
+}
+
+#endif // DYNAMIC_QML_WIDGET_H
diff --git a/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp
new file mode 100644
index 0000000000..49e743c861
--- /dev/null
+++ b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp
@@ -0,0 +1,142 @@
+#include "DeclarativeCoverArtProvider.h"
+#include "playlist/PlayableItem.h"
+#include "playlist/PlayableProxyModel.h"
+#include "Query.h"
+#include "Album.h"
+#include "Artist.h"
+#include "utils/TomahawkUtilsGui.h"
+#include "utils/Logger.h"
+
+#include
+#include
+#include
+#include
+
+namespace Tomahawk
+{
+
+DeclarativeCoverArtProvider::DeclarativeCoverArtProvider( )
+ : QDeclarativeImageProvider( QDeclarativeImageProvider::Pixmap )
+{
+
+}
+
+DeclarativeCoverArtProvider::~DeclarativeCoverArtProvider()
+{
+}
+
+QPixmap DeclarativeCoverArtProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
+{
+ // We always can generate it in the requested size
+ int width = requestedSize.width() > 0 ? requestedSize.width() : 230;
+ int height = requestedSize.height() > 0 ? requestedSize.height() : 230;
+
+ if( size )
+ *size = QSize( width, height );
+
+ QPixmap cover;
+
+ tDebug() << "DeclarativeCoverArtprovider: Getting album art by id:" << id << requestedSize;
+
+ bool mirrored = false;
+ bool labeled = false;
+
+ QString coverId = id;
+ if(coverId.contains("-mirror")) {
+ coverId.remove("-mirror");
+ mirrored = true;
+ }
+ if(coverId.contains("-labels")) {
+ coverId.remove("-labels");
+ labeled = true;
+ }
+
+ artist_ptr artist = Artist::getByCoverId( coverId );
+ if ( !artist.isNull() )
+ {
+ tDebug() << "Returning artist cover:" << artist->cover( *size ).isNull();
+ cover = artist->cover( *size );
+ if ( cover.isNull() )
+ {
+ tDebug() << Q_FUNC_INFO << "Returning default artist image";
+ cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::Original, *size );
+ }
+ }
+
+ if ( cover.isNull() )
+ {
+ album_ptr album = Album::getByCoverId( coverId );
+ if ( !album.isNull() )
+ {
+ tDebug() << "Returning album cover:" << album->cover( *size ).isNull();
+ cover = album->cover( *size );
+ if ( cover.isNull() )
+ {
+ tDebug() << Q_FUNC_INFO << "Returning default album image";
+ cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::Original, *size );
+ }
+ }
+ }
+
+ if ( cover.isNull() )
+ {
+ tDebug() << Q_FUNC_INFO << "Returning default track image";
+ cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultTrackImage, TomahawkUtils::Original, *size );
+ }
+
+ QImage image(*size, QImage::Format_ARGB32);
+
+ if(labeled) {
+ QImage coverImage(*size, QImage::Format_RGB32);
+ QPainter bgPainter(&coverImage);
+ bgPainter.drawPixmap(0, 0, size->width(), size->height(), cover);
+
+ QColor c1;
+ c1.setRgb( 0, 0, 0 );
+ c1.setAlphaF( 0.00 );
+ QColor c2;
+ c2.setRgb( 0, 0, 0 );
+ c2.setAlphaF( 0.88 );
+
+ QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 0, 1 ) );
+ gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 0.6, c2 );
+ gradient.setColorAt( 1.0, c2 );
+
+ bgPainter.setPen( Qt::transparent );
+ bgPainter.setBrush(QBrush(gradient));
+ bgPainter.drawRect(0, size->height() * 0.7, size->width(), size->height() * 0.3);
+ cover = QPixmap::fromImage(coverImage);
+ }
+
+ QPainter painter(&image);
+ if(!mirrored) {
+ image.fill(Qt::white);
+ painter.drawPixmap(0, 0, size->width(), size->height(), cover);
+ } else {
+ image.fill(QColor(0, 0, 0, 0));
+
+ // Lets paint half of the image in a fragment per line
+ int mirrorHeight = size->height() / 2;
+ int fragmentCount = mirrorHeight;
+ int fragmentHeight = mirrorHeight / fragmentCount;
+
+ QPainter::PixmapFragment fragments[fragmentCount];
+
+ qreal fragmentOpacity = 0;
+ int fragmentStartY = size->height() - mirrorHeight;
+ for(int i = 0; i < fragmentCount; ++i) {
+ QPointF point = QPointF(size->width() / 2, fragmentStartY + (fragmentHeight / 2));
+ QRectF sourceRect = QRectF(0, fragmentStartY, size->width(), fragmentHeight);
+ fragments[i] = QPainter::PixmapFragment::create(point, sourceRect, 1, 1, 0, fragmentOpacity);
+ fragmentOpacity += 0.5 / fragmentCount;
+ fragmentStartY += fragmentHeight;
+ }
+ painter.drawPixmapFragments(fragments, fragmentCount, cover);
+ }
+
+ return QPixmap::fromImage(image);
+}
+
+}
diff --git a/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h
new file mode 100644
index 0000000000..3614070959
--- /dev/null
+++ b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h
@@ -0,0 +1,23 @@
+#ifndef DECLARATIVECOVERARTPROVIDER_H
+#define DECLARATIVECOVERARTPROVIDER_H
+
+
+#include "playlist/PlayableProxyModel.h"
+
+#include
+
+
+namespace Tomahawk
+{
+
+class DeclarativeCoverArtProvider: public QDeclarativeImageProvider
+{
+public:
+ DeclarativeCoverArtProvider();
+ ~DeclarativeCoverArtProvider();
+
+ QPixmap requestPixmap( const QString &id, QSize *size, const QSize &requestedSize );
+};
+
+}
+#endif // DECLARATIVECOVERARTPROVIDER_H
diff --git a/src/libtomahawk/widgets/DeclarativeView.cpp b/src/libtomahawk/widgets/DeclarativeView.cpp
new file mode 100644
index 0000000000..9804b70aaf
--- /dev/null
+++ b/src/libtomahawk/widgets/DeclarativeView.cpp
@@ -0,0 +1,58 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Michael Zanetti
+ *
+ * Tomahawk is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tomahawk is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tomahawk. If not, see .
+ */
+
+#include "DeclarativeView.h"
+#include "playlist/PlayableItem.h"
+#include "DeclarativeCoverArtProvider.h"
+#include "utils/TomahawkUtilsGui.h"
+
+#include
+#include
+#include
+
+namespace Tomahawk
+{
+
+DeclarativeView::DeclarativeView( QWidget *parent ):
+ QDeclarativeView( parent )
+{
+
+ // Needed to make the QML contents scale with tomahawk
+ setResizeMode( QDeclarativeView::SizeRootObjectToView );
+
+ // This types seem to be needed everywhere anyways, lets the register here
+ qmlRegisterType( "tomahawk", 1, 0, "PlayableItem");
+// qmlRegisterType("tomahawk", 1, 0, "SearchField");
+
+ // QML image providers will be deleted by the view
+ engine()->addImageProvider( "albumart", new DeclarativeCoverArtProvider() );
+
+ // Register the view itself to make it easy to invoke the view's slots from QML
+ rootContext()->setContextProperty( "mainView", this );
+
+ rootContext()->setContextProperty( "defaultFontSize", TomahawkUtils::defaultFontSize() );
+ rootContext()->setContextProperty( "defaultFontHeight", TomahawkUtils::defaultFontHeight() );
+
+}
+
+DeclarativeView::~DeclarativeView()
+{
+
+}
+
+}
diff --git a/src/libtomahawk/widgets/DeclarativeView.h b/src/libtomahawk/widgets/DeclarativeView.h
new file mode 100644
index 0000000000..06e36e7903
--- /dev/null
+++ b/src/libtomahawk/widgets/DeclarativeView.h
@@ -0,0 +1,61 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Michael Zanetti
+ *
+ * Tomahawk is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tomahawk is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tomahawk. If not, see .
+ */
+
+#ifndef DECLARATIVEVIEW_H
+#define DECLARATIVEVIEW_H
+
+#include
+
+class QAbstractItemModel;
+
+/**
+ * @class This is the main class for Tomahawk's declarative views
+ *
+ * DeclarativeView inherits from QDeclarativeView and registers some
+ * common types, properties and functions used by all of Tomhawk's
+ * declarative views:
+ *
+ * Registered Types:
+ * - PlayableItem
+ *
+ * Set context properties:
+ * - mainView: This view, so you can invoke this view's slots from QML
+ * - defaultFontSize: system default font point size
+ * - defaultFontHeight: system default font pixel height
+ *
+ * It also registers an albumart image provider. You can access album art
+ * in QML with the source url "image://albumart/".
+ * The cover id can be obtained by the CoverIdRole in PlayableModels
+ *
+ * After subclassing this, all you have to do is call setSource() to
+ * load the QML file and optionally setModel().
+ */
+
+namespace Tomahawk
+{
+
+class DeclarativeView: public QDeclarativeView
+{
+ Q_OBJECT
+public:
+ DeclarativeView(QWidget *parent = 0);
+ ~DeclarativeView();
+};
+
+}
+#endif
diff --git a/src/tomahawk/TomahawkApp.cpp b/src/tomahawk/TomahawkApp.cpp
index a169de60b6..9a84baf0d6 100644
--- a/src/tomahawk/TomahawkApp.cpp
+++ b/src/tomahawk/TomahawkApp.cpp
@@ -40,7 +40,7 @@
#include "database/DatabaseResolver.h"
#include "playlist/dynamic/GeneratorFactory.h"
#include "playlist/dynamic/echonest/EchonestGenerator.h"
-#include "playlist/dynamic/database/DatabaseGenerator.h"
+//#include "playlist/dynamic/database/DatabaseGenerator.h"
#include "playlist/XspfUpdater.h"
#include "network/Servent.h"
#include "network/DbSyncConnection.h"
@@ -228,8 +228,8 @@ TomahawkApp::init()
tDebug() << "Init Echonest Factory.";
GeneratorFactory::registerFactory( "echonest", new EchonestFactory );
#endif
- tDebug() << "Init Database Factory.";
- GeneratorFactory::registerFactory( "database", new DatabaseFactory );
+/* tDebug() << "Init Database Factory.";
+ GeneratorFactory::registerFactory( "database", new DatabaseFactory );*/
// Register shortcut handler for this platform
#ifdef Q_WS_MAC
diff --git a/src/tomahawk/sourcetree/SourcesModel.cpp b/src/tomahawk/sourcetree/SourcesModel.cpp
index 8a9b6fdf5f..c298dc0117 100644
--- a/src/tomahawk/sourcetree/SourcesModel.cpp
+++ b/src/tomahawk/sourcetree/SourcesModel.cpp
@@ -313,8 +313,13 @@ SourcesModel::appendGroups()
sc->setSortValue( 1 );
// browse section
+ GenericPageItem* radio = new GenericPageItem( this, m_browse, tr( "Radio" ), ImageRegistry::instance()->icon( RESPATH "images/station.svg" ),
+ boost::bind( &ViewManager::showRadioPage, ViewManager::instance() ),
+ boost::bind( &ViewManager::recentPlaysWidget, ViewManager::instance() ) );
+ radio->setSortValue( 2 );
+
LovedTracksItem* loved = new LovedTracksItem( this, m_browse );
- loved->setSortValue( 2 );
+ loved->setSortValue( 3 );
GenericPageItem* networkActivity = new GenericPageItem( this, m_browse, tr( "Network Activity" ), TomahawkUtils::defaultPixmap( TomahawkUtils::NetworkActivity, TomahawkUtils::Original ),
boost::bind( &ViewManager::showNetworkActivityPage, ViewManager::instance() ),
@@ -341,7 +346,6 @@ SourcesModel::appendGroups()
inbox->setSortValue( 7 );
m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 4 );
-
m_cloudGroup = new GroupItem( this, m_rootItem, tr( "Cloud" ), 5 );
endInsertRows();
diff --git a/src/tomahawk/sourcetree/items/CategoryItems.cpp b/src/tomahawk/sourcetree/items/CategoryItems.cpp
index 3696baa263..ce505f107a 100644
--- a/src/tomahawk/sourcetree/items/CategoryItems.cpp
+++ b/src/tomahawk/sourcetree/items/CategoryItems.cpp
@@ -163,7 +163,7 @@ CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction )
QString firstArtist;
// now we want to add each artist as a filter...
- QList< dyncontrol_ptr > contrls;
+/* QList< dyncontrol_ptr > contrls;
while ( !stream.atEnd() )
{
QString artist;
@@ -183,7 +183,7 @@ CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction )
QString name = firstArtist.isEmpty() ? tr( "New Station" ) : tr( "%1 Station" ).arg( firstArtist );
newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), contrls );
newpl->setProperty( "newname", name );
- connect( newpl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( playlistToRenameLoaded() ) );
+ connect( newpl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( playlistToRenameLoaded() ) );*/
ViewManager::instance()->show( newpl );
return true;
@@ -295,7 +295,7 @@ CategoryAddItem::parsedDroppedTracks( const QList< query_ptr >& tracks )
newpl->setMode( OnDemand );
// now we want to add each query as a song or similar artist filter...
- QList< dyncontrol_ptr > controls;
+/* QList< dyncontrol_ptr > controls;
foreach ( const Tomahawk::query_ptr& q, tracks )
{
dyncontrol_ptr c = newpl->generator()->createControl( "Song" );
@@ -303,7 +303,7 @@ CategoryAddItem::parsedDroppedTracks( const QList< query_ptr >& tracks )
controls << c;
}
- newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), controls );
+ newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), controls );*/
ViewManager::instance()->show( newpl );
connect( newpl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( playlistToRenameLoaded() ) );
diff --git a/src/tomahawk/sourcetree/items/SourceItem.cpp b/src/tomahawk/sourcetree/items/SourceItem.cpp
index bea7219813..4aca89cf1c 100644
--- a/src/tomahawk/sourcetree/items/SourceItem.cpp
+++ b/src/tomahawk/sourcetree/items/SourceItem.cpp
@@ -107,7 +107,7 @@ SourceItem::SourceItem( SourcesModel* mdl, SourceTreeItem* parent, const Tomahaw
}
if ( !stations.isEmpty() || source->isLocal() )
{
- m_stations = new CategoryItem( model(), this, SourcesModel::StationsCategory, source->isLocal() );
+ m_stations = new CategoryItem( model(), this, SourcesModel::StationsCategory, false /* source->isLocal() */ );
onStationsAdded( stations );
}
@@ -338,14 +338,14 @@ void
SourceItem::playlistsAddedInternal( SourceTreeItem* parent, const QList< dynplaylist_ptr >& playlists )
{
QList< SourceTreeItem* > items;
- int addOffset = playlists.first()->author()->isLocal() ? 1 : 0;
+ int addOffset = 0; //playlists.first()->author()->isLocal() ? 1 : 0;
int from = parent->children().count() - addOffset;
parent->beginRowsAdded( from, from + playlists.count() - 1 );
foreach ( const dynplaylist_ptr& p, playlists )
{
DynamicPlaylistItem* plItem = new DynamicPlaylistItem( model(), parent, p, parent->children().count() - addOffset );
-// qDebug() << "Dynamic Playlist added:" << p->title() << p->creator() << p->info();
+// tDebug() << "Dynamic Playlist added:" << p->title() << p->creator() << p->info();
p->loadRevision();
items << plItem;
@@ -515,7 +515,7 @@ SourceItem::onStationsAdded( const QList< dynplaylist_ptr >& stations )
// add the category too
int cur = children().count();
beginRowsAdded( cur, cur );
- m_stations = new CategoryItem( model(), this, SourcesModel::StationsCategory, source()->isLocal() );
+ m_stations = new CategoryItem( model(), this, SourcesModel::StationsCategory, false /* source()->isLocal() */ );
endRowsAdded();
}