subscriptionlistmodel.cpp 14.1 KB
Newer Older
1 2 3
/*
    This file is part of Akregator.

4
    Copyright (C) 2007 Frank Osterfeld <osterfeld@kde.org>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

    This program is free software you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

Frank Osterfeld's avatar
cleanup  
Frank Osterfeld committed
25
#include "subscriptionlistmodel.h"
26
#include "feed.h"
27 28
#include "feedlist.h"
#include "folder.h"
29
#include "subscriptionlistjobs.h"
30
#include "treenode.h"
31

32

Laurent Montel's avatar
Laurent Montel committed
33
#include "akregator_debug.h"
34
#include <KIconLoader>
35 36 37 38
#include <KLocalizedString>

#include <QByteArray>
#include <QDataStream>
Laurent Montel's avatar
Laurent Montel committed
39
#include <QIcon>
40 41 42 43 44 45
#include <QList>
#include <QMimeData>
#include <QUrl>
#include <QVariant>

#include <cassert>
46

47
using namespace Akregator;
48
using namespace Syndication;
49

Laurent Montel's avatar
Laurent Montel committed
50
#define AKREGATOR_TREENODE_MIMETYPE QStringLiteral("akregator/treenode-id")
51

Laurent Montel's avatar
Laurent Montel committed
52 53
namespace
{
54

Laurent Montel's avatar
Laurent Montel committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
static QString errorCodeToString(Syndication::ErrorCode err)
{
    switch (err) {
    case Timeout:
        return i18n("Timeout on remote server");
    case UnknownHost:
        return i18n("Unknown host");
    case FileNotFound:
        return i18n("Feed file not found on remote server");
    case InvalidXml:
        return i18n("Could not read feed (invalid XML)");
    case XmlNotAccepted:
        return i18n("Could not read feed (unknown format)");
    case InvalidFormat:
        return i18n("Could not read feed (invalid feed)");
    case Success:
    case Aborted:
    default:
        return QString();
74
    }
Laurent Montel's avatar
Laurent Montel committed
75
}
76

Laurent Montel's avatar
Laurent Montel committed
77 78 79 80
static const Akregator::TreeNode *nodeForIndex(const QModelIndex &index, const FeedList *feedList)
{
    return (!index.isValid() || !feedList) ? 0 : feedList->findByID(index.internalId());
}
81 82
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
83
Akregator::SubscriptionListModel::SubscriptionListModel(const QSharedPointer<const FeedList> &feedList, QObject *parent) : QAbstractItemModel(parent), m_feedList(feedList), m_beganRemoval(false)
84
{
Laurent Montel's avatar
Laurent Montel committed
85
    if (!m_feedList) {
86
        return;
Laurent Montel's avatar
Laurent Montel committed
87
    }
Laurent Montel's avatar
Laurent Montel committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101
    connect(m_feedList.data(), &FeedList::signalNodeAdded,
            this, &SubscriptionListModel::subscriptionAdded);
    connect(m_feedList.data(), &FeedList::signalAboutToRemoveNode,
            this, &SubscriptionListModel::aboutToRemoveSubscription);
    connect(m_feedList.data(), &FeedList::signalNodeRemoved,
            this, &SubscriptionListModel::subscriptionRemoved);
    connect(m_feedList.data(), &FeedList::signalNodeChanged,
            this, &SubscriptionListModel::subscriptionChanged);
    connect(m_feedList.data(), &FeedList::fetchStarted,
            this, &SubscriptionListModel::fetchStarted);
    connect(m_feedList.data(), &FeedList::fetched,
            this, &SubscriptionListModel::fetched);
    connect(m_feedList.data(), &FeedList::fetchAborted,
            this, &SubscriptionListModel::fetchAborted);
102 103
}

Laurent Montel's avatar
Laurent Montel committed
104
int Akregator::SubscriptionListModel::columnCount(const QModelIndex &) const
105 106 107 108
{
    return 3;
}

Laurent Montel's avatar
Laurent Montel committed
109
int Akregator::SubscriptionListModel::rowCount(const QModelIndex &parent) const
110
{
Laurent Montel's avatar
Laurent Montel committed
111
    if (!parent.isValid()) {
112
        return 1;
Laurent Montel's avatar
Laurent Montel committed
113
    }
114

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
115
    const Akregator::TreeNode *const node = nodeForIndex(parent, m_feedList.data());
116 117 118
    return node ? node->children().count() : 0;
}

Laurent Montel's avatar
Laurent Montel committed
119
uint Akregator::SubscriptionListModel::nodeIdForIndex(const QModelIndex &idx) const
Allen Winter's avatar
Allen Winter committed
120 121 122 123
{
    return idx.isValid() ? idx.internalId() : 0;
}

Laurent Montel's avatar
Laurent Montel committed
124
QVariant Akregator::SubscriptionListModel::data(const QModelIndex &index, int role) const
125
{
Laurent Montel's avatar
Laurent Montel committed
126
    if (!index.isValid()) {
127
        return QVariant();
Laurent Montel's avatar
Laurent Montel committed
128
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
129

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
130
    const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data());
131

Laurent Montel's avatar
Laurent Montel committed
132
    if (!node) {
133
        return QVariant();
Laurent Montel's avatar
Laurent Montel committed
134
    }
135

Laurent Montel's avatar
Laurent Montel committed
136 137 138 139 140 141 142 143 144 145
    switch (role) {
    case Qt::EditRole:
    case Qt::DisplayRole: {
        switch (index.column()) {
        case TitleColumn:
            return node->title();
        case UnreadCountColumn:
            return node->unread();
        case TotalCountColumn:
            return node->totalCount();
146
        }
Laurent Montel's avatar
Laurent Montel committed
147 148 149 150 151
        break;
    }
    case Qt::ToolTipRole: {
        if (node->isGroup() || node->isAggregation()) {
            return node->title();
Frank Osterfeld's avatar
Frank Osterfeld committed
152
        }
Laurent Montel's avatar
Laurent Montel committed
153 154 155
        const Feed *const feed = qobject_cast<const Feed *const>(node);
        if (!feed) {
            return QString();
156
        }
Laurent Montel's avatar
Laurent Montel committed
157 158
        if (feed->fetchErrorOccurred()) {
            return i18n("Could not fetch feed: %1", errorCodeToString(feed->fetchErrorCode()));
159
        }
Laurent Montel's avatar
Laurent Montel committed
160 161 162 163 164
        return feed->title();
    }
    case Qt::DecorationRole: {
        if (index.column() != TitleColumn) {
            return QVariant();
165
        }
Laurent Montel's avatar
Laurent Montel committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        const Feed *const feed = qobject_cast<const Feed *const>(node);
        return feed && feed->isFetching() ? node->icon().pixmap(KIconLoader::SizeSmall, QIcon::Active) : node->icon();
    }
    case SubscriptionIdRole: {
        return node->id();
    }
    case IsGroupRole: {
        return node->isGroup();
    }
    case IsFetchableRole: {
        return !node->isGroup() && !node->isAggregation();
    }
    case IsAggregationRole: {
        return node->isAggregation();
    }
    case LinkRole: {
        const Feed *const feed = qobject_cast<const Feed *const>(node);
        return feed ? feed->xmlUrl() : QVariant();
    }
    case IsOpenRole: {
        if (!node->isGroup()) {
            return false;
188
        }
Laurent Montel's avatar
Laurent Montel committed
189 190 191 192 193 194 195
        const Akregator::Folder *const folder = qobject_cast<const Akregator::Folder *const>(node);
        Q_ASSERT(folder);
        return folder->isOpen();
    }
    case HasUnreadRole: {
        return node->unread() > 0;
    }
196 197 198 199 200
    }

    return QVariant();
}

Laurent Montel's avatar
Laurent Montel committed
201
QVariant Akregator::SubscriptionListModel::headerData(int section, Qt::Orientation, int role) const
202
{
Laurent Montel's avatar
Laurent Montel committed
203
    if (role != Qt::DisplayRole) {
204
        return QVariant();
Laurent Montel's avatar
Laurent Montel committed
205
    }
206

Laurent Montel's avatar
Laurent Montel committed
207 208 209 210 211 212 213
    switch (section) {
    case TitleColumn:
        return i18nc("Feedlist's column header", "Feeds");
    case UnreadCountColumn:
        return i18nc("Feedlist's column header", "Unread");
    case TotalCountColumn:
        return i18nc("Feedlist's column header", "Total");
214 215 216 217 218
    }

    return QVariant();
}

Laurent Montel's avatar
Laurent Montel committed
219
QModelIndex Akregator::SubscriptionListModel::parent(const QModelIndex &index) const
220
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
221
    const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data());
222

Laurent Montel's avatar
Laurent Montel committed
223
    if (!node || !node->parent()) {
224
        return QModelIndex();
Laurent Montel's avatar
Laurent Montel committed
225
    }
226

Laurent Montel's avatar
Laurent Montel committed
227
    const Akregator::Folder *parent = node->parent();
228

Laurent Montel's avatar
Laurent Montel committed
229 230 231
    if (!parent->parent()) {
        return createIndex(0, 0, parent->id());
    }
232

Laurent Montel's avatar
Laurent Montel committed
233
    const Akregator::Folder *const grandparent = parent->parent();
234

Laurent Montel's avatar
Laurent Montel committed
235
    const int row = grandparent->indexOf(parent);
236

Laurent Montel's avatar
Laurent Montel committed
237
    Q_ASSERT(row != -1);
238

Laurent Montel's avatar
Laurent Montel committed
239
    return createIndex(row, 0, parent->id());
240 241
}

Laurent Montel's avatar
Laurent Montel committed
242
QModelIndex Akregator::SubscriptionListModel::index(int row, int column, const QModelIndex &parent) const
243
{
Laurent Montel's avatar
Laurent Montel committed
244
    if (!parent.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
245
        return (row == 0 && m_feedList) ? createIndex(row, column, m_feedList->allFeedsFolder()->id()) : QModelIndex();
Laurent Montel's avatar
Laurent Montel committed
246
    }
247

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
248
    const Akregator::TreeNode *const parentNode = nodeForIndex(parent, m_feedList.data());
249 250 251 252 253

    if (!parentNode) {
        return QModelIndex();
    }

Laurent Montel's avatar
Laurent Montel committed
254 255
    const Akregator::TreeNode *const childNode = parentNode->childAt(row);
    return  childNode ? createIndex(row, column, childNode->id()) : QModelIndex();
256 257
}

Laurent Montel's avatar
Laurent Montel committed
258
QModelIndex SubscriptionListModel::indexForNode(const TreeNode *node) const
259
{
Laurent Montel's avatar
Laurent Montel committed
260
    if (!node || !m_feedList) {
261
        return QModelIndex();
Laurent Montel's avatar
Laurent Montel committed
262 263 264 265 266 267 268 269 270
    }
    const Folder *const parent = node->parent();
    if (!parent) {
        return index(0, 0);
    }
    const int row = parent->indexOf(node);
    Q_ASSERT(row >= 0);
    const QModelIndex idx = index(row, 0, indexForNode(parent));
    Q_ASSERT(idx.internalId() == node->id());
271 272 273
    return idx;
}

Laurent Montel's avatar
Laurent Montel committed
274
void Akregator::SubscriptionListModel::subscriptionAdded(Akregator::TreeNode *subscription)
275
{
Laurent Montel's avatar
Laurent Montel committed
276 277 278 279
    const Folder *const parent = subscription->parent();
    const int row = parent ? parent->indexOf(subscription) : 0;
    Q_ASSERT(row >= 0);
    beginInsertRows(indexForNode(parent), row, row);
280
    endInsertRows();
281 282
}

Laurent Montel's avatar
Laurent Montel committed
283
void Akregator::SubscriptionListModel::aboutToRemoveSubscription(Akregator::TreeNode *subscription)
284
{
285
    qCDebug(AKREGATOR_LOG) << subscription->id();
Laurent Montel's avatar
Laurent Montel committed
286 287 288
    const Folder *const parent = subscription->parent();
    const int row = parent ? parent->indexOf(subscription) : -1;
    if (row < 0) {
289
        return;
Laurent Montel's avatar
Laurent Montel committed
290 291
    }
    beginRemoveRows(indexForNode(parent), row, row);
292 293 294
    m_beganRemoval = true;
}

Laurent Montel's avatar
Laurent Montel committed
295
void Akregator::SubscriptionListModel::subscriptionRemoved(TreeNode *subscription)
296
{
297
    qCDebug(AKREGATOR_LOG) << subscription->id();
Laurent Montel's avatar
Laurent Montel committed
298
    if (m_beganRemoval) {
299 300 301
        m_beganRemoval = false;
        endRemoveRows();
    }
302 303
}

Laurent Montel's avatar
Laurent Montel committed
304
void Akregator::SubscriptionListModel::subscriptionChanged(TreeNode *node)
305
{
Laurent Montel's avatar
Laurent Montel committed
306 307
    const QModelIndex idx = indexForNode(node);
    if (!idx.isValid()) {
308
        return;
Laurent Montel's avatar
Laurent Montel committed
309
    }
Laurent Montel's avatar
Laurent Montel committed
310
    Q_EMIT dataChanged(index(idx.row(), 0, idx.parent()),
Laurent Montel's avatar
Laurent Montel committed
311
                       index(idx.row(), ColumnCount - 1, idx.parent()));
312 313
}

Laurent Montel's avatar
Laurent Montel committed
314
void SubscriptionListModel::fetchStarted(Akregator::Feed *node)
315
{
Laurent Montel's avatar
Laurent Montel committed
316
    subscriptionChanged(node);
317 318
}

Laurent Montel's avatar
Laurent Montel committed
319
void SubscriptionListModel::fetched(Akregator::Feed *node)
320
{
Laurent Montel's avatar
Laurent Montel committed
321
    subscriptionChanged(node);
322 323
}

Laurent Montel's avatar
Laurent Montel committed
324
void SubscriptionListModel::fetchError(Akregator::Feed *node)
325
{
Laurent Montel's avatar
Laurent Montel committed
326
    subscriptionChanged(node);
327 328
}

Laurent Montel's avatar
Laurent Montel committed
329
void SubscriptionListModel::fetchAborted(Akregator::Feed *node)
Frank Osterfeld's avatar
Frank Osterfeld committed
330
{
Laurent Montel's avatar
Laurent Montel committed
331
    subscriptionChanged(node);
Frank Osterfeld's avatar
Frank Osterfeld committed
332 333
}

Laurent Montel's avatar
Laurent Montel committed
334
void Akregator::FolderExpansionHandler::itemExpanded(const QModelIndex &idx)
335
{
Laurent Montel's avatar
Laurent Montel committed
336
    setExpanded(idx, true);
337 338
}

Laurent Montel's avatar
Laurent Montel committed
339
void Akregator::FolderExpansionHandler::itemCollapsed(const QModelIndex &idx)
340
{
Laurent Montel's avatar
Laurent Montel committed
341
    setExpanded(idx, false);
342 343
}

Laurent Montel's avatar
Laurent Montel committed
344
void Akregator::FolderExpansionHandler::setExpanded(const QModelIndex &idx, bool expanded)
345
{
Laurent Montel's avatar
Laurent Montel committed
346
    if (!m_feedList || !m_model) {
347
        return;
Laurent Montel's avatar
Laurent Montel committed
348 349 350
    }
    Akregator::TreeNode *const node = m_feedList->findByID(m_model->nodeIdForIndex(idx));
    if (!node || !node->isGroup()) {
351
        return;
Laurent Montel's avatar
Laurent Montel committed
352
    }
353

Laurent Montel's avatar
Laurent Montel committed
354 355 356
    Akregator::Folder *const folder = qobject_cast<Akregator::Folder *>(node);
    Q_ASSERT(folder);
    folder->setOpen(expanded);
357 358
}

Laurent Montel's avatar
Laurent Montel committed
359
FolderExpansionHandler::FolderExpansionHandler(QObject *parent) : QObject(parent), m_feedList(), m_model(0)
360 361 362
{
}

Laurent Montel's avatar
Laurent Montel committed
363
void FolderExpansionHandler::setModel(Akregator::SubscriptionListModel *model)
364 365 366 367
{
    m_model = model;
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
368
void FolderExpansionHandler::setFeedList(const QSharedPointer<FeedList> &feedList)
369 370 371 372
{
    m_feedList = feedList;
}

Laurent Montel's avatar
Laurent Montel committed
373
Qt::ItemFlags SubscriptionListModel::flags(const QModelIndex &idx) const
374
{
Laurent Montel's avatar
Laurent Montel committed
375 376
    const Qt::ItemFlags flags = QAbstractItemModel::flags(idx);
    if (!idx.isValid() || (idx.column() != TitleColumn)) {
377
        return flags;
Laurent Montel's avatar
Laurent Montel committed
378 379
    }
    if (!idx.parent().isValid()) { // the root folder is neither draggable nor editable
380
        return flags | Qt::ItemIsDropEnabled;
Laurent Montel's avatar
Laurent Montel committed
381
    }
382 383 384 385 386 387
    return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
}

QStringList SubscriptionListModel::mimeTypes() const
{
    QStringList types;
Laurent Montel's avatar
Laurent Montel committed
388
    types << QStringLiteral("text/uri-list") << AKREGATOR_TREENODE_MIMETYPE;
389 390 391
    return types;
}

Laurent Montel's avatar
Laurent Montel committed
392
QMimeData *SubscriptionListModel::mimeData(const QModelIndexList &indexes) const
393
{
Laurent Montel's avatar
Laurent Montel committed
394
    QMimeData *mimeData = new QMimeData;
395 396

    QList<QUrl> urls;
Laurent Montel's avatar
Laurent Montel committed
397
    for (const QModelIndex &i : indexes) {
398
        const QUrl url(i.data(LinkRole).toString());
Laurent Montel's avatar
Laurent Montel committed
399
        if (!url.isEmpty()) {
400
            urls << url;
Laurent Montel's avatar
Laurent Montel committed
401
        }
402 403
    }

Laurent Montel's avatar
Laurent Montel committed
404
    mimeData->setUrls(urls);
405 406

    QByteArray idList;
Laurent Montel's avatar
Laurent Montel committed
407
    QDataStream idStream(&idList, QIODevice::WriteOnly);
Laurent Montel's avatar
Laurent Montel committed
408
    for (const QModelIndex &i : indexes)
Laurent Montel's avatar
Laurent Montel committed
409 410 411
        if (i.isValid()) {
            idStream << i.data(SubscriptionIdRole).toInt();
        }
412

Laurent Montel's avatar
Laurent Montel committed
413
    mimeData->setData(AKREGATOR_TREENODE_MIMETYPE, idList);
414 415 416 417

    return mimeData;
}

Laurent Montel's avatar
Laurent Montel committed
418
bool SubscriptionListModel::setData(const QModelIndex &idx, const QVariant &value, int role)
419
{
Laurent Montel's avatar
Laurent Montel committed
420
    if (!idx.isValid() || idx.column() != TitleColumn || role != Qt::EditRole) {
421
        return false;
Laurent Montel's avatar
Laurent Montel committed
422
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
423
    const TreeNode *const node = nodeForIndex(idx, m_feedList.data());
Laurent Montel's avatar
Laurent Montel committed
424
    if (!node) {
425
        return false;
Laurent Montel's avatar
Laurent Montel committed
426 427 428 429
    }
    RenameSubscriptionJob *job = new RenameSubscriptionJob(this);
    job->setSubscriptionId(node->id());
    job->setName(value.toString());
430 431 432 433
    job->start();
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
434 435 436 437 438
bool SubscriptionListModel::dropMimeData(const QMimeData *data,
        Qt::DropAction action,
        int row,
        int column,
        const QModelIndex &parent)
439
{
Laurent Montel's avatar
Laurent Montel committed
440
    Q_UNUSED(column)
441

Laurent Montel's avatar
Laurent Montel committed
442
    if (action == Qt::IgnoreAction) {
443
        return true;
Laurent Montel's avatar
Laurent Montel committed
444
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
445

446 447
    //if ( column != TitleColumn )
    //    return false;
Frank Osterfeld's avatar
Frank Osterfeld committed
448

Laurent Montel's avatar
Laurent Montel committed
449
    if (!data->hasFormat(AKREGATOR_TREENODE_MIMETYPE)) {
Frank Osterfeld's avatar
Frank Osterfeld committed
450
        return false;
Laurent Montel's avatar
Laurent Montel committed
451
    }
452

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
453
    const TreeNode *const droppedOnNode = qobject_cast<const TreeNode *>(nodeForIndex(parent, m_feedList.data()));
Frank Osterfeld's avatar
Frank Osterfeld committed
454

Laurent Montel's avatar
Laurent Montel committed
455
    if (!droppedOnNode) {
Frank Osterfeld's avatar
Frank Osterfeld committed
456
        return false;
Laurent Montel's avatar
Laurent Montel committed
457
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
458

Laurent Montel's avatar
Laurent Montel committed
459 460
    const Folder *const destFolder = droppedOnNode->isGroup() ? qobject_cast<const Folder *>(droppedOnNode) : droppedOnNode->parent();
    if (!destFolder) {
Frank Osterfeld's avatar
Frank Osterfeld committed
461
        return false;
Laurent Montel's avatar
Laurent Montel committed
462
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
463

Laurent Montel's avatar
Laurent Montel committed
464
    QByteArray idData = data->data(AKREGATOR_TREENODE_MIMETYPE);
Frank Osterfeld's avatar
Frank Osterfeld committed
465
    QList<int> ids;
Laurent Montel's avatar
Laurent Montel committed
466 467
    QDataStream stream(&idData, QIODevice::ReadOnly);
    while (!stream.atEnd()) {
Frank Osterfeld's avatar
Frank Osterfeld committed
468 469 470 471
        int id;
        stream >> id;
        ids << id;
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
472

Frank Osterfeld's avatar
Frank Osterfeld committed
473
    //don't drop nodes into their own subtree
Laurent Montel's avatar
Laurent Montel committed
474
    for (const int id : qAsConst(ids)) {
Laurent Montel's avatar
Laurent Montel committed
475 476
        const Folder *const asFolder = qobject_cast<const Folder *>(m_feedList->findByID(id));
        if (asFolder && (asFolder == destFolder || asFolder->subtreeContains(destFolder))) {
Frank Osterfeld's avatar
Frank Osterfeld committed
477
            return false;
Laurent Montel's avatar
Laurent Montel committed
478
        }
Frank Osterfeld's avatar
Frank Osterfeld committed
479
    }
Frank Osterfeld's avatar
Frank Osterfeld committed
480

Laurent Montel's avatar
Laurent Montel committed
481
    const TreeNode *const after = droppedOnNode->isGroup() ? destFolder->childAt(row) : droppedOnNode;
Frank Osterfeld's avatar
Frank Osterfeld committed
482

Laurent Montel's avatar
Laurent Montel committed
483
    for (const int id : qAsConst(ids)) {
Laurent Montel's avatar
Laurent Montel committed
484 485
        const TreeNode *const node = m_feedList->findByID(id);
        if (!node) {
Frank Osterfeld's avatar
Frank Osterfeld committed
486
            continue;
Laurent Montel's avatar
Laurent Montel committed
487 488 489 490
        }
        MoveSubscriptionJob *job = new MoveSubscriptionJob(this);
        job->setSubscriptionId(node->id());
        job->setDestination(destFolder->id(), after ? after->id() : -1);
Frank Osterfeld's avatar
Frank Osterfeld committed
491
        job->start();
492 493
    }

Frank Osterfeld's avatar
Frank Osterfeld committed
494
    return true;
495 496
}