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"