From 01d20f6263f5fd2f71890b201141ff2d0729705c Mon Sep 17 00:00:00 2001 From: Lukas Holecek <hluk@email.cz> Date: Tue, 30 Mar 2021 18:38:36 +0200 Subject: [PATCH] itemsync: Synchronize own item positions Fixes #1558 --- plugins/itemsync/filewatcher.h | 2 +- plugins/itemsync/itemsync.cpp | 66 ++++++++++++++++++------ plugins/itemsync/itemsync.h | 15 +++--- plugins/itemsync/tests/itemsynctests.cpp | 56 ++++++++++++++++++++ plugins/itemsync/tests/itemsynctests.h | 2 + 5 files changed, 114 insertions(+), 27 deletions(-) diff --git a/plugins/itemsync/filewatcher.h b/plugins/itemsync/filewatcher.h index 2f09f3372..bc1f6c723 100644 --- a/plugins/itemsync/filewatcher.h +++ b/plugins/itemsync/filewatcher.h @@ -69,7 +69,7 @@ public: static Hash calculateHash(const QByteArray &bytes); FileWatcher(const QString &path, const QStringList &paths, QAbstractItemModel *model, - int maxItems, const QList<FileFormat> &formatSettings, QObject *parent); + int maxItems, const QList<FileFormat> &formatSettings, QObject *parent = nullptr); const QString &path() const { return m_path; } diff --git a/plugins/itemsync/itemsync.cpp b/plugins/itemsync/itemsync.cpp index 5fc4a91ae..1f9675385 100644 --- a/plugins/itemsync/itemsync.cpp +++ b/plugins/itemsync/itemsync.cpp @@ -419,22 +419,15 @@ bool ItemSync::eventFilter(QObject *, QEvent *event) return ItemWidget::filterMouseEvents(m_label, event); } -ItemSyncSaver::ItemSyncSaver(const QString &tabPath) - : m_tabPath(tabPath) - , m_watcher(nullptr) -{ -} - -ItemSyncSaver::ItemSyncSaver( - QAbstractItemModel *model, - const QString &tabPath, - const QString &path, - const QStringList &files, - int maxItems, - const QList<FileFormat> &formatSettings) - : m_tabPath(tabPath) - , m_watcher(new FileWatcher(path, files, model, maxItems, formatSettings, this)) +ItemSyncSaver::ItemSyncSaver(QAbstractItemModel *model, const QString &tabPath, FileWatcher *watcher) + : m_model(model) + , m_tabPath(tabPath) + , m_watcher(watcher) { + if (m_watcher) + m_watcher->setParent(this); + connect( model, &QAbstractItemModel::rowsMoved, + this, &ItemSyncSaver::onRowsMoved ); } bool ItemSyncSaver::saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file) @@ -538,6 +531,44 @@ void ItemSyncSaver::setFocus(bool focus) m_watcher->setUpdatesEnabled(focus); } +void ItemSyncSaver::onRowsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int destinationRow) +{ + if (!m_model) + return; + + /* If own items were moved, change their base names in data to trigger + * updating/renaming file names so they are also moved in other app instances. + * + * The implementation works in common cases but will fail if: + * - Items are moved after the last own item. + * - Items move repeatedly to some not-top position. + */ + const int count = end - start + 1; + + const int baseRow = destinationRow < start + ? destinationRow + count + : destinationRow; + QString baseName; + if (destinationRow > 0) { + const QModelIndex baseIndex = m_model->index(baseRow, 0); + baseName = FileWatcher::getBaseName(baseIndex); + + if ( !isOwnFile(baseName) ) + return; + + if ( !baseName.isEmpty() && !baseName.contains(QLatin1Char('-')) ) + baseName.append(QLatin1String("-0000")); + } + + for (int row = baseRow - 1; row >= baseRow - count; --row) { + const auto index = m_model->index(row, 0); + if ( isOwnItem(index) ) { + const QVariantMap data = {{mimeBaseName, baseName}}; + m_model->setData(index, data, contentType::updateData); + } + } +} + QString ItemSyncScriptable::getMimeBaseName() const { return mimeBaseName; @@ -780,7 +811,7 @@ ItemSaverPtr ItemSyncLoader::loadItems(const QString &tabName, QAbstractItemMode const auto tabPath = m_tabPaths.value(tabName); const auto path = files.isEmpty() ? tabPath : QFileInfo(files.first()).absolutePath(); if ( path.isEmpty() ) - return std::make_shared<ItemSyncSaver>(tabPath); + return std::make_shared<ItemSyncSaver>(model, tabPath, nullptr); QDir dir(path); if ( !dir.mkpath(".") ) { @@ -788,5 +819,6 @@ ItemSaverPtr ItemSyncLoader::loadItems(const QString &tabName, QAbstractItemMode return nullptr; } - return std::make_shared<ItemSyncSaver>(model, tabPath, dir.path(), files, maxItems, m_formatSettings); + auto *watcher = new FileWatcher(path, files, model, maxItems, m_formatSettings); + return std::make_shared<ItemSyncSaver>(model, tabPath, watcher); } diff --git a/plugins/itemsync/itemsync.h b/plugins/itemsync/itemsync.h index bf932327d..fbbfcdb32 100644 --- a/plugins/itemsync/itemsync.h +++ b/plugins/itemsync/itemsync.h @@ -60,15 +60,7 @@ class ItemSyncSaver final : public QObject, public ItemSaverInterface Q_OBJECT public: - explicit ItemSyncSaver(const QString &tabPath); - - ItemSyncSaver( - QAbstractItemModel *model, - const QString &tabPath, - const QString &path, - const QStringList &files, - int maxItems, - const QList<FileFormat> &formatSettings); + ItemSyncSaver(QAbstractItemModel *model, const QString &tabPath, FileWatcher *watcher); bool saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file) override; @@ -80,7 +72,12 @@ public: void setFocus(bool focus) override; + void setFileWatcher(FileWatcher *watcher); + private: + void onRowsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int destinationRow); + + QPointer<QAbstractItemModel> m_model; QString m_tabPath; FileWatcher *m_watcher; }; diff --git a/plugins/itemsync/tests/itemsynctests.cpp b/plugins/itemsync/tests/itemsynctests.cpp index 46ffd9010..dbfbc8ca2 100644 --- a/plugins/itemsync/tests/itemsynctests.cpp +++ b/plugins/itemsync/tests/itemsynctests.cpp @@ -692,3 +692,59 @@ void ItemSyncTests::addItemsWhenFullOmitDeletingNotOwned() FilePtr f1(dir1.file(testFileName1)); QVERIFY(f1->exists()); } + +void ItemSyncTests::moveOwnItemsSortsBaseNames() +{ + TestDir dir1(1); + const QString tab1 = testTab(1); + RUN(Args() << "show" << tab1, ""); + + const Args args = Args() << "separator" << "," << "tab" << tab1; + + const QString testScript = R"( + var baseNames = []; + for (var i = 0; i < 4; ++i) { + baseNames.push(str(read(plugins.itemsync.mimeBaseName, i))); + } + for (var i = 0; i < 3; ++i) { + var j = i + 1; + if (baseNames[i] <= baseNames[j]) { + print("Failed test: baseNames["+i+"] > baseNames["+j+"]\\n"); + print(" Where baseNames["+i+"] = '" + baseNames[i] + "'\\n"); + print(" baseNames["+j+"] = '" + baseNames[j] + "'\\n"); + } + } + )"; + + RUN(args << "add" << "A" << "B" << "C" << "D", ""); + RUN(args << "read(0,1,2,3)", "D,C,B,A"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "END" << "CTRL+UP", ""); + RUN(args << "read(0,1,2,3)", "D,C,A,B"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "DOWN" << "CTRL+UP", ""); + RUN(args << "read(0,1,2,3)", "D,C,B,A"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "DOWN" << "SHIFT+UP" << "CTRL+UP", ""); + RUN(args << "read(0,1,2,3)", "D,B,A,C"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "CTRL+UP", ""); + RUN(args << "read(0,1,2,3)", "B,A,D,C"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "CTRL+DOWN", ""); + RUN(args << "read(0,1,2,3)", "D,B,A,C"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "END" << "CTRL+HOME", ""); + RUN(args << "read(0,1,2,3)", "C,D,B,A"); + RUN(args << testScript, ""); + + RUN(args << "keys" << "END" << "UP" << "CTRL+HOME", ""); + RUN(args << "read(0,1,2,3)", "B,C,D,A"); + RUN(args << testScript, ""); +} diff --git a/plugins/itemsync/tests/itemsynctests.h b/plugins/itemsync/tests/itemsynctests.h index 7c00a3543..ca001cdae 100644 --- a/plugins/itemsync/tests/itemsynctests.h +++ b/plugins/itemsync/tests/itemsynctests.h @@ -65,6 +65,8 @@ private slots: void addItemsWhenFullOmitDeletingNotOwned(); + void moveOwnItemsSortsBaseNames(); + private: TestInterfacePtr m_test; }; -- GitLab