File indexing completed on 2024-12-01 07:38:57

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2006 Dario Massarin <nekkar@libero.it>
0004    Copyright (C) 2009 Lukas Appelhans <l.appelhans@gmx.de>
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 */
0011 
0012 #include "transfersview.h"
0013 #include "core/kget.h"
0014 #include "core/transfertreemodel.h"
0015 #include "settings.h"
0016 #include "transferdetails.h"
0017 #include "transfersviewdelegate.h"
0018 
0019 #include "kget_debug.h"
0020 
0021 #include <KIO/JobUiDelegateFactory>
0022 #include <KIO/OpenUrlJob>
0023 #include <KLocalizedString>
0024 
0025 #include <QAction>
0026 #include <QDebug>
0027 #include <QDropEvent>
0028 #include <QGroupBox>
0029 #include <QHeaderView>
0030 #include <QMenu>
0031 #include <QSignalMapper>
0032 
0033 TransfersView::TransfersView(QWidget *parent)
0034     : QTreeView(parent)
0035 {
0036     //     setItemsExpandable(false);
0037     setRootIsDecorated(false);
0038     setAnimated(true);
0039     setAllColumnsShowFocus(true);
0040     header()->setDefaultAlignment(Qt::AlignCenter);
0041     header()->setMinimumSectionSize(80);
0042     header()->setContextMenuPolicy(Qt::CustomContextMenu);
0043     header()->setSectionsClickable(true);
0044     m_headerMenu = new QMenu(header());
0045 
0046     setSelectionMode(QAbstractItemView::ExtendedSelection);
0047     setDragEnabled(true);
0048     setAcceptDrops(true);
0049     setDropIndicatorShown(true);
0050     setEditTriggers(QAbstractItemView::NoEditTriggers);
0051     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0052     setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0053 
0054     connect(header(), &QWidget::customContextMenuRequested, this, &TransfersView::slotShowHeaderMenu);
0055     connect(header(), &QHeaderView::sectionCountChanged, this, &TransfersView::populateHeaderActions);
0056     connect(header(), &QHeaderView::sectionMoved, this, &TransfersView::slotSectionMoved);
0057     connect(header(), &QHeaderView::sectionResized, this, &TransfersView::slotSaveHeader);
0058     connect(this, &TransfersView::doubleClicked, this, &TransfersView::slotItemActivated);
0059     connect(this, &TransfersView::collapsed, this, &TransfersView::slotItemCollapsed);
0060     connect(KGet::model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this](const QModelIndex &parent, int first, int last) {
0061         closeExpandableDetails(parent, first, last);
0062     });
0063 }
0064 
0065 TransfersView::~TransfersView()
0066 {
0067 }
0068 
0069 void TransfersView::setModel(QAbstractItemModel *model)
0070 {
0071     QTreeView::setModel(model);
0072     int nGroups = model->rowCount(QModelIndex());
0073 
0074     for (int i = 0; i < nGroups; i++) {
0075         qCDebug(KGET_DEBUG) << "openEditor for row " << i;
0076         openPersistentEditor(model->index(i, TransferTreeModel::Status, QModelIndex()));
0077     }
0078 
0079     QByteArray loadedState = QByteArray::fromBase64(Settings::headerState().toLatin1());
0080     if (loadedState.isEmpty()) {
0081         setColumnWidth(0, 230);
0082     } else {
0083         header()->restoreState(loadedState);
0084     }
0085 
0086     // Workaround if the saved headerState is corrupted
0087     header()->setRootIndex(QModelIndex());
0088 
0089     populateHeaderActions();
0090     toggleMainGroup();
0091     connect(model, &QAbstractItemModel::rowsRemoved, this, &TransfersView::toggleMainGroup);
0092 }
0093 
0094 void TransfersView::dropEvent(QDropEvent *event)
0095 {
0096     QModelIndex dropIndex = indexAt(event->pos());
0097     QTreeView::dropEvent(event);
0098 
0099     setExpanded(dropIndex, true);
0100 }
0101 
0102 void TransfersView::rowsInserted(const QModelIndex &parent, int start, int end)
0103 {
0104     qCDebug(KGET_DEBUG) << "TransfersView::rowsInserted";
0105 
0106     if (!parent.isValid()) {
0107         qCDebug(KGET_DEBUG) << "parent is not valid " << start << "  " << end;
0108 
0109         for (int i = start; i <= end; i++) {
0110             qCDebug(KGET_DEBUG) << "openEditor for row " << i;
0111             openPersistentEditor(model()->index(i, TransferTreeModel::Status, parent));
0112         }
0113     }
0114 
0115     QTreeView::rowsInserted(parent, start, end);
0116 
0117     setExpanded(parent, true);
0118     toggleMainGroup();
0119 }
0120 
0121 void TransfersView::populateHeaderActions()
0122 {
0123     m_headerMenu->clear();
0124     m_headerMenu->addSection(i18n("Select columns"));
0125 
0126     auto *columnMapper = new QSignalMapper(this);
0127     connect(columnMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mappedInt), this, &TransfersView::slotHideSection);
0128 
0129     // Create for each column an action with the column-header as name
0130     QVector<QAction *> orderedMenuItems(header()->count());
0131     for (int i = 0; i < header()->count(); ++i) {
0132         auto *action = new QAction(this);
0133         action->setText(model()->headerData(i, Qt::Horizontal).toString());
0134         action->setCheckable(true);
0135         action->setChecked(!header()->isSectionHidden(i));
0136         orderedMenuItems[header()->visualIndex(i)] = action;
0137 
0138         connect(action, SIGNAL(toggled(bool)), columnMapper, SLOT(map()));
0139         columnMapper->setMapping(action, i);
0140     }
0141 
0142     // append the sorted actions
0143     for (int i = 0; i < orderedMenuItems.count(); ++i) {
0144         m_headerMenu->addAction(orderedMenuItems[i]);
0145     }
0146 }
0147 
0148 void TransfersView::slotHideSection(int logicalIndex)
0149 {
0150     const bool hide = !header()->isSectionHidden(logicalIndex);
0151     header()->setSectionHidden(logicalIndex, hide);
0152     slotSaveHeader();
0153 }
0154 
0155 void TransfersView::slotSectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
0156 {
0157     Q_UNUSED(logicalIndex)
0158 
0159     // first item is the title, so increase the indexes by one
0160     ++oldVisualIndex;
0161     ++newVisualIndex;
0162     QList<QAction *> actions = m_headerMenu->actions();
0163 
0164     QAction *before = actions.last();
0165     if (newVisualIndex + 1 < actions.count()) {
0166         if (newVisualIndex > oldVisualIndex) {
0167             before = actions[newVisualIndex + 1];
0168         } else {
0169             before = actions[newVisualIndex];
0170         }
0171     }
0172 
0173     QAction *action = actions[oldVisualIndex];
0174     m_headerMenu->removeAction(action);
0175     m_headerMenu->insertAction(before, action);
0176     slotSaveHeader();
0177 }
0178 
0179 void TransfersView::slotSaveHeader()
0180 {
0181     Settings::setHeaderState(header()->saveState().toBase64());
0182     Settings::self()->save();
0183 }
0184 
0185 void TransfersView::dragMoveEvent(QDragMoveEvent *event)
0186 {
0187     Q_UNUSED(event)
0188 
0189     closeExpandableDetails();
0190     QTreeView::dragMoveEvent(event);
0191 }
0192 
0193 void TransfersView::slotItemActivated(const QModelIndex &index)
0194 {
0195     if (!index.isValid())
0196         return;
0197 
0198     TransferTreeModel *transferTreeModel = KGet::model();
0199     ModelItem *item = transferTreeModel->itemFromIndex(index);
0200     auto *view_delegate = static_cast<TransfersViewDelegate *>(itemDelegate());
0201 
0202     if (!item)
0203         return;
0204 
0205     if (!item->isGroup() && index.column() == 0) {
0206         if (!view_delegate->isExtended(index)) {
0207             TransferHandler *handler = item->asTransfer()->transferHandler();
0208             QWidget *widget = getDetailsWidgetForTransfer(handler);
0209 
0210             m_editingIndexes.append(index);
0211             view_delegate->extendItem(widget, index);
0212         } else {
0213             m_editingIndexes.removeAll(index);
0214             view_delegate->contractItem(index);
0215         }
0216         KGet::actionCollection()->action("transfer_show_details")->setChecked(view_delegate->isExtended(index));
0217     } else if (!item->isGroup() && static_cast<TransferModelItem *>(item)->transferHandler()->status() == Job::Finished) {
0218         auto job = new KIO::OpenUrlJob(static_cast<TransferModelItem *>(item)->transferHandler()->dest(), this);
0219         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0220         job->start();
0221     }
0222 }
0223 
0224 void TransfersView::slotItemCollapsed(const QModelIndex &index)
0225 {
0226     if (!index.isValid())
0227         return;
0228 
0229     TransferTreeModel *transferTreeModel = KGet::model();
0230     ModelItem *item = transferTreeModel->itemFromIndex(index);
0231     auto *view_delegate = static_cast<TransfersViewDelegate *>(itemDelegate());
0232 
0233     if (!item)
0234         return;
0235 
0236     if (item->isGroup()) {
0237         TransferGroupHandler *groupHandler = item->asGroup()->groupHandler();
0238         QList<TransferHandler *> transfers = groupHandler->transfers();
0239 
0240         foreach (TransferHandler *transfer, transfers) {
0241             qCDebug(KGET_DEBUG) << "Transfer = " << transfer->source().toString();
0242             view_delegate->contractItem(KGet::model()->itemFromTransferHandler(transfer)->index());
0243         }
0244     }
0245 }
0246 
0247 void TransfersView::toggleMainGroup()
0248 {
0249     // show or hide the first group header if there's only one download group
0250     int nGroups = model()->rowCount(QModelIndex());
0251 
0252     if (nGroups <= 1) {
0253         setRootIndex(model()->index(0, 0, QModelIndex()));
0254     } else {
0255         setRootIndex(QModelIndex());
0256     }
0257     header()->setRootIndex(QModelIndex()); // HACK: else the header isn't visible with no visible items in the view
0258 }
0259 
0260 void TransfersView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0261 {
0262     Q_UNUSED(parent)
0263     Q_UNUSED(start)
0264     Q_UNUSED(end)
0265 
0266     closeExpandableDetails(currentIndex());
0267 }
0268 
0269 void TransfersView::slotShowHeaderMenu(const QPoint &point)
0270 {
0271     m_headerMenu->popup(header()->mapToGlobal(point));
0272 }
0273 
0274 void TransfersView::closeExpandableDetails(const QModelIndex &transferIndex)
0275 {
0276     auto *view_delegate = static_cast<TransfersViewDelegate *>(itemDelegate());
0277 
0278     if (transferIndex.isValid()) {
0279         view_delegate->contractItem(transferIndex);
0280         m_editingIndexes.removeAll(transferIndex);
0281     } else {
0282         view_delegate->contractAll();
0283         m_editingIndexes.clear();
0284     }
0285 }
0286 
0287 void TransfersView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
0288 {
0289     Q_UNUSED(deselected)
0290     if (!selected.indexes().isEmpty()) {
0291         auto *view_delegate = static_cast<TransfersViewDelegate *>(itemDelegate());
0292         KGet::actionCollection()->action("transfer_show_details")->setChecked(view_delegate->isExtended(selected.indexes().first()));
0293     }
0294 
0295     QTreeView::selectionChanged(selected, deselected);
0296 }
0297 
0298 void TransfersView::closeExpandableDetails(const QModelIndex &parent, int rowStart, int rowEnd)
0299 {
0300     Q_UNUSED(parent)
0301     Q_UNUSED(rowStart)
0302     Q_UNUSED(rowEnd)
0303 
0304     auto *view_delegate = static_cast<TransfersViewDelegate *>(itemDelegate());
0305 
0306     view_delegate->contractAll();
0307     m_editingIndexes.clear();
0308 }
0309 
0310 QWidget *TransfersView::getDetailsWidgetForTransfer(TransferHandler *handler)
0311 {
0312     auto *groupBox = new QGroupBox(i18n("Transfer Details"));
0313 
0314     auto *layout = new QVBoxLayout(groupBox);
0315     QWidget *detailsWidget = TransferDetails::detailsWidget(handler);
0316     layout->addWidget(detailsWidget);
0317 
0318     return groupBox;
0319 }
0320 
0321 #include "moc_transfersview.cpp"