From 09a8a0fb51559a8d64e44d03ef064370baeb8a0c Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Tue, 17 Dec 2013 21:12:27 +0000 Subject: [PATCH] Basic undo/redo support for playqueue. --- ChangeLog | 1 + README | 6 ++ gui/mainwindow.cpp | 6 +- gui/settings.cpp | 6 ++ gui/settings.h | 1 + models/playqueuemodel.cpp | 162 +++++++++++++++++++++++++++----------- models/playqueuemodel.h | 37 ++++++--- mpd/mpdconnection.cpp | 5 ++ mpd/mpdconnection.h | 1 + 9 files changed, 162 insertions(+), 63 deletions(-) diff --git a/ChangeLog b/ChangeLog index 30da95e1c..c604903a9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ ----- 1. Add option to control startup-state; visible, hidden, or remembered from previous run. +2. Basic undo/redo support for playqueue. 1.2.1 ----- diff --git a/README b/README index 446188ec7..2e5169a8e 100644 --- a/README +++ b/README @@ -344,6 +344,11 @@ volumeStep= Volume % increments. Used when mouse wheel is activated over volume control. Default is 5. (Values 1..20 are acceptable) +undoSteps= + Control maximum number of playqueue undo/redo steps. Set to 0 to disable + undo/redo. + Default is 10. (Values 0..20 are acceptable) + e.g. [General] iconTheme=oxygen @@ -353,6 +358,7 @@ cueFileCodecs=GBK, big5-0 networkAccessEnabled=false albumViewLoadAll=true volumeStep=2 +undoSteps=20 7. CUE Files diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 9a4a4f1a9..506eacc11 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -625,8 +625,8 @@ MainWindow::MainWindow(QWidget *parent) playQueue->addAction(cropPlayQueueAction); playQueue->addAction(shufflePlayQueueAction); playQueue->addAction(shufflePlayQueueAlbumsAction); -// playQueue->addAction(playQueueModel.undoAct()); -// playQueue->addAction(playQueueModel.redoAct()); + playQueue->addAction(playQueueModel.undoAct()); + playQueue->addAction(playQueueModel.redoAct()); Action *sep=new Action(this); sep->setSeparator(true); playQueue->addAction(sep); @@ -1301,7 +1301,7 @@ void MainWindow::controlConnectionsMenu(bool enable) void MainWindow::controlDynamicButton() { stopDynamicButton->setVisible(dynamicLabel->isVisible() && PAGE_DYNAMIC!=tabWidget->current_index()); -// playQueueModel.enableUndo(!Dynamic::self()->isRunning()); + playQueueModel.enableUndo(!Dynamic::self()->isRunning()); } void MainWindow::readSettings() diff --git a/gui/settings.cpp b/gui/settings.cpp index b05a5c150..068fd7692 100644 --- a/gui/settings.cpp +++ b/gui/settings.cpp @@ -819,6 +819,12 @@ Settings::StartupState Settings::startupState() return getStartupState(GET_STRING("startupState", getStartupStateStr(SS_Previous))); } +int Settings::undoSteps() +{ + int v=GET_INT("undoSteps", 10); + return RESTRICT(v, 0, 20); +} + void Settings::removeConnectionDetails(const QString &v) { if (v==currentConnection()) { diff --git a/gui/settings.h b/gui/settings.h index 16feb1d30..9dbdf4ff9 100644 --- a/gui/settings.h +++ b/gui/settings.h @@ -200,6 +200,7 @@ public: bool albumViewLoadAll(); int volumeStep(); StartupState startupState(); + int undoSteps(); void removeConnectionDetails(const QString &v); void saveConnectionDetails(const MPDConnectionDetails &v); diff --git a/models/playqueuemodel.cpp b/models/playqueuemodel.cpp index 484c1b120..cb1e6da9a 100644 --- a/models/playqueuemodel.cpp +++ b/models/playqueuemodel.cpp @@ -52,7 +52,6 @@ #include #include #include -#include #if defined ENABLE_MODEL_TEST #include "modeltest.h" @@ -127,10 +126,10 @@ PlayQueueModel::PlayQueueModel(QObject *parent) , dropAdjust(0) , stopAfterCurrent(false) , stopAfterTrackId(-1) -// , undoEnabled(true) -// , undoStack(new QUndoStack(this)) + , undoLimit(Settings::self()->undoSteps()) + , undoEnabled(undoLimit>0) + , lastCommand(Cmd_Other) { -// undoStack->setUndoLimit(constUndoStackSize); fetcher=new StreamFetcher(this); connect(this, SIGNAL(modelReset()), this, SLOT(stats())); connect(fetcher, SIGNAL(result(const QStringList &, int, bool, quint8)), SLOT(addFiles(const QStringList &, int, bool, quint8))); @@ -138,6 +137,7 @@ PlayQueueModel::PlayQueueModel(QObject *parent) connect(fetcher, SIGNAL(status(QString)), SIGNAL(streamFetchStatus(QString))); connect(this, SIGNAL(filesAdded(const QStringList, quint32, quint32, int, quint8)), MPDConnection::self(), SLOT(add(const QStringList, quint32, quint32, int, quint8))); + connect(this, SIGNAL(populate(QStringList)), MPDConnection::self(), SLOT(populate(QStringList))); connect(this, SIGNAL(move(const QList &, quint32, quint32)), MPDConnection::self(), SLOT(move(const QList &, quint32, quint32))); connect(MPDConnection::self(), SIGNAL(prioritySet(const QList &, quint8)), SLOT(prioritySet(const QList &, quint8))); @@ -156,13 +156,13 @@ PlayQueueModel::PlayQueueModel(QObject *parent) new ModelTest(this, this); #endif -// undoAction=ActionCollection::get()->createAction("playqueue-undo", i18n("Undo"), "edit-undo"); -// undoAction->setShortcut(Qt::ControlModifier+Qt::Key_Z); -// redoAction=ActionCollection::get()->createAction("playqueue-redo", i18n("Redo"), "edit-redo"); -// redoAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_Z); -// connect(undoAction, SIGNAL(triggered(bool)), this, SLOT(undo())); -// connect(redoAction, SIGNAL(triggered(bool)), this, SLOT(redo())); -// controlActions(); + undoAction=ActionCollection::get()->createAction("playqueue-undo", i18n("Undo"), "edit-undo"); + undoAction->setShortcut(Qt::ControlModifier+Qt::Key_Z); + redoAction=ActionCollection::get()->createAction("playqueue-redo", i18n("Redo"), "edit-redo"); + redoAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_Z); + connect(undoAction, SIGNAL(triggered(bool)), this, SLOT(undo())); + connect(redoAction, SIGNAL(triggered(bool)), this, SLOT(redo())); + controlActions(); } PlayQueueModel::~PlayQueueModel() @@ -720,7 +720,11 @@ void PlayQueueModel::setState(MPDState st) // Update playqueue with contents returned from MPD. void PlayQueueModel::update(const QList &songList) { -// saveHistory(); + QList prev; + if (undoEnabled) { + prev=songs; + } + QSet newIds; foreach (const Song &s, songList) { newIds.insert(s.id); @@ -793,6 +797,8 @@ void PlayQueueModel::update(const QList &songList) } emit statsUpdated(songs.size(), time); } + + saveHistory(prev); } void PlayQueueModel::setStopAfterTrack(qint32 track) @@ -864,45 +870,107 @@ void PlayQueueModel::crop(const QList &rowsToKeep) } } -//void PlayQueueModel::enableUndo(bool e) -//{ -// if (e==undoEnabled) { -// return; -// } -// undoEnabled=e; -// if (!e) { -// undoStack->clear(); -// } -// controlActions(); -//} +void PlayQueueModel::enableUndo(bool e) +{ + if (e==undoEnabled) { + return; + } + undoEnabled=e && undoLimit>0; + if (!e) { + undoStack.clear(); + redoStack.clear(); + } + controlActions(); +} -//void PlayQueueModel::saveHistory() -//{ -// if (!undoEnabled) { -// return; -// } -// controlActions(); -//} +static QStringList getFileNames(const QList &songs) +{ + QStringList fn; + foreach (const Song &s, songs) { + fn.append(s.file); + } + return fn; +} -//void PlayQueueModel::controlActions() -//{ -// undoAction->setEnabled(!undoStack.isEmpty()); -// redoAction->setEnabled(!redoStack.isEmpty()); -//} +void PlayQueueModel::saveHistory(const QList &prevList) +{ + if (!undoEnabled) { + return; + } -//void PlayQueueModel::undo() -//{ -// if (!undoEnabled) { -// return; -// } -//} + if (prevList==songs) { + lastCommand=Cmd_Other; + return; + } -//void PlayQueueModel::redo() -//{ -// if (!undoEnabled) { -// return; -// } -//} + switch (lastCommand) { + case Cmd_Redo: { + if (redoStack.isEmpty()) { + lastCommand=Cmd_Other; + } else { + QStringList actioned=redoStack.pop(); + if (actioned!=getFileNames(songs)) { + lastCommand=Cmd_Other; + } else { + undoStack.push(getFileNames(prevList)); + } + } + break; + } + case Cmd_Undo: { + if (undoStack.isEmpty()) { + lastCommand=Cmd_Other; + } else { + QStringList actioned=undoStack.pop(); + if (actioned!=getFileNames(songs)) { + lastCommand=Cmd_Other; + } else { + redoStack.push(getFileNames(prevList)); + } + } + break; + } + case Cmd_Other: + break; + } + + if (Cmd_Other==lastCommand) { + redoStack.clear(); + undoStack.push(getFileNames(prevList)); + if (undoStack.size()>undoLimit) { + undoStack.pop_back(); + } + } + + controlActions(); + lastCommand=Cmd_Other; +} + +void PlayQueueModel::controlActions() +{ + undoAction->setEnabled(!undoStack.isEmpty()); + undoAction->setVisible(undoLimit>0); + redoAction->setEnabled(!redoStack.isEmpty()); + redoAction->setVisible(undoLimit>0); +} + +void PlayQueueModel::undo() +{ + if (!undoEnabled || undoStack.isEmpty()) { + return; + } + emit populate(undoStack.top()); + lastCommand=Cmd_Undo; +} + +void PlayQueueModel::redo() +{ + if (!undoEnabled || redoStack.isEmpty()) { + return; + } + emit populate(redoStack.top()); + lastCommand=Cmd_Redo; +} void PlayQueueModel::playSong(const QString &file) { diff --git a/models/playqueuemodel.h b/models/playqueuemodel.h index 2e6b0f71f..95afd05bb 100644 --- a/models/playqueuemodel.h +++ b/models/playqueuemodel.h @@ -34,8 +34,8 @@ #include #include #include +#include -class QUndoStack; class StreamFetcher; class Action; @@ -103,13 +103,13 @@ public: void remove(const QList &rowsToRemove); void crop(const QList &rowsToKeep); -// void enableUndo(bool e); -// Action * undoAct() { return undoAction; } -// Action * redoAct() { return redoAction; } + void enableUndo(bool e); + Action * undoAct() { return undoAction; } + Action * redoAct() { return redoAction; } -//private: -// void saveHistory(); -// void controlActions(); +private: + void saveHistory(const QList &prevList); + void controlActions(); public Q_SLOTS: void addItems(const QStringList &items, int row, bool replace, quint8 priority); @@ -125,13 +125,14 @@ private Q_SLOTS: void stopAfterCurrentChanged(bool afterCurrent); void remove(const QList &rem); void updateDetails(const QList &updated); -// void undo(); -// void redo(); + void undo(); + void redo(); Q_SIGNALS: void stop(bool afterCurrent); void clearStopAfter(); void filesAdded(const QStringList filenames, const quint32 row, const quint32 size, int action, quint8 priority); + void populate(const QStringList &items); void move(const QList &items, const quint32 row, const quint32 size); void statsUpdated(int songs, quint32 time); void fetchingStreams(); @@ -154,10 +155,20 @@ private: bool stopAfterCurrent; qint32 stopAfterTrackId; -// bool undoEnabled; -// Action *undoAction; -// Action *redoAction; -// QUndoStack *undoStack; + enum Command + { + Cmd_Other, + Cmd_Undo, + Cmd_Redo + }; + + int undoLimit; + bool undoEnabled; + Command lastCommand; + Action *undoAction; + Action *redoAction; + QStack undoStack; + QStack redoStack; }; #endif diff --git a/mpd/mpdconnection.cpp b/mpd/mpdconnection.cpp index a33d2df19..1c88d5e66 100644 --- a/mpd/mpdconnection.cpp +++ b/mpd/mpdconnection.cpp @@ -580,6 +580,11 @@ void MPDConnection::add(const QStringList &files, quint32 pos, quint32 size, int } } +void MPDConnection::populate(const QStringList &files) +{ + add(files, 0, 0, AddAndReplace, 0); +} + void MPDConnection::addAndPlay(const QString &file) { toggleStopAfterCurrent(false); diff --git a/mpd/mpdconnection.h b/mpd/mpdconnection.h index 909017e76..7180a8469 100644 --- a/mpd/mpdconnection.h +++ b/mpd/mpdconnection.h @@ -207,6 +207,7 @@ public Q_SLOTS: // Current Playlist void add(const QStringList &files, bool replace, quint8 priority); void add(const QStringList &files, quint32 pos, quint32 size, int action, quint8 priority); + void populate(const QStringList &files); void addAndPlay(const QString &file); void currentSong(); void playListChanges();