refactor: fully refactor playlist manager
- refactor playlist manager. remove useless qt model design. - update action updator function.
This commit is contained in:
@@ -179,6 +179,11 @@ void ActionManager::updateActionState(PlaylistManager* pm, GraphicsView* gv)
|
|||||||
const auto* gs = gv->scene();
|
const auto* gs = gv->scene();
|
||||||
const auto* settings = Settings::instance();
|
const auto* settings = Settings::instance();
|
||||||
|
|
||||||
|
// Update copy actions
|
||||||
|
bool canCopy = pm->currentIndex().has_value();
|
||||||
|
actionCopyPixmap->setEnabled(canCopy);
|
||||||
|
actionCopyFilePath->setEnabled(canCopy);
|
||||||
|
|
||||||
// Update Prev/Next Picture State
|
// Update Prev/Next Picture State
|
||||||
const int galleryFileCount = pm->totalCount();
|
const int galleryFileCount = pm->totalCount();
|
||||||
const bool loopGallery = settings->loopGallery();
|
const bool loopGallery = settings->loopGallery();
|
||||||
@@ -186,11 +191,9 @@ void ActionManager::updateActionState(PlaylistManager* pm, GraphicsView* gv)
|
|||||||
actionNextPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isLastIndex()));
|
actionNextPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isLastIndex()));
|
||||||
|
|
||||||
// Update Avoid Reset Transform
|
// Update Avoid Reset Transform
|
||||||
actionToggleAvoidResetTransform->setCheckable(gv->avoidResetTransform());
|
actionToggleAvoidResetTransform->setChecked(gv->isAvoidResetTransform());
|
||||||
|
|
||||||
// Update Checkerboard
|
// Update Checkerboard
|
||||||
actionToggleCheckerboard->setChecked(gv->checkerboard());
|
actionToggleCheckerboard->setChecked(gv->isCheckerboard());
|
||||||
|
|
||||||
// Update Animation Graphics Action
|
// Update Animation Graphics Action
|
||||||
bool canPauseAnimation = gs->canPauseAnimation();
|
bool canPauseAnimation = gs->canPauseAnimation();
|
||||||
actionTogglePauseAnimation->setEnabled(canPauseAnimation);
|
actionTogglePauseAnimation->setEnabled(canPauseAnimation);
|
||||||
@@ -202,7 +205,10 @@ void ActionManager::updateActionState(PlaylistManager* pm, GraphicsView* gv)
|
|||||||
bool canSkipAnimation = gs->canSkipAnimationFrame();
|
bool canSkipAnimation = gs->canSkipAnimationFrame();
|
||||||
actionAnimationNextFrame->setEnabled(canSkipAnimation);
|
actionAnimationNextFrame->setEnabled(canSkipAnimation);
|
||||||
|
|
||||||
// TODO: Add more...
|
// Update trash, located in explorer and properties.
|
||||||
|
bool hasLoadedFile = pm->currentIndex().has_value();
|
||||||
|
actionTrash->setEnabled(hasLoadedFile);
|
||||||
|
actionLocateInFileManager->setEnabled(hasLoadedFile);
|
||||||
|
actionProperties->setEnabled(hasLoadedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ private:
|
|||||||
GraphicsScene::GraphicsScene(QObject *parent)
|
GraphicsScene::GraphicsScene(QObject *parent)
|
||||||
: QGraphicsScene(parent)
|
: QGraphicsScene(parent)
|
||||||
{
|
{
|
||||||
showText(tr("Drag image here"));
|
showText(tr("Drag image here", "The same meaning of MainWindow's hint."));
|
||||||
connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, this, &GraphicsScene::updateStyle);
|
connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, this, &GraphicsScene::updateStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ void GraphicsView::setEnableAutoFitToScreen(bool enable)
|
|||||||
m_enableFitToScreen = enable;
|
m_enableFitToScreen = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::avoidResetTransform() const
|
bool GraphicsView::isAvoidResetTransform() const
|
||||||
{
|
{
|
||||||
return m_avoidResetTransform;
|
return m_avoidResetTransform;
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@ void GraphicsView::setAvoidResetTransform(bool avoidReset)
|
|||||||
m_avoidResetTransform = avoidReset;
|
m_avoidResetTransform = avoidReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::checkerboard() const {
|
bool GraphicsView::isCheckerboard() const {
|
||||||
return m_checkerboardEnabled;
|
return m_checkerboardEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ public:
|
|||||||
bool isSceneBiggerThanView() const;
|
bool isSceneBiggerThanView() const;
|
||||||
void setEnableAutoFitToScreen(bool enable = true);
|
void setEnableAutoFitToScreen(bool enable = true);
|
||||||
|
|
||||||
bool avoidResetTransform() const;
|
bool isAvoidResetTransform() const;
|
||||||
void setAvoidResetTransform(bool avoidReset);
|
void setAvoidResetTransform(bool avoidReset);
|
||||||
|
|
||||||
bool checkerboard() const;
|
bool isCheckerboard() const;
|
||||||
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
||||||
|
|
||||||
static QTransform resetScale(const QTransform & orig);
|
static QTransform resetScale(const QTransform & orig);
|
||||||
|
|||||||
17
app/main.cpp
17
app/main.cpp
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include "playlistmanager.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@@ -68,21 +67,19 @@ int main(int argc, char *argv[])
|
|||||||
a.installEventFilter(fileOpenEventHandler);
|
a.installEventFilter(fileOpenEventHandler);
|
||||||
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
||||||
if (w.isHidden()) {
|
if (w.isHidden()) {
|
||||||
w.setWindowOpacity(1);
|
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
} else {
|
} else {
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
}
|
}
|
||||||
w.showUrls({url});
|
w.showFiles({url});
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle dock icon clicks to show hidden window
|
// Handle dock icon clicks to show hidden window
|
||||||
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
||||||
if (state == Qt::ApplicationActive && w.isHidden()) {
|
if (state == Qt::ApplicationActive && w.isHidden()) {
|
||||||
w.showUrls({});
|
w.showFiles({});
|
||||||
w.galleryCurrent(true, true);
|
w.galleryCurrent(false);
|
||||||
w.setWindowOpacity(1);
|
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
w.raise();
|
w.raise();
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
@@ -90,12 +87,8 @@ int main(int argc, char *argv[])
|
|||||||
});
|
});
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
QStringList urlStrList = parser.positionalArguments();
|
QStringList fileList = parser.positionalArguments();
|
||||||
QList<QUrl> && urlList = PlaylistManager::convertToUrlList(urlStrList);
|
w.showFiles(fileList);
|
||||||
|
|
||||||
if (!urlList.isEmpty()) {
|
|
||||||
w.showUrls(urlList);
|
|
||||||
}
|
|
||||||
|
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
this->setWindowIcon(QIcon(u":/icons/app-icon.svg"_s));
|
this->setWindowIcon(QIcon(u":/icons/app-icon.svg"_s));
|
||||||
this->setAcceptDrops(true);
|
this->setAcceptDrops(true);
|
||||||
|
|
||||||
m_pm->setAutoLoadFilterSuffixes(supportedImageFormats());
|
m_pm->setAllowedSuffixes(supportedImageFormats());
|
||||||
|
|
||||||
GraphicsScene * scene = new GraphicsScene(this);
|
GraphicsScene * scene = new GraphicsScene(this);
|
||||||
|
|
||||||
@@ -104,13 +104,13 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
m_am->actionToggleCheckerboard,
|
m_am->actionToggleCheckerboard,
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
connect(m_pm, &PlaylistManager::totalCountChanged, this, &MainWindow::updateGalleryButtonsVisibility);
|
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::updateActionState, this));
|
||||||
|
|
||||||
connect(m_pm->model(), &PlaylistModel::modelReset, this, std::bind(&MainWindow::galleryCurrent, this, false, false));
|
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::galleryCurrent, this, false));
|
||||||
connect(m_pm, &PlaylistManager::currentIndexChanged, this, std::bind(&MainWindow::galleryCurrent, this, true, false));
|
connect(m_pm, &PlaylistManager::currentIndexChanged, this, std::bind(&MainWindow::galleryCurrent, this, false));
|
||||||
|
|
||||||
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [this](){
|
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [this](){
|
||||||
QTimer::singleShot(500, this, std::bind(&MainWindow::galleryCurrent, this, false, true));
|
QTimer::singleShot(500, this, std::bind(&MainWindow::galleryCurrent, this, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
|
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
|
||||||
@@ -130,26 +130,15 @@ MainWindow::~MainWindow()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::showUrls(const QList<QUrl> &urls)
|
void MainWindow::showFiles(const QStringList &files)
|
||||||
{
|
{
|
||||||
if (!urls.isEmpty()) {
|
m_pm->loadPlaylist(files);
|
||||||
const QUrl & firstUrl = urls.first();
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
||||||
if (urls.count() == 1) {
|
}
|
||||||
const QString lowerCaseUrlPath(firstUrl.path().toLower());
|
|
||||||
if (lowerCaseUrlPath.endsWith(".m3u8") || lowerCaseUrlPath.endsWith(".m3u")) {
|
|
||||||
m_pm->loadM3U8Playlist(firstUrl);
|
|
||||||
galleryCurrent(true, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_gv->showFileFromPath(firstUrl.toLocalFile());
|
|
||||||
m_pm->loadPlaylist(urls);
|
|
||||||
} else {
|
|
||||||
m_gv->showText(tr("File url list is empty"));
|
|
||||||
m_pm->setPlaylist(urls);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void MainWindow::showFiles(const QList<QUrl>& urls)
|
||||||
|
{
|
||||||
|
m_pm->loadPlaylist(urls);
|
||||||
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,15 +190,9 @@ void MainWindow::adjustWindowSizeBySceneRect()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// can be empty if it is NOT from a local file.
|
|
||||||
QUrl MainWindow::currentImageFileUrl() const
|
|
||||||
{
|
|
||||||
return m_pm->urlByIndex(m_pm->curIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::clearGallery()
|
void MainWindow::clearGallery()
|
||||||
{
|
{
|
||||||
m_pm->setPlaylist({});
|
m_pm->clearPlaylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::galleryPrev()
|
void MainWindow::galleryPrev()
|
||||||
@@ -217,10 +200,9 @@ void MainWindow::galleryPrev()
|
|||||||
const bool loopGallery = Settings::instance()->loopGallery();
|
const bool loopGallery = Settings::instance()->loopGallery();
|
||||||
if (!loopGallery && m_pm->isFirstIndex()) return;
|
if (!loopGallery && m_pm->isFirstIndex()) return;
|
||||||
|
|
||||||
QModelIndex index = m_pm->previousIndex();
|
auto index = m_pm->previousIndex();
|
||||||
if (index.isValid()) {
|
if (index.has_value()) {
|
||||||
m_pm->setCurrentIndex(index);
|
m_pm->setCurrentIndex(*index);
|
||||||
m_gv->showFileFromPath(m_pm->localFileByIndex(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,31 +211,29 @@ void MainWindow::galleryNext()
|
|||||||
const bool loopGallery = Settings::instance()->loopGallery();
|
const bool loopGallery = Settings::instance()->loopGallery();
|
||||||
if (!loopGallery && m_pm->isLastIndex()) return;
|
if (!loopGallery && m_pm->isLastIndex()) return;
|
||||||
|
|
||||||
QModelIndex index = m_pm->nextIndex();
|
auto index = m_pm->nextIndex();
|
||||||
if (index.isValid()) {
|
if (index.has_value()) {
|
||||||
m_pm->setCurrentIndex(index);
|
m_pm->setCurrentIndex(*index);
|
||||||
m_gv->showFileFromPath(m_pm->localFileByIndex(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use this to update minor information.
|
void MainWindow::galleryCurrent(bool fromFileWatcher)
|
||||||
void MainWindow::galleryCurrent(bool showLoadImageHintWhenEmpty, bool reloadImage)
|
|
||||||
{
|
{
|
||||||
QModelIndex index = m_pm->curIndex();
|
auto index = m_pm->currentIndex();
|
||||||
bool shouldResetfileWatcher = true;
|
if (index.has_value()) {
|
||||||
if (index.isValid()) {
|
QString localFilePath = m_pm->localFileByIndex(*index);
|
||||||
const QString & localFilePath(m_pm->localFileByIndex(index));
|
m_gv->showFileFromPath(localFilePath);
|
||||||
if (reloadImage) m_gv->showFileFromPath(localFilePath);
|
if (!fromFileWatcher) {
|
||||||
shouldResetfileWatcher = !updateFileWatcher(localFilePath);
|
updateFileWatcher(localFilePath);
|
||||||
setWindowTitle(m_pm->urlByIndex(index).fileName());
|
setWindowTitle(m_pm->urlByIndex(*index).fileName());
|
||||||
} else if (showLoadImageHintWhenEmpty && m_pm->totalCount() <= 0) {
|
}
|
||||||
m_gv->showText(QCoreApplication::translate("GraphicsScene", "Drag image here"));
|
} else {
|
||||||
|
m_gv->showText(tr("Drag image here"));
|
||||||
setWindowTitle(QString());
|
setWindowTitle(QString());
|
||||||
|
updateFileWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGalleryButtonsVisibility();
|
updateActionState();
|
||||||
|
|
||||||
if (shouldResetfileWatcher) updateFileWatcher();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MainWindow::supportedImageFormats()
|
QStringList MainWindow::supportedImageFormats()
|
||||||
@@ -361,23 +341,10 @@ void MainWindow::resizeEvent(QResizeEvent *event)
|
|||||||
|
|
||||||
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
||||||
{
|
{
|
||||||
// TODO: Update this function.
|
|
||||||
QMenu * menu = new QMenu;
|
QMenu * menu = new QMenu;
|
||||||
|
|
||||||
// TODO: currentImageFileUrl() may be redundant.
|
menu->addAction(m_am->actionCopyPixmap);
|
||||||
QMenu * copyMenu = new QMenu(tr("&Copy"));
|
menu->addAction(m_am->actionCopyFilePath);
|
||||||
QUrl currentFileUrl = currentImageFileUrl();
|
|
||||||
|
|
||||||
copyMenu->setIcon(QIcon::fromTheme(u"edit-copy"_s));
|
|
||||||
copyMenu->addAction(m_am->actionCopyPixmap);
|
|
||||||
if (currentFileUrl.isValid()) {
|
|
||||||
copyMenu->addAction(m_am->actionCopyFilePath);
|
|
||||||
}
|
|
||||||
if (copyMenu->actions().count() == 1) {
|
|
||||||
menu->addActions(copyMenu->actions());
|
|
||||||
} else {
|
|
||||||
menu->addMenu(copyMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
@@ -402,26 +369,16 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
// TODO: Move checked update to action manager.
|
menu->addAction(m_am->actionToggleAvoidResetTransform);
|
||||||
QAction * avoidResetTransform = m_am->actionToggleAvoidResetTransform;
|
|
||||||
avoidResetTransform->setCheckable(true);
|
|
||||||
avoidResetTransform->setChecked(m_gv->avoidResetTransform());
|
|
||||||
menu->addAction(avoidResetTransform);
|
|
||||||
menu->addAction(m_am->actionToggleCheckerboard);
|
menu->addAction(m_am->actionToggleCheckerboard);
|
||||||
QAction * pauseAnimation = m_am->actionTogglePauseAnimation;
|
menu->addAction(m_am->actionTogglePauseAnimation);
|
||||||
pauseAnimation->setCheckable(true);
|
|
||||||
pauseAnimation->setChecked(m_gv->scene()->pauseAnimation());
|
|
||||||
menu->addAction(pauseAnimation);
|
|
||||||
menu->addAction(m_am->actionAnimationNextFrame);
|
menu->addAction(m_am->actionAnimationNextFrame);
|
||||||
|
|
||||||
if (currentFileUrl.isValid()) {
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
if (currentFileUrl.isLocalFile()) {
|
|
||||||
menu->addAction(m_am->actionTrash);
|
menu->addAction(m_am->actionTrash);
|
||||||
menu->addAction(m_am->actionLocateInFileManager);
|
menu->addAction(m_am->actionLocateInFileManager);
|
||||||
}
|
|
||||||
menu->addAction(m_am->actionProperties);
|
menu->addAction(m_am->actionProperties);
|
||||||
}
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
@@ -430,14 +387,13 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
|
|
||||||
menu->exec(mapToGlobal(event->pos()));
|
menu->exec(mapToGlobal(event->pos()));
|
||||||
menu->deleteLater();
|
menu->deleteLater();
|
||||||
copyMenu->deleteLater();
|
|
||||||
|
|
||||||
return FramelessWindow::contextMenuEvent(event);
|
return FramelessWindow::contextMenuEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
||||||
{
|
{
|
||||||
if (event->mimeData()->hasUrls() || event->mimeData()->hasImage() || event->mimeData()->hasText()) {
|
if (event->mimeData()->hasUrls()) {
|
||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
} else {
|
} else {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
@@ -456,24 +412,8 @@ void MainWindow::dropEvent(QDropEvent *event)
|
|||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
|
|
||||||
const QMimeData * mimeData = event->mimeData();
|
const QMimeData * mimeData = event->mimeData();
|
||||||
|
|
||||||
if (mimeData->hasUrls()) {
|
if (mimeData->hasUrls()) {
|
||||||
const QList<QUrl> &urls = mimeData->urls();
|
showFiles(mimeData->urls());
|
||||||
if (urls.isEmpty()) {
|
|
||||||
m_gv->showText(tr("File url list is empty"));
|
|
||||||
} else {
|
|
||||||
showUrls(urls);
|
|
||||||
}
|
|
||||||
} else if (mimeData->hasImage()) {
|
|
||||||
QImage img = qvariant_cast<QImage>(mimeData->imageData());
|
|
||||||
QPixmap pixmap = QPixmap::fromImage(img);
|
|
||||||
if (pixmap.isNull()) {
|
|
||||||
m_gv->showText(tr("Image data is invalid"));
|
|
||||||
} else {
|
|
||||||
m_gv->showImage(pixmap);
|
|
||||||
}
|
|
||||||
} else if (mimeData->hasText()) {
|
|
||||||
m_gv->showText(mimeData->text());
|
|
||||||
} else {
|
} else {
|
||||||
m_gv->showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
|
m_gv->showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
|
||||||
}
|
}
|
||||||
@@ -504,25 +444,7 @@ void MainWindow::updateWidgetsPosition()
|
|||||||
|
|
||||||
void MainWindow::toggleAvoidResetTransform()
|
void MainWindow::toggleAvoidResetTransform()
|
||||||
{
|
{
|
||||||
m_gv->setAvoidResetTransform(!m_gv->avoidResetTransform());
|
m_gv->setAvoidResetTransform(!m_gv->isAvoidResetTransform());
|
||||||
}
|
|
||||||
|
|
||||||
bool MainWindow::canPaste() const
|
|
||||||
{
|
|
||||||
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
|
|
||||||
if (clipboardData->hasImage()) {
|
|
||||||
return true;
|
|
||||||
} else if (clipboardData->hasText()) {
|
|
||||||
QString clipboardText(clipboardData->text());
|
|
||||||
if (clipboardText.startsWith("PICTURE:")) {
|
|
||||||
QString maybeFilename(clipboardText.mid(8));
|
|
||||||
if (QFile::exists(maybeFilename)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::quitAppAction(bool force)
|
void MainWindow::quitAppAction(bool force)
|
||||||
@@ -563,11 +485,11 @@ void MainWindow::on_actionCopyPixmap_triggered()
|
|||||||
|
|
||||||
void MainWindow::on_actionCopyFilePath_triggered()
|
void MainWindow::on_actionCopyFilePath_triggered()
|
||||||
{
|
{
|
||||||
QUrl currentFileUrl(currentImageFileUrl());
|
auto index = m_pm->currentIndex();
|
||||||
if (currentFileUrl.isValid()) {
|
if (!index.has_value()) return;
|
||||||
|
|
||||||
QClipboard *cb = QApplication::clipboard();
|
QClipboard *cb = QApplication::clipboard();
|
||||||
cb->setText(currentFileUrl.toLocalFile());
|
cb->setText(m_pm->localFileByIndex(*index));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionPrevPicture_triggered()
|
void MainWindow::on_actionPrevPicture_triggered()
|
||||||
@@ -673,15 +595,16 @@ void MainWindow::on_actionTrash_triggered()
|
|||||||
QMessageBox::warning(this, tr("Failed to move file to trash"),
|
QMessageBox::warning(this, tr("Failed to move file to trash"),
|
||||||
tr("Move to trash failed, it might caused by file permission issue, file system limitation, or platform limitation."));
|
tr("Move to trash failed, it might caused by file permission issue, file system limitation, or platform limitation."));
|
||||||
} else {
|
} else {
|
||||||
m_pm->removeAt(index);
|
m_pm->removeFromPlaylist(*index);
|
||||||
galleryCurrent(true, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionLocateInFileManager_triggered()
|
void MainWindow::on_actionLocateInFileManager_triggered()
|
||||||
{
|
{
|
||||||
QUrl currentFileUrl = currentImageFileUrl();
|
auto index = m_pm->currentIndex();
|
||||||
|
if (!index.has_value()) return;
|
||||||
|
QUrl currentFileUrl = m_pm->urlByIndex(*index);
|
||||||
if (!currentFileUrl.isValid()) return;
|
if (!currentFileUrl.isValid()) return;
|
||||||
|
|
||||||
QFileInfo fileInfo(currentFileUrl.toLocalFile());
|
QFileInfo fileInfo(currentFileUrl.toLocalFile());
|
||||||
@@ -716,7 +639,9 @@ void MainWindow::on_actionLocateInFileManager_triggered()
|
|||||||
|
|
||||||
void MainWindow::on_actionProperties_triggered()
|
void MainWindow::on_actionProperties_triggered()
|
||||||
{
|
{
|
||||||
QUrl currentFileUrl = currentImageFileUrl();
|
auto index = m_pm->currentIndex();
|
||||||
|
if (!index.has_value()) return;
|
||||||
|
QUrl currentFileUrl = m_pm->urlByIndex(*index);
|
||||||
if (!currentFileUrl.isValid()) return;
|
if (!currentFileUrl.isValid()) return;
|
||||||
|
|
||||||
MetadataModel * md = new MetadataModel();
|
MetadataModel * md = new MetadataModel();
|
||||||
@@ -750,3 +675,7 @@ bool MainWindow::updateFileWatcher(const QString &basePath)
|
|||||||
if (!basePath.isEmpty()) return m_fileSystemWatcher->addPath(basePath);
|
if (!basePath.isEmpty()) return m_fileSystemWatcher->addPath(basePath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateActionState() {
|
||||||
|
m_am->updateActionState(m_pm, m_gv);
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ public:
|
|||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
|
|
||||||
void showUrls(const QList<QUrl> &urls);
|
void showFiles(const QStringList& files);
|
||||||
|
void showFiles(const QList<QUrl>& urls);
|
||||||
void initWindowSize();
|
void initWindowSize();
|
||||||
void adjustWindowSizeBySceneRect();
|
void adjustWindowSizeBySceneRect();
|
||||||
QUrl currentImageFileUrl() const;
|
|
||||||
|
|
||||||
void clearGallery();
|
void clearGallery();
|
||||||
void galleryPrev();
|
void galleryPrev();
|
||||||
void galleryNext();
|
void galleryNext();
|
||||||
void galleryCurrent(bool showLoadImageHintWhenEmpty, bool reloadImage);
|
void galleryCurrent(bool fromFileWatcher);
|
||||||
|
|
||||||
static QStringList supportedImageFormats();
|
static QStringList supportedImageFormats();
|
||||||
|
|
||||||
@@ -57,7 +57,6 @@ protected slots:
|
|||||||
void closeWindow();
|
void closeWindow();
|
||||||
void updateWidgetsPosition();
|
void updateWidgetsPosition();
|
||||||
void toggleAvoidResetTransform();
|
void toggleAvoidResetTransform();
|
||||||
bool canPaste() const;
|
|
||||||
void quitAppAction(bool force = false);
|
void quitAppAction(bool force = false);
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
void toggleMaximize();
|
void toggleMaximize();
|
||||||
@@ -98,6 +97,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool updateFileWatcher(const QString & basePath = QString());
|
bool updateFileWatcher(const QString & basePath = QString());
|
||||||
|
void updateActionState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ActionManager *m_am;
|
ActionManager *m_am;
|
||||||
|
|||||||
@@ -9,152 +9,13 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
PlaylistModel::PlaylistModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaylistModel::~PlaylistModel()
|
|
||||||
= default;
|
|
||||||
|
|
||||||
void PlaylistModel::setPlaylist(const QList<QUrl> &urls)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_playlist = urls;
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QList<QUrl> & urls)
|
|
||||||
{
|
|
||||||
if (urls.isEmpty()) return {};
|
|
||||||
if (urls.count() == 1) {
|
|
||||||
return loadPlaylist(urls.constFirst());
|
|
||||||
} else {
|
|
||||||
setPlaylist(urls);
|
|
||||||
return index(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QUrl &url)
|
|
||||||
{
|
|
||||||
QFileInfo info(url.toLocalFile());
|
|
||||||
QDir dir(info.path());
|
|
||||||
QString && currentFileName = info.fileName();
|
|
||||||
|
|
||||||
if (dir.path() == m_currentDir) {
|
|
||||||
int idx = indexOf(url);
|
|
||||||
return idx == -1 ? appendToPlaylist(url) : index(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList entryList = dir.entryList(
|
|
||||||
m_autoLoadSuffixes,
|
|
||||||
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
|
||||||
|
|
||||||
QCollator collator;
|
|
||||||
collator.setNumericMode(true);
|
|
||||||
|
|
||||||
std::sort(entryList.begin(), entryList.end(), collator);
|
|
||||||
|
|
||||||
QList<QUrl> playlist;
|
|
||||||
|
|
||||||
int idx = -1;
|
|
||||||
for (int i = 0; i < entryList.count(); i++) {
|
|
||||||
const QString & fileName = entryList.at(i);
|
|
||||||
const QString & oneEntry = dir.absoluteFilePath(fileName);
|
|
||||||
const QUrl & url = QUrl::fromLocalFile(oneEntry);
|
|
||||||
playlist.append(url);
|
|
||||||
if (fileName == currentFileName) {
|
|
||||||
idx = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == -1) {
|
|
||||||
idx = playlist.count();
|
|
||||||
playlist.append(url);
|
|
||||||
}
|
|
||||||
m_currentDir = dir.path();
|
|
||||||
|
|
||||||
setPlaylist(playlist);
|
|
||||||
|
|
||||||
return index(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url)
|
|
||||||
{
|
|
||||||
const int lastIndex = rowCount();
|
|
||||||
beginInsertRows(QModelIndex(), lastIndex, lastIndex);
|
|
||||||
m_playlist.append(url);
|
|
||||||
endInsertRows();
|
|
||||||
return index(lastIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PlaylistModel::removeAt(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= rowCount()) return false;
|
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
|
||||||
m_playlist.removeAt(index);
|
|
||||||
endRemoveRows();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PlaylistModel::indexOf(const QUrl &url) const
|
|
||||||
{
|
|
||||||
return m_playlist.indexOf(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl PlaylistModel::urlByIndex(int index) const
|
|
||||||
{
|
|
||||||
return m_playlist.value(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList PlaylistModel::autoLoadFilterSuffixes() const
|
|
||||||
{
|
|
||||||
return m_autoLoadSuffixes;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> PlaylistModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
|
||||||
result.insert(UrlRole, "url");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PlaylistModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_playlist.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant PlaylistModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid()) return {};
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case Qt::DisplayRole:
|
|
||||||
return m_playlist.at(index.row()).fileName();
|
|
||||||
case UrlRole:
|
|
||||||
return m_playlist.at(index.row());
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaylistManager::PlaylistManager(QObject *parent)
|
PlaylistManager::PlaylistManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, m_currentIndex(std::nullopt)
|
||||||
|
, m_playlist()
|
||||||
|
, m_allowedSuffixes()
|
||||||
{
|
{
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this,
|
|
||||||
[this](const QModelIndex &, int, int) {
|
|
||||||
if (m_model.rowCount() <= m_currentIndex) {
|
|
||||||
setProperty("currentIndex", m_currentIndex - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
auto onRowCountChanged = [this](){
|
|
||||||
emit totalCountChanged(m_model.rowCount());
|
|
||||||
};
|
|
||||||
|
|
||||||
connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged);
|
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged);
|
|
||||||
connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistManager::~PlaylistManager()
|
PlaylistManager::~PlaylistManager()
|
||||||
@@ -162,35 +23,106 @@ PlaylistManager::~PlaylistManager()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistModel *PlaylistManager::model()
|
// region: Allowed Suffixes Operations
|
||||||
{
|
|
||||||
return &m_model;
|
const QStringList& PlaylistManager::allowedSuffixes() const {
|
||||||
|
return m_allowedSuffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setPlaylist(const QList<QUrl> &urls)
|
void PlaylistManager::setAllowedSuffixes(const QStringList& nameFilters) {
|
||||||
{
|
m_allowedSuffixes = nameFilters;
|
||||||
m_model.setPlaylist(urls);
|
emit allowedSuffixesChanged(m_allowedSuffixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QList<QUrl> &urls)
|
// endregion
|
||||||
{
|
|
||||||
QModelIndex idx = m_model.loadPlaylist(urls);
|
// region: Playlist Loader and Reset
|
||||||
setProperty("currentIndex", idx.row());
|
|
||||||
return idx;
|
bool PlaylistManager::loadPlaylist(const QStringList& files) {
|
||||||
|
// Convert string list to url list first
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const QString & file : std::as_const(files)) {
|
||||||
|
urls.append(urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadPlaylist(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QUrl &url)
|
bool PlaylistManager::loadPlaylist(const QList<QUrl>& urls) {
|
||||||
{
|
// Check its count, and check M3U8 playlist for single file case.
|
||||||
QModelIndex idx = m_model.loadPlaylist(url);
|
if (urls.isEmpty()) {
|
||||||
setProperty("currentIndex", idx.row());
|
clearPlaylist();
|
||||||
return idx;
|
return true;
|
||||||
|
} else if (urls.count() == 1) {
|
||||||
|
const QUrl& firstUrl = urls.first();
|
||||||
|
const QString lowerCaseUrlPath(firstUrl.path().toLower());
|
||||||
|
if (lowerCaseUrlPath.endsWith(".m3u8") || lowerCaseUrlPath.endsWith(".m3u")) {
|
||||||
|
return loadM3U8Playlist(firstUrl);
|
||||||
|
} else {
|
||||||
|
return loadSingleFilePlaylist(firstUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return loadMultipleFilesPlaylist(std::move(urls));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
bool PlaylistManager::loadSingleFilePlaylist(const QUrl& url) {
|
||||||
{
|
// Check URL at first
|
||||||
|
if (!checkUrl(url))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QFileInfo info(url.toLocalFile());
|
||||||
|
QDir dir(info.path());
|
||||||
|
QString currentFileName = info.fileName();
|
||||||
|
|
||||||
|
QStringList entryList = dir.entryList(
|
||||||
|
m_allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
||||||
|
|
||||||
|
QCollator collator;
|
||||||
|
collator.setNumericMode(true);
|
||||||
|
|
||||||
|
std::sort(entryList.begin(), entryList.end(), collator);
|
||||||
|
|
||||||
|
QList<QUrl> playlist;
|
||||||
|
std::optional<qsizetype> idx = std::nullopt;
|
||||||
|
for (qsizetype i = 0; i < entryList.count(); i++) {
|
||||||
|
const QString &fileName = entryList.at(i);
|
||||||
|
const QString &oneEntry = dir.absoluteFilePath(fileName);
|
||||||
|
const QUrl &url = QUrl::fromLocalFile(oneEntry);
|
||||||
|
if (!checkUrl(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playlist.append(url);
|
||||||
|
if (fileName == currentFileName) {
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlaylist(std::move(playlist), idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::loadMultipleFilesPlaylist(const QList<QUrl>&& urls) {
|
||||||
|
// Check all of them are valid
|
||||||
|
for (const auto& url : std::as_const(urls)) {
|
||||||
|
if (!checkUrl(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set playlist and return
|
||||||
|
setPlaylist(std::move(urls));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::loadM3U8Playlist(const QUrl& url) {
|
||||||
|
// Check URL at first
|
||||||
|
if (!checkUrl(url))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QList<QUrl> urls;
|
||||||
QFile file(url.toLocalFile());
|
QFile file(url.toLocalFile());
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
QList<QUrl> urls;
|
|
||||||
while (!file.atEnd()) {
|
while (!file.atEnd()) {
|
||||||
QString line = file.readLine();
|
QString line = file.readLine();
|
||||||
if (line.startsWith('#')) {
|
if (line.startsWith('#')) {
|
||||||
@@ -198,86 +130,185 @@ QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
|||||||
}
|
}
|
||||||
QFileInfo fileInfo(file);
|
QFileInfo fileInfo(file);
|
||||||
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
||||||
|
if (!checkUrl(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
urls.append(item);
|
urls.append(item);
|
||||||
}
|
}
|
||||||
return loadPlaylist(urls);
|
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlaylist(std::move(urls));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::setPlaylist(const QList<QUrl>&& urls, std::optional<qsizetype> indexHint) {
|
||||||
|
// Setup playlist
|
||||||
|
m_playlist = std::move(urls);
|
||||||
|
|
||||||
|
// Set index with hint
|
||||||
|
if (indexHint.has_value()) {
|
||||||
|
m_currentIndex = *indexHint;
|
||||||
|
}
|
||||||
|
sanitizeIndex();
|
||||||
|
|
||||||
|
// Raise signal
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::clearPlaylist() {
|
||||||
|
m_playlist.clear();
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(this->totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Playlist Operations
|
||||||
|
|
||||||
|
void PlaylistManager::appendToPlaylist(const QString &file)
|
||||||
|
{
|
||||||
|
return appendToPlaylist(urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::appendToPlaylist(const QUrl &url)
|
||||||
|
{
|
||||||
|
m_playlist.append(url);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::insertIntoPlaylist(qsizetype index, const QString &file)
|
||||||
|
{
|
||||||
|
return insertIntoPlaylist(index, urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::insertIntoPlaylist(qsizetype index, const QUrl &url)
|
||||||
|
{
|
||||||
|
if (index < 0 || index > totalCount()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
m_playlist.insert(index, url);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlaylistManager::totalCount() const
|
bool PlaylistManager::removeFromPlaylist(qsizetype index)
|
||||||
{
|
{
|
||||||
return m_model.rowCount();
|
if (index < 0 || index >= totalCount()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
m_playlist.removeAt(index);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::previousIndex() const
|
// endregion
|
||||||
{
|
|
||||||
int count = totalCount();
|
|
||||||
if (count == 0) return {};
|
|
||||||
|
|
||||||
return m_model.index(isFirstIndex() ? count - 1 : m_currentIndex - 1);
|
// region: Index Operations
|
||||||
|
|
||||||
|
qsizetype PlaylistManager::totalCount() const
|
||||||
|
{
|
||||||
|
return m_playlist.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::nextIndex() const
|
std::optional<qsizetype> PlaylistManager::previousIndex() const
|
||||||
{
|
{
|
||||||
int count = totalCount();
|
if (!m_currentIndex.has_value() || totalCount() <= 0) return std::nullopt;
|
||||||
if (count == 0) return {};
|
return (isFirstIndex() ? totalCount() - 1 : *m_currentIndex - 1);
|
||||||
|
|
||||||
return m_model.index(isLastIndex() ? 0 : m_currentIndex + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::curIndex() const
|
std::optional<qsizetype> PlaylistManager::nextIndex() const
|
||||||
{
|
{
|
||||||
return m_model.index(m_currentIndex);
|
if (!m_currentIndex.has_value() || totalCount() <= 0) return std::nullopt;
|
||||||
|
return (isLastIndex() ? 0 : *m_currentIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isFirstIndex() const
|
bool PlaylistManager::isFirstIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex == 0;
|
return m_currentIndex.has_value() && *m_currentIndex == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isLastIndex() const
|
bool PlaylistManager::isLastIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex + 1 == totalCount();
|
return m_currentIndex.has_value() && *m_currentIndex + 1 == totalCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setCurrentIndex(const QModelIndex &index)
|
std::optional<qsizetype> PlaylistManager::currentIndex() const
|
||||||
{
|
{
|
||||||
if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) {
|
return m_currentIndex;
|
||||||
setProperty("currentIndex", index.row());
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::setCurrentIndex(qsizetype index)
|
||||||
|
{
|
||||||
|
if (!m_currentIndex.has_value()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (index < 0 || index >= totalCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*m_currentIndex == index) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentIndex = index;
|
||||||
|
emit currentIndexChanged(index);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl PlaylistManager::urlByIndex(const QModelIndex &index)
|
QUrl PlaylistManager::urlByIndex(qsizetype index) const
|
||||||
{
|
{
|
||||||
return m_model.urlByIndex(index.row());
|
return m_playlist.value(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PlaylistManager::localFileByIndex(const QModelIndex &index)
|
QString PlaylistManager::localFileByIndex(qsizetype index) const
|
||||||
{
|
{
|
||||||
return urlByIndex(index).toLocalFile();
|
return urlByIndex(index).toLocalFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::removeAt(const QModelIndex &index)
|
// endregion
|
||||||
{
|
|
||||||
return m_model.removeAt(index.row());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters)
|
// region: Misc
|
||||||
{
|
|
||||||
m_model.setProperty("autoLoadFilterSuffixes", nameFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QUrl> PlaylistManager::convertToUrlList(const QStringList &files)
|
void PlaylistManager::sanitizeIndex()
|
||||||
{
|
{
|
||||||
QList<QUrl> urlList;
|
auto cnt = totalCount();
|
||||||
for (const QString & str : std::as_const(files)) {
|
if (cnt == 0) {
|
||||||
QUrl url = QUrl::fromLocalFile(str);
|
// If playlist is empty, current index should be invalid.
|
||||||
if (url.isValid()) {
|
m_currentIndex = std::nullopt;
|
||||||
urlList.append(url);
|
} else {
|
||||||
|
if (m_currentIndex.has_value()) {
|
||||||
|
// Clamp current index in playlist range.
|
||||||
|
// If it is already in range, not change was done.
|
||||||
|
auto currentIndex = *m_currentIndex;
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
m_currentIndex = 0;
|
||||||
|
} else if (currentIndex >= cnt) {
|
||||||
|
m_currentIndex = cnt - 1;
|
||||||
|
} else {
|
||||||
|
; // No change.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there is no set value for current index,
|
||||||
|
// set it at the beginning of playlist.
|
||||||
|
m_currentIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::checkUrl(const QUrl& url)
|
||||||
|
{
|
||||||
|
return url.isValid() && url.isLocalFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl PlaylistManager::urlFromString(const QString& file)
|
||||||
|
{
|
||||||
|
return QUrl::fromLocalFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|||||||
@@ -5,84 +5,126 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QAbstractListModel>
|
#include <QObject>
|
||||||
|
#include <optional>
|
||||||
class PlaylistModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum PlaylistRole {
|
|
||||||
UrlRole = Qt::UserRole
|
|
||||||
};
|
|
||||||
Q_ENUM(PlaylistRole)
|
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged)
|
|
||||||
|
|
||||||
explicit PlaylistModel(QObject *parent = nullptr);
|
|
||||||
~PlaylistModel() override;
|
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & urls);
|
|
||||||
QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
|
||||||
QModelIndex loadPlaylist(const QUrl & url);
|
|
||||||
QModelIndex appendToPlaylist(const QUrl & url);
|
|
||||||
bool removeAt(int index);
|
|
||||||
int indexOf(const QUrl & url) const;
|
|
||||||
QUrl urlByIndex(int index) const;
|
|
||||||
QStringList autoLoadFilterSuffixes() const;
|
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void autoLoadFilterSuffixesChanged(QStringList suffixes);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// model data
|
|
||||||
QList<QUrl> m_playlist;
|
|
||||||
// properties
|
|
||||||
QStringList m_autoLoadSuffixes = {};
|
|
||||||
// internal
|
|
||||||
QString m_currentDir;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/// The manager of playlist.
|
||||||
class PlaylistManager : public QObject
|
class PlaylistManager : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)
|
/// Constructs a PlaylistManager with an optional parent QObject.
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes WRITE setAutoLoadFilterSuffixes)
|
explicit PlaylistManager(QObject* parent = nullptr);
|
||||||
Q_PROPERTY(PlaylistModel * model READ model CONSTANT)
|
/// Destructor.
|
||||||
|
|
||||||
explicit PlaylistManager(QObject *parent = nullptr);
|
|
||||||
~PlaylistManager();
|
~PlaylistManager();
|
||||||
|
|
||||||
PlaylistModel * model();
|
/// Get the allowed file suffixes of this playlist.
|
||||||
|
/// @return The current list of allowed suffixes.
|
||||||
|
const QStringList& allowedSuffixes() const;
|
||||||
|
/// Set the allowed file suffixes of this playlist.
|
||||||
|
/// @remark This only works for "load the whole directory by one file" case.
|
||||||
|
/// @param[in] nameFilters The new list of allowed suffixes.
|
||||||
|
void setAllowedSuffixes(const QStringList& nameFilters);
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & url);
|
/// Load user specified files as playlist.
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
/// @remark The wrapper for another overload.
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QUrl & url);
|
/// @return True for successful load, otherwise false.
|
||||||
Q_INVOKABLE QModelIndex loadM3U8Playlist(const QUrl & url);
|
bool loadPlaylist(const QStringList& files);
|
||||||
|
/// Load user specified URLs as playlist.
|
||||||
|
/// @remark This function automatically dispatch these files into correct sub-load functions.
|
||||||
|
/// @return True for successful load, otherwise false.
|
||||||
|
bool loadPlaylist(const QList<QUrl>& urls);
|
||||||
|
/// Clear the entire playlist and reset current index.
|
||||||
|
void clearPlaylist();
|
||||||
|
|
||||||
int totalCount() const;
|
/// Append a local file path to the playlist.
|
||||||
QModelIndex previousIndex() const;
|
void appendToPlaylist(const QString& file);
|
||||||
QModelIndex nextIndex() const;
|
/// Append a URL to the playlist.
|
||||||
QModelIndex curIndex() const;
|
void appendToPlaylist(const QUrl& url);
|
||||||
|
/// Insert a local file path at the given index.
|
||||||
|
/// @return True if inserted successfully, false if index is out of range.
|
||||||
|
bool insertIntoPlaylist(qsizetype index, const QString& file);
|
||||||
|
/// Insert a URL at the given index.
|
||||||
|
/// @return True if inserted successfully, false if index is out of range.
|
||||||
|
bool insertIntoPlaylist(qsizetype index, const QUrl& url);
|
||||||
|
/// Remove the entry at the given index.
|
||||||
|
/// @return True if removed successfully, false if index is out of range.
|
||||||
|
bool removeFromPlaylist(qsizetype index);
|
||||||
|
|
||||||
|
/// Returns the total number of items in the playlist.
|
||||||
|
/// @return Total count of entries in the playlist.
|
||||||
|
qsizetype totalCount() const;
|
||||||
|
/// Try fetching previous index.
|
||||||
|
/// @details This previous index is gallery-loop-aware.
|
||||||
|
/// @return std::nullopt if playlist is empty, otherwise the previous index considering gallery loop.
|
||||||
|
std::optional<qsizetype> previousIndex() const;
|
||||||
|
/// Try fetching next index.
|
||||||
|
/// @details This next index is gallery-loop-aware.
|
||||||
|
/// @return std::nullopt if playlist is empty, otherwise the next index considering gallery loop.
|
||||||
|
std::optional<qsizetype> nextIndex() const;
|
||||||
|
/// Check whether current index is the first index.
|
||||||
|
/// @return False if current index is not the first index or playlist is empty, otherwise true.
|
||||||
bool isFirstIndex() const;
|
bool isFirstIndex() const;
|
||||||
|
/// Check whether current index is the last index.
|
||||||
|
/// @return False if current index is not the last index or playlist is empty, otherwise true.
|
||||||
bool isLastIndex() const;
|
bool isLastIndex() const;
|
||||||
void setCurrentIndex(const QModelIndex & index);
|
/// Returns the current index.
|
||||||
QUrl urlByIndex(const QModelIndex & index);
|
/// @return std::nullopt if the playlist is empty, otherwise the current index.
|
||||||
QString localFileByIndex(const QModelIndex & index);
|
std::optional<qsizetype> currentIndex() const;
|
||||||
bool removeAt(const QModelIndex & index);
|
/// Set the current index.
|
||||||
|
/// @remark Index only can be set when playlist is not empty.
|
||||||
void setAutoLoadFilterSuffixes(const QStringList &nameFilters);
|
/// @return True if successful set, including "not changed" set, otherwise false.
|
||||||
|
/// This return value only indicates whether we can set index and given index is valid for setting.
|
||||||
static QList<QUrl> convertToUrlList(const QStringList & files);
|
/// If "no changed" set occurs, this function returns true, but doesn't raise current index changed signal.
|
||||||
|
bool setCurrentIndex(qsizetype index);
|
||||||
|
/// Returns the URL at the given index.
|
||||||
|
/// @return The URL if index is valid.
|
||||||
|
/// This class guarantee that this QUrl is pointing to a local file.
|
||||||
|
QUrl urlByIndex(qsizetype index) const;
|
||||||
|
/// Returns the local file path at the given index.
|
||||||
|
/// @return The local file path if index is valid.
|
||||||
|
/// This class guarantee that local file path always is valid.
|
||||||
|
QString localFileByIndex(qsizetype index) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentIndexChanged(int index);
|
void allowedSuffixesChanged(const QStringList& suffixes);
|
||||||
void totalCountChanged(int count);
|
/// Raised when index was changed bu setter.
|
||||||
|
/// The argument holds the new current index.
|
||||||
|
/// Please note that this signal will NOT be raised by the whole playlist change caused index change.
|
||||||
|
void currentIndexChanged(qsizetype index);
|
||||||
|
/// The whole playlist was changed, involving loading, editing and deleting some or full entries.
|
||||||
|
/// The argument holds the total count of new play list.
|
||||||
|
void playlistChanged(qsizetype count);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_currentIndex = -1;
|
/// The fundamental playlist setter.
|
||||||
PlaylistModel m_model;
|
/// @details All playlist load functions will call this function to set playlist at last.
|
||||||
|
/// @param[in] urls ALl urls in this list should be checked by checkUrl() function.
|
||||||
|
/// @param[in] indexHint Try to set current index to this index if possible.
|
||||||
|
void setPlaylist(const QList<QUrl>&& urls, std::optional<qsizetype> indexHint = std::nullopt);
|
||||||
|
/// Load playlist from a single file.
|
||||||
|
/// @details This function scans the same directory for siblings matching allowed suffixes, sorted naturally.
|
||||||
|
/// @return True if the playlist was loaded successfully, false otherwise.
|
||||||
|
bool loadSingleFilePlaylist(const QUrl& url);
|
||||||
|
/// Load playlist from multiple URLs directly.
|
||||||
|
/// @return True if all URLs are valid, false otherwise.
|
||||||
|
bool loadMultipleFilesPlaylist(const QList<QUrl>&& urls);
|
||||||
|
/// Load playlist from an M3U/M3U8 playlist file.
|
||||||
|
/// @return True if loaded successfully, false otherwise.
|
||||||
|
bool loadM3U8Playlist(const QUrl& url);
|
||||||
|
|
||||||
|
/// Make sure that current index is valid.
|
||||||
|
/// @details This function usually is used by those functions manipulating playlist.
|
||||||
|
void sanitizeIndex();
|
||||||
|
|
||||||
|
/// Check whether given QUrl is proper to be put into playlist.
|
||||||
|
/// @details Only the url pointing to local file can be put into playlist.
|
||||||
|
static bool checkUrl(const QUrl& url);
|
||||||
|
/// Convert a string to local file into a QUrl.
|
||||||
|
/// @param[in] file The string pointing to local file.
|
||||||
|
static QUrl urlFromString(const QString& file);
|
||||||
|
|
||||||
|
std::optional<qsizetype> m_currentIndex;
|
||||||
|
QList<QUrl> m_playlist;
|
||||||
|
QStringList m_allowedSuffixes;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user