diff --git a/src/models/filebrowsermodel.cpp b/src/models/filebrowsermodel.cpp new file mode 100644 index 00000000..dbcd171c --- /dev/null +++ b/src/models/filebrowsermodel.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include "filebrowsermodel.h" + +FileBrowserModel::FileBrowserModel(QObject *parent) : KDirModel(parent) +{ + QMimeDatabase db; + QList mimeList = db.allMimeTypes(); + QStringList mimeTypes; + mimeTypes << QStringLiteral("inode/directory"); + foreach (const QMimeType &mime, mimeList) { + if (mime.name().startsWith(QStringLiteral("audio/"))) { + mimeTypes << mime.name(); + } + } + + dirLister()->setMimeFilter(mimeTypes); +} + +FileBrowserModel::~FileBrowserModel() += default; + +QString FileBrowserModel::url() const +{ + return dirLister()->url().toString(); +} + +void FileBrowserModel::setUrl(const QString &url) +{ + QString path = QUrl(url).path(); + path = QUrl::fromLocalFile(path).toString(); + + if (dirLister()->url().path() == QUrl(path).path()) { + dirLister()->updateDirectory(QUrl(path)); + return; + } + + beginResetModel(); + dirLister()->openUrl(QUrl(path)); + + endResetModel(); + emit urlChanged(); +} + +QHash FileBrowserModel::roleNames() const +{ + auto roles = KDirModel::roleNames(); + + roles[static_cast(ColumnsRoles::NameRole)] = "name"; + roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::DirectoryRole)] = "directory"; + + return roles; +} + +QVariant FileBrowserModel::data(const QModelIndex &index, int role) const +{ + auto result = QVariant(); + + if (role < ColumnsRoles::NameRole) { + result = KDirModel::data(index,role); + } + + switch(role) + { + case ColumnsRoles::NameRole: + { + KFileItem item = itemForIndex(index); + result = item.name(); + break; + } + case ColumnsRoles::ContainerDataRole: + { + KFileItem item = itemForIndex(index); + result = item.url(); + break; + } + case ColumnsRoles::ImageUrlRole: + { + KFileItem item = itemForIndex(index); + if (item.isDir()) { + result = QUrl(QStringLiteral("image://icon/folder")); + } else { + result = QUrl(QStringLiteral("image://icon/audio-x-generic")); + } + break; + } + case ColumnsRoles::DirectoryRole: + KFileItem item = itemForIndex(index); + result = item.isDir(); + break; + } + + return result; +} + +#include "moc_filebrowsermodel.cpp" diff --git a/src/models/filebrowsermodel.h b/src/models/filebrowsermodel.h new file mode 100644 index 00000000..ca4bbcc4 --- /dev/null +++ b/src/models/filebrowsermodel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FILEBROWSERMODEL_H +#define FILEBROWSERMODEL_H + +#include +#include +#include + +class MusicAudioTrack; + +class FileBrowserModel : public KDirModel +{ + Q_OBJECT + +public: + + enum ColumnsRoles { + NameRole = Qt::UserRole + 1, + ContainerDataRole = Qt::UserRole + 2, + ImageUrlRole = Qt::UserRole + 3, + DirectoryRole = Qt::UserRole + 4 + }; + + Q_ENUM(ColumnsRoles) + + explicit FileBrowserModel(QObject *parent = nullptr); + + ~FileBrowserModel() override; + + QString url() const; + + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void setUrl(const QString &url); + +Q_SIGNALS: + + void urlChanged(); + +}; + +#endif //FILEBROWSERMODEL_H diff --git a/src/models/filebrowserproxymodel.cpp b/src/models/filebrowserproxymodel.cpp new file mode 100644 index 00000000..96767b21 --- /dev/null +++ b/src/models/filebrowserproxymodel.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2016-2017 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "filebrowserproxymodel.h" + +#include "filebrowsermodel.h" + +#include +#include +#include +#include +#include "elisautils.h" + +FileBrowserProxyModel::FileBrowserProxyModel(QObject *parent) : KDirSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + mThreadPool.setMaxThreadCount(1); + mFileModel = std::make_unique(); + setSourceModel(mFileModel.get()); + setSortFoldersFirst(true); + sort(Qt::AscendingOrder); + QObject::connect(mFileModel.get(), &FileBrowserModel::urlChanged,this, &FileBrowserProxyModel::urlChanged); + mTopFolder = QDir::homePath(); + openFolder(mTopFolder, true); +} + +FileBrowserProxyModel::~FileBrowserProxyModel() +{ +} + +QString FileBrowserProxyModel::filterText() const +{ + return mFilterText; +} + +void FileBrowserProxyModel::setFilterText(const QString &filterText) +{ + QWriteLocker writeLocker(&mDataLock); + + if (mFilterText == filterText) + return; + + mFilterText = filterText; + + mFilterExpression.setPattern(mFilterText); + mFilterExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + mFilterExpression.optimize(); + + invalidate(); + + Q_EMIT filterTextChanged(mFilterText); +} + +bool FileBrowserProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + bool result = false; + + for (int column = 0, columnCount = sourceModel()->columnCount(source_parent); column < columnCount; ++column) { + auto currentIndex = sourceModel()->index(source_row, column, source_parent); + + const auto &nameValue = sourceModel()->data(currentIndex, FileBrowserModel::NameRole).toString(); + + if (mFilterExpression.match(nameValue).hasMatch()) { + result = true; + continue; + } + + if (result) { + continue; + } + + if (!result) { + break; + } + } + + return result; +} + +void FileBrowserProxyModel::enqueueToPlayList() +{ + qDebug() << "enqueue"; + QtConcurrent::run(&mThreadPool, [=] () { + QReadLocker locker(&mDataLock); + auto allTrackUrls = QList(); + for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { + auto currentIndex = index(rowIndex, 0); + if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { + allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); + } + } + Q_EMIT filesToEnqueue(allTrackUrls, + ElisaUtils::AppendPlayList, + ElisaUtils::DoNotTriggerPlay); + }); +} + +void FileBrowserProxyModel::replaceAndPlayOfPlayList() +{ + qDebug() << "replace"; + QtConcurrent::run(&mThreadPool, [=] () { + QReadLocker locker(&mDataLock); + auto allTrackUrls = QList(); + for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { + auto currentIndex = index(rowIndex, 0); + if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { + allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); + } + } + Q_EMIT filesToEnqueue(allTrackUrls, + ElisaUtils::ReplacePlayList, + ElisaUtils::TriggerPlay); + }); +} + +QString FileBrowserProxyModel::parentFolder() const +{ + //return to the top folder if parent directory does not exist + QDir dir(mFileModel->dirLister()->url().toLocalFile()); + if (dir.cdUp()) { + return dir.path(); + } else { + return mTopFolder; + } +} + +void FileBrowserProxyModel::openParentFolder() +{ + if (canGoBack()) { + QString parent = parentFolder(); + mFileModel->setUrl(parent); + if (parent == mTopFolder) { + Q_EMIT canGoBackChanged(); + } + } +} + +bool FileBrowserProxyModel::canGoBack() const +{ + return mFileModel->dirLister()->url().toLocalFile() != mTopFolder; +} + +void FileBrowserProxyModel::openFolder(const QString &folder, bool isDisplayRoot) +{ + if (folder.isEmpty()) { + return; + } + mFileModel->setUrl(folder); + if (!isDisplayRoot) { + Q_EMIT canGoBackChanged(); + } +} + +MusicAudioTrack FileBrowserProxyModel::loadMetaDataFromUrl(const QUrl &url) +{ + auto newTrack = ElisaUtils::scanOneFile(url,mMimeDb,mExtractors); + qDebug() << "loaded metadata " << url << newTrack; + return newTrack; +} + +QString FileBrowserProxyModel::url() const +{ + return mFileModel->dirLister()->url().toLocalFile(); +} + +#include "moc_filebrowserproxymodel.cpp" diff --git a/src/models/filebrowserproxymodel.h b/src/models/filebrowserproxymodel.h new file mode 100644 index 00000000..48a8cb86 --- /dev/null +++ b/src/models/filebrowserproxymodel.h @@ -0,0 +1,113 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FILEBROWSERPROXYMODEL_H +#define FILEBROWSERPROXYMODEL_H + +#include +#include +#include +#include +#include + +#include "filebrowsermodel.h" +#include "musicaudiotrack.h" +#include "elisautils.h" + +class FileBrowserProxyModel : public KDirSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QString filterText + READ filterText + WRITE setFilterText + NOTIFY filterTextChanged) + + Q_PROPERTY(bool canGoBack READ canGoBack NOTIFY canGoBackChanged) + + Q_PROPERTY(QString url READ url NOTIFY urlChanged) + +public: + + explicit FileBrowserProxyModel(QObject *parent = nullptr); + + ~FileBrowserProxyModel() override; + + QString filterText() const; + + QString url() const; + + bool canGoBack() const; + + Q_INVOKABLE MusicAudioTrack loadMetaDataFromUrl(const QUrl &url); + +public Q_SLOTS: + + void enqueueToPlayList(); + + void replaceAndPlayOfPlayList(); + + void setFilterText(const QString &filterText); + void openParentFolder(); + + void openFolder(const QString &folder, bool isDisplayRoot = false); + +Q_SIGNALS: + + void filesToEnqueue(QList newFiles, + ElisaUtils::PlayListEnqueueMode enqueueMode, + ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); + + void urlChanged(); + + void canGoBackChanged(); + + void filterTextChanged(const QString &filterText); + +protected: + + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + + QString parentFolder() const; + + QString mTopFolder; + + KFileMetaData::ExtractorCollection mExtractors; + + QMimeDatabase mMimeDb; + + QString mFilterText; + + QRegularExpression mFilterExpression; + + QReadWriteLock mDataLock; + + QThreadPool mThreadPool; + + std::unique_ptr mFileModel; + +}; + +Q_DECLARE_METATYPE(FileBrowserProxyModel) + +#endif // FILEBROWSERPROXYMODEL_H diff --git a/src/qml/FileBrowserDelegate.qml b/src/qml/FileBrowserDelegate.qml new file mode 100644 index 00000000..d21e2733 --- /dev/null +++ b/src/qml/FileBrowserDelegate.qml @@ -0,0 +1,283 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 +import QtQuick.Layouts 1.2 + +import org.kde.elisa 1.0 + +FocusScope { + id: fileDelegate + + property var fileName + property var fileUrl + property var imageUrl + property var contentModel + property bool isDirectory + + signal enqueue(var data) + signal replaceAndPlay(var data) + signal open(var data) + signal selected() + + Controls1.Action { + id: replaceAndPlayAction + text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") + iconName: "media-playback-start" + onTriggered: replaceAndPlay(fileUrl) + } + + Controls1.Action { + id: enqueueAction + text: i18nc("Enqueue current track", "Enqueue") + iconName: "media-track-add-amarok" + onTriggered: enqueue(fileUrl) + } + + Controls1.Action { + id: openAction + text: i18nc("Enqueue current track", "Enqueue") + iconName: 'document-open-folder' + onTriggered: open(fileUrl) + } + + Controls1.Action { + id: viewDetailsAction + text: i18nc("Show track metadata", "View Details") + iconName: "help-about" + onTriggered: { + if (metadataLoader.active === false) { + metadataLoader.active = true + metadataLoader.item.trackDataHelper.trackData = contentModel.loadMetaDataFromUrl(fileUrl) + } + else { + metadataLoader.item.close(); + metadataLoader.active = false + } + } + } + + Loader { + id: metadataLoader + active: false + onLoaded: item.show() + + sourceComponent: MediaTrackMetadataView { + trackDataHelper: TrackDataHelper { + id: dataHelper + } + + onRejected: metadataLoader.active = false; + } + } + + Keys.onReturnPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + Keys.onEnterPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + + ColumnLayout { + anchors.fill: parent + + spacing: 0 + + MouseArea { + id: hoverArea + + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + Layout.preferredHeight: fileDelegate.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + mainLabelSize.height + Layout.fillWidth: true + + onClicked: fileDelegate.selected() + + onDoubleClicked: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + + TextMetrics { + id: mainLabelSize + font: mainLabel.font + text: mainLabel.text + } + + ColumnLayout { + id: mainData + + spacing: 0 + anchors.fill: parent + + Item { + Layout.preferredHeight: fileDelegate.width * 0.85 + Layout.preferredWidth: fileDelegate.width * 0.85 + + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Loader { + id: hoverLoader + active: false + + anchors.centerIn: parent + z: 1 + + opacity: 0 + + sourceComponent: Row { + + Controls1.ToolButton { + id: detailsButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + action: viewDetailsAction + visible: !isDirectory + } + + Controls1.ToolButton { + id: enqueueOpenButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + action: isDirectory ? openAction : enqueueAction + } + + Controls1.ToolButton { + id: replaceAndPlayButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + scale: LayoutMirroring.enabled ? -1 : 1 + action: replaceAndPlayAction + visible: !isDirectory + } + } + } + + Image { + id: icon + anchors.fill: parent + + sourceSize.width: parent.width + sourceSize.height: parent.height + fillMode: Image.PreserveAspectFit + smooth: true + + source: imageUrl + + asynchronous: true + } + } + + LabelWithToolTip { + id: mainLabel + + font.weight: Font.Bold + color: myPalette.text + + horizontalAlignment: Text.AlignLeft + + Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 + Layout.preferredWidth: fileDelegate.width * 0.85 + Layout.maximumHeight: mainLabelSize.height * 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + + text: fileName + wrapMode: Label.Wrap + elide: Text.ElideRight + } + + Item { + Layout.fillHeight: true + } + } + } + + Item { + Layout.fillHeight: true + } + } + + states: [ + State { + name: 'notSelected' + when: !fileDelegate.activeFocus && !hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: icon + opacity: 1 + } + }, + State { + name: 'hoveredOrSelected' + when: fileDelegate.activeFocus || hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: icon + opacity: 0.2 + } + } + ] + + transitions: [ + Transition { + to: 'hoveredOrSelected' + SequentialAnimation { + PropertyAction { + properties: "active" + } + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 100 + } + } + }, + Transition { + to: 'notSelected' + SequentialAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 100 + } + PropertyAction { + properties: "active" + } + } + } + ] +} + diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml new file mode 100644 index 00000000..185bc97e --- /dev/null +++ b/src/qml/FileBrowserView.qml @@ -0,0 +1,155 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 +import QtQuick.Layouts 1.2 + +import org.kde.elisa 1.0 + +FocusScope { + id: fileView + + property bool isSubPage: false + property alias contentModel: contentDirectoryView.model + property alias expandedFilterView: navigationBar.expandedFilterView + + signal filterViewChanged(bool expandedFilterView) + + function goBack() { + contentModel.openParentFolder() + } + + function loadFolderAndClear(data) { + contentModel.openFolder(data) + navigationBar.filterText = "" + } + + SystemPalette { + id: myPalette + colorGroup: SystemPalette.Active + } + + Theme { + id: elisaTheme + } + + MouseArea { + anchors.fill: parent + hoverEnabled: false + acceptedButtons: Qt.BackButton + onClicked: contentModel.openParentFolder() + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + NavigationActionBar { + id: navigationBar + + mainTitle: i18nc("Title of the file browser view", "Files") + image: elisaTheme.folderIcon + secondaryTitle: contentModel.url + enableGoBack: contentModel.canGoBack + showRating: false + + height: elisaTheme.navigationBarHeight + Layout.preferredHeight: height + Layout.minimumHeight: height + Layout.maximumHeight: height + Layout.fillWidth: true + + Binding { + target: contentModel + property: 'filterText' + value: navigationBar.filterText + } + + onEnqueue: contentModel.enqueueToPlayList() + onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() + onGoBack: contentModel.openParentFolder() + onFilterViewChanged: fileView.filterViewChanged(expandedFilterView) + } + + Rectangle { + color: myPalette.base + + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + + GridView { + id: contentDirectoryView + anchors.topMargin: 20 + anchors.fill: parent + + focus: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + + add: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + remove: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + cellWidth: elisaTheme.gridDelegateWidth + cellHeight:elisaTheme.gridDelegateHeight + + delegate: FileBrowserDelegate { + width: contentDirectoryView.cellWidth + height: contentDirectoryView.cellHeight + focus: true + + isDirectory: model.directory + fileName: model.name + fileUrl: model.containerData + imageUrl: model.imageUrl + contentModel: fileView.contentModel + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + onOpen: loadFolderAndClear(data) + } + } + } + } +}