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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + station-artist + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + \ 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 @@ + + + station-genre + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + \ 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 @@ + + + station-year + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + + \ 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(); }