File indexing completed on 2024-05-05 04:59:12

0001 /***************************************************************************
0002  *   Copyright (C) 2005 by Joris Guisson                                   *
0003  *   joris.guisson@gmail.com                                               *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
0019  ***************************************************************************/
0020 #include "fileview.h"
0021 
0022 #include <QFileDialog>
0023 #include <QFileInfo>
0024 #include <QHeaderView>
0025 #include <QItemSelectionModel>
0026 #include <QMenu>
0027 #include <QSortFilterProxyModel>
0028 
0029 #include <KConfigGroup>
0030 #include <KIO/JobUiDelegateFactory>
0031 #include <KIO/OpenUrlJob>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 #include <kwidgetsaddons_version.h>
0035 
0036 #include "iwfilelistmodel.h"
0037 #include "iwfiletreemodel.h"
0038 #include <interfaces/torrentfileinterface.h>
0039 #include <interfaces/torrentinterface.h>
0040 #include <util/bitset.h>
0041 #include <util/error.h>
0042 #include <util/functions.h>
0043 #include <util/log.h>
0044 #include <util/timer.h>
0045 
0046 using namespace bt;
0047 
0048 namespace kt
0049 {
0050 
0051 FileView::FileView(QWidget *parent)
0052     : QTreeView(parent)
0053     , curr_tc(nullptr)
0054     , model(nullptr)
0055 {
0056     setContextMenuPolicy(Qt::CustomContextMenu);
0057     setRootIsDecorated(false);
0058     setSortingEnabled(true);
0059     setAlternatingRowColors(true);
0060     setSelectionMode(QAbstractItemView::ExtendedSelection);
0061     setSelectionBehavior(QAbstractItemView::SelectRows);
0062     setUniformRowHeights(true);
0063 
0064     proxy_model = new QSortFilterProxyModel(this);
0065     proxy_model->setSortRole(Qt::UserRole);
0066     setModel(proxy_model);
0067 
0068     context_menu = new QMenu(this);
0069     open_action = context_menu->addAction(QIcon::fromTheme("document-open"), i18nc("Open file", "Open"), this, &FileView::open);
0070     context_menu->addSeparator();
0071     download_first_action = context_menu->addAction(i18n("Download first"), this, &FileView::downloadFirst);
0072     download_normal_action = context_menu->addAction(i18n("Download normally"), this, &FileView::downloadNormal);
0073     download_last_action = context_menu->addAction(i18n("Download last"), this, &FileView::downloadLast);
0074     context_menu->addSeparator();
0075     dnd_action = context_menu->addAction(i18n("Do Not Download"), this, &FileView::doNotDownload);
0076     delete_action = context_menu->addAction(i18n("Delete File(s)"), this, &FileView::deleteFiles);
0077     context_menu->addSeparator();
0078     move_files_action = context_menu->addAction(i18n("Move File"), this, &FileView::moveFiles);
0079     context_menu->addSeparator();
0080     collapse_action = context_menu->addAction(i18n("Collapse Folder Tree"), this, &FileView::collapseTree);
0081     expand_action = context_menu->addAction(i18n("Expand Folder Tree"), this, &FileView::expandTree);
0082 
0083     connect(this, &QWidget::customContextMenuRequested, this, &FileView::showContextMenu);
0084     connect(this, &QAbstractItemView::doubleClicked, this, &FileView::onDoubleClicked);
0085 
0086     setEnabled(false);
0087     show_list_of_files = false;
0088     redraw = false;
0089 }
0090 
0091 FileView::~FileView()
0092 {
0093 }
0094 
0095 void FileView::changeTC(bt::TorrentInterface *tc, KSharedConfigPtr cfg)
0096 {
0097     if (tc == curr_tc)
0098         return;
0099 
0100     if (model) {
0101         saveState(cfg);
0102         if (curr_tc)
0103             expanded_state_map[curr_tc] = model->saveExpandedState(proxy_model, this);
0104     }
0105     proxy_model->setSourceModel(nullptr);
0106     delete model;
0107     model = nullptr;
0108     curr_tc = tc;
0109     setEnabled(tc != nullptr);
0110     if (tc) {
0111         connect(tc, &TorrentInterface::missingFilesMarkedDND, this, &FileView::onMissingFileMarkedDND);
0112 
0113         if (show_list_of_files)
0114             model = new IWFileListModel(tc, this);
0115         else
0116             model = new IWFileTreeModel(tc, this);
0117 
0118         proxy_model->setSourceModel(model);
0119         setRootIsDecorated(tc->getStats().multi_file_torrent);
0120         loadState(cfg);
0121         QMap<bt::TorrentInterface *, QByteArray>::iterator i = expanded_state_map.find(tc);
0122         if (i != expanded_state_map.end())
0123             model->loadExpandedState(proxy_model, this, i.value());
0124         else
0125             expandAll();
0126     } else {
0127         proxy_model->setSourceModel(nullptr);
0128         model = nullptr;
0129     }
0130 }
0131 
0132 void FileView::onMissingFileMarkedDND(bt::TorrentInterface *tc)
0133 {
0134     if (curr_tc == tc)
0135         model->missingFilesMarkedDND();
0136 }
0137 
0138 void FileView::showContextMenu(const QPoint &p)
0139 {
0140     const TorrentStats &s = curr_tc->getStats();
0141 
0142     QModelIndexList sel = selectionModel()->selectedRows();
0143     if (sel.count() == 0)
0144         return;
0145 
0146     if (sel.count() > 1) {
0147         download_first_action->setEnabled(true);
0148         download_normal_action->setEnabled(true);
0149         download_last_action->setEnabled(true);
0150         open_action->setEnabled(false);
0151         dnd_action->setEnabled(true);
0152         delete_action->setEnabled(true);
0153         context_menu->popup(mapToGlobal(p));
0154         move_files_action->setEnabled(true);
0155         collapse_action->setEnabled(!show_list_of_files);
0156         expand_action->setEnabled(!show_list_of_files);
0157         return;
0158     }
0159 
0160     QModelIndex item = proxy_model->mapToSource(sel.front());
0161     bt::TorrentFileInterface *file = model->indexToFile(item);
0162 
0163     download_first_action->setEnabled(false);
0164     download_last_action->setEnabled(false);
0165     download_normal_action->setEnabled(false);
0166     dnd_action->setEnabled(false);
0167     delete_action->setEnabled(false);
0168 
0169     if (!s.multi_file_torrent) {
0170         open_action->setEnabled(true);
0171         move_files_action->setEnabled(true);
0172         preview_path = curr_tc->getStats().output_path;
0173         collapse_action->setEnabled(false);
0174         expand_action->setEnabled(false);
0175     } else if (file) {
0176         move_files_action->setEnabled(true);
0177         collapse_action->setEnabled(false);
0178         expand_action->setEnabled(false);
0179         if (!file->isNull()) {
0180             open_action->setEnabled(true);
0181             preview_path = file->getPathOnDisk();
0182 
0183             download_first_action->setEnabled(file->getPriority() != FIRST_PRIORITY);
0184             download_normal_action->setEnabled(file->getPriority() != NORMAL_PRIORITY);
0185             download_last_action->setEnabled(file->getPriority() != LAST_PRIORITY);
0186             dnd_action->setEnabled(file->getPriority() != ONLY_SEED_PRIORITY);
0187             delete_action->setEnabled(file->getPriority() != EXCLUDED);
0188         } else {
0189             open_action->setEnabled(false);
0190         }
0191     } else {
0192         move_files_action->setEnabled(false);
0193         download_first_action->setEnabled(true);
0194         download_normal_action->setEnabled(true);
0195         download_last_action->setEnabled(true);
0196         dnd_action->setEnabled(true);
0197         delete_action->setEnabled(true);
0198         open_action->setEnabled(true);
0199         preview_path = curr_tc->getDataDir() + model->dirPath(item);
0200         collapse_action->setEnabled(!show_list_of_files);
0201         expand_action->setEnabled(!show_list_of_files);
0202     }
0203 
0204     context_menu->popup(mapToGlobal(p));
0205 }
0206 
0207 void FileView::open()
0208 {
0209     auto job = new KIO::OpenUrlJob(QUrl(preview_path), nullptr);
0210     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0211     job->start();
0212 }
0213 
0214 void FileView::changePriority(bt::Priority newpriority)
0215 {
0216     QModelIndexList sel = selectionModel()->selectedRows(2);
0217     for (QModelIndexList::iterator i = sel.begin(); i != sel.end(); ++i)
0218         *i = proxy_model->mapToSource(*i);
0219 
0220     model->changePriority(sel, newpriority);
0221     proxy_model->invalidate();
0222 }
0223 
0224 void FileView::downloadFirst()
0225 {
0226     changePriority(FIRST_PRIORITY);
0227 }
0228 
0229 void FileView::downloadLast()
0230 {
0231     changePriority(LAST_PRIORITY);
0232 }
0233 
0234 void FileView::downloadNormal()
0235 {
0236     changePriority(NORMAL_PRIORITY);
0237 }
0238 
0239 void FileView::doNotDownload()
0240 {
0241     changePriority(ONLY_SEED_PRIORITY);
0242 }
0243 
0244 void FileView::deleteFiles()
0245 {
0246     QModelIndexList sel = selectionModel()->selectedRows();
0247     Uint32 n = sel.count();
0248     if (n == 1) // single item can be a directory
0249     {
0250         if (!model->indexToFile(proxy_model->mapToSource(sel.front())))
0251             ++n;
0252     }
0253 
0254     QString msg = i18np("You will lose all data in this file, are you sure you want to do this?",
0255                         "You will lose all data in these files, are you sure you want to do this?",
0256                         n);
0257 
0258     if (KMessageBox::warningTwoActions(nullptr, msg, QString(), KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::PrimaryAction)
0259         changePriority(EXCLUDED);
0260 }
0261 
0262 void FileView::moveFiles()
0263 {
0264     if (curr_tc->getStats().multi_file_torrent) {
0265         QModelIndexList sel = selectionModel()->selectedRows();
0266         QMap<bt::TorrentFileInterface *, QString> moves;
0267 
0268         QString dir = QFileDialog::getExistingDirectory(this, i18n("Select a directory to move the data to"));
0269         if (dir.isNull())
0270             return;
0271 
0272         foreach (const QModelIndex &idx, sel) {
0273             bt::TorrentFileInterface *tfi = model->indexToFile(proxy_model->mapToSource(idx));
0274             if (!tfi)
0275                 continue;
0276 
0277             moves.insert(tfi, dir);
0278         }
0279 
0280         if (moves.count() > 0) {
0281             curr_tc->moveTorrentFiles(moves);
0282         }
0283     } else {
0284         QString dir = QFileDialog::getExistingDirectory(this, i18n("Select a directory to move the data to"));
0285         if (dir.isNull())
0286             return;
0287 
0288         curr_tc->changeOutputDir(dir, bt::TorrentInterface::MOVE_FILES);
0289     }
0290 }
0291 
0292 void FileView::expandCollapseTree(const QModelIndex &idx, bool expand)
0293 {
0294     int rowCount = proxy_model->rowCount(idx);
0295     for (int i = 0; i < rowCount; i++) {
0296         const QModelIndex &ridx = proxy_model->index(i, 0, idx);
0297         if (proxy_model->hasChildren(ridx))
0298             expandCollapseTree(ridx, expand);
0299     }
0300     setExpanded(idx, expand);
0301 }
0302 
0303 void FileView::expandCollapseSelected(bool expand)
0304 {
0305     QModelIndexList sel = selectionModel()->selectedRows();
0306     for (QModelIndexList::iterator i = sel.begin(); i != sel.end(); ++i) {
0307         if (proxy_model->hasChildren(*i))
0308             expandCollapseTree(*i, expand);
0309     }
0310 }
0311 
0312 void FileView::collapseTree()
0313 {
0314     expandCollapseSelected(false);
0315 }
0316 
0317 void FileView::expandTree()
0318 {
0319     expandCollapseSelected(true);
0320 }
0321 
0322 void FileView::onDoubleClicked(const QModelIndex &index)
0323 {
0324     if (!curr_tc)
0325         return;
0326 
0327     const TorrentStats &s = curr_tc->getStats();
0328 
0329     if (s.multi_file_torrent) {
0330         bt::TorrentFileInterface *file = model->indexToFile(proxy_model->mapToSource(index));
0331         if (!file) {
0332             // directory
0333             auto job = new KIO::OpenUrlJob(QUrl(curr_tc->getDataDir() + model->dirPath(proxy_model->mapToSource(index))), nullptr);
0334             job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0335             job->start();
0336         } else {
0337             // file
0338             auto job = new KIO::OpenUrlJob(QUrl(file->getPathOnDisk()), nullptr);
0339             job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0340             job->start();
0341         }
0342     } else {
0343         auto job = new KIO::OpenUrlJob(QUrl(curr_tc->getStats().output_path), nullptr);
0344         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0345         job->start();
0346     }
0347 }
0348 
0349 void FileView::saveState(KSharedConfigPtr cfg)
0350 {
0351     if (!model)
0352         return;
0353 
0354     KConfigGroup g = cfg->group("FileView");
0355     QByteArray s = header()->saveState();
0356     g.writeEntry("state", s.toBase64());
0357 }
0358 
0359 void FileView::loadState(KSharedConfigPtr cfg)
0360 {
0361     KConfigGroup g = cfg->group("FileView");
0362     QByteArray s = QByteArray::fromBase64(g.readEntry("state", QByteArray()));
0363     if (!s.isNull()) {
0364         QHeaderView *v = header();
0365         v->restoreState(s);
0366         sortByColumn(v->sortIndicatorSection(), v->sortIndicatorOrder());
0367     }
0368 }
0369 
0370 void FileView::update()
0371 {
0372     if (model)
0373         model->update();
0374 
0375     if (redraw) {
0376         scheduleDelayedItemsLayout();
0377         redraw = false;
0378     }
0379 }
0380 
0381 void FileView::onTorrentRemoved(bt::TorrentInterface *tc)
0382 {
0383     expanded_state_map.remove(tc);
0384 }
0385 
0386 void FileView::setShowListOfFiles(bool on, KSharedConfigPtr cfg)
0387 {
0388     if (show_list_of_files == on)
0389         return;
0390 
0391     show_list_of_files = on;
0392     if (!model || !curr_tc)
0393         return;
0394 
0395     saveState(cfg);
0396     expanded_state_map[curr_tc] = model->saveExpandedState(proxy_model, this);
0397 
0398     proxy_model->setSourceModel(nullptr);
0399     delete model;
0400     model = nullptr;
0401 
0402     if (show_list_of_files)
0403         model = new IWFileListModel(curr_tc, this);
0404     else
0405         model = new IWFileTreeModel(curr_tc, this);
0406 
0407     proxy_model->setSourceModel(model);
0408     setRootIsDecorated(curr_tc->getStats().multi_file_torrent);
0409     loadState(cfg);
0410     QMap<bt::TorrentInterface *, QByteArray>::iterator i = expanded_state_map.find(curr_tc);
0411     if (i != expanded_state_map.end())
0412         model->loadExpandedState(proxy_model, this, i.value());
0413     else
0414         expandAll();
0415 
0416     collapse_action->setEnabled(!show_list_of_files);
0417     expand_action->setEnabled(!show_list_of_files);
0418 }
0419 
0420 bool FileView::viewportEvent(QEvent *event)
0421 {
0422     executeDelayedItemsLayout();
0423     return QTreeView::viewportEvent(event);
0424 }
0425 
0426 void FileView::filePercentageChanged(bt::TorrentFileInterface *file, float percentage)
0427 {
0428     if (model)
0429         model->filePercentageChanged(file, percentage);
0430 }
0431 
0432 void FileView::filePreviewChanged(bt::TorrentFileInterface *file, bool preview)
0433 {
0434     if (model)
0435         model->filePreviewChanged(file, preview);
0436 }
0437 
0438 void FileView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0439 {
0440     Q_UNUSED(topLeft)
0441     Q_UNUSED(bottomRight)
0442     redraw = true;
0443 }
0444 }
0445 
0446 #include "moc_fileview.cpp"