qBittorrent
propertieswidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "propertieswidget.h"
30 
31 #include <QClipboard>
32 #include <QDateTime>
33 #include <QDebug>
34 #include <QDir>
35 #include <QHeaderView>
36 #include <QListWidgetItem>
37 #include <QMenu>
38 #include <QSplitter>
39 #include <QShortcut>
40 #include <QStackedWidget>
41 #include <QThread>
42 #include <QUrl>
43 
48 #include "base/preferences.h"
49 #include "base/unicodestrings.h"
50 #include "base/utils/fs.h"
51 #include "base/utils/misc.h"
52 #include "base/utils/string.h"
54 #include "gui/lineedit.h"
55 #include "gui/raisedmessagebox.h"
58 #include "gui/uithememanager.h"
59 #include "gui/utils.h"
60 #include "downloadedpiecesbar.h"
61 #include "peerlistwidget.h"
62 #include "pieceavailabilitybar.h"
63 #include "proplistdelegate.h"
64 #include "proptabbar.h"
65 #include "speedwidget.h"
66 #include "trackerlistwidget.h"
67 #include "ui_propertieswidget.h"
68 
69 #ifdef Q_OS_MACOS
70 #include "gui/macutilities.h"
71 #endif
72 
74  : QWidget(parent)
75  , m_ui(new Ui::PropertiesWidget())
76  , m_torrent(nullptr)
77  , m_handleWidth(-1)
78 {
79  m_ui->setupUi(this);
80  setAutoFillBackground(true);
81 
82  m_state = VISIBLE;
83 
84  // Files list
85  m_ui->filesList->header()->setContextMenuPolicy(Qt::CustomContextMenu);
86 
87  // Set Properties list model
89  m_ui->filesList->setModel(m_propListModel);
91  m_ui->filesList->setItemDelegate(m_propListDelegate);
92  m_ui->filesList->setSortingEnabled(true);
93 
94  // Torrent content filtering
95  m_contentFilterLine = new LineEdit(this);
96  m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
97  m_contentFilterLine->setFixedWidth(Utils::Gui::scaledSize(this, 300));
98  connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::filterText);
99  m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
100 
101  // SIGNAL/SLOTS
102  connect(m_ui->selectAllButton, &QPushButton::clicked, m_propListModel, &TorrentContentFilterModel::selectAll);
103  connect(m_ui->selectNoneButton, &QPushButton::clicked, m_propListModel, &TorrentContentFilterModel::selectNone);
105  connect(m_ui->listWebSeeds, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayWebSeedListMenu);
107  connect(m_ui->stackedProperties, &QStackedWidget::currentChanged, this, &PropertiesWidget::loadDynamicData);
110  connect(m_ui->filesList, &QAbstractItemView::clicked
111  , m_ui->filesList, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
112  connect(m_ui->filesList, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayFilesListMenu);
113  connect(m_ui->filesList, &QAbstractItemView::doubleClicked, this, &PropertiesWidget::openItem);
114  connect(m_ui->filesList->header(), &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayFileListHeaderMenu);
115  connect(m_ui->filesList->header(), &QHeaderView::sectionMoved, this, &PropertiesWidget::saveSettings);
116  connect(m_ui->filesList->header(), &QHeaderView::sectionResized, this, &PropertiesWidget::saveSettings);
117  connect(m_ui->filesList->header(), &QHeaderView::sortIndicatorChanged, this, &PropertiesWidget::saveSettings);
118 
119  // set bar height relative to screen dpi
120  const int barHeight = Utils::Gui::scaledSize(this, 18);
121 
122  // Downloaded pieces progress bar
123  m_ui->tempProgressBarArea->setVisible(false);
125  m_ui->groupBarLayout->addWidget(m_downloadedPieces, 0, 1);
126  m_downloadedPieces->setFixedHeight(barHeight);
127  m_downloadedPieces->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
128 
129  // Pieces availability bar
130  m_ui->tempAvailabilityBarArea->setVisible(false);
132  m_ui->groupBarLayout->addWidget(m_piecesAvailability, 1, 1);
133  m_piecesAvailability->setFixedHeight(barHeight);
134  m_piecesAvailability->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
135 
136  // Tracker list
137  m_trackerList = new TrackerListWidget(this);
138  m_ui->trackerUpButton->setIcon(UIThemeManager::instance()->getIcon("go-up"));
139  m_ui->trackerUpButton->setIconSize(Utils::Gui::smallIconSize());
140  m_ui->trackerDownButton->setIcon(UIThemeManager::instance()->getIcon("go-down"));
141  m_ui->trackerDownButton->setIconSize(Utils::Gui::smallIconSize());
142  connect(m_ui->trackerUpButton, &QPushButton::clicked, m_trackerList, &TrackerListWidget::moveSelectionUp);
143  connect(m_ui->trackerDownButton, &QPushButton::clicked, m_trackerList, &TrackerListWidget::moveSelectionDown);
144  m_ui->hBoxLayoutTrackers->insertWidget(0, m_trackerList);
145  // Peers list
146  m_peerList = new PeerListWidget(this);
147  m_ui->vBoxLayoutPeerPage->addWidget(m_peerList);
148  // Tab bar
149  m_tabBar = new PropTabBar(nullptr);
150  m_tabBar->setContentsMargins(0, 5, 0, 5);
151  m_ui->verticalLayout->addLayout(m_tabBar);
152  connect(m_tabBar, &PropTabBar::tabChanged, m_ui->stackedProperties, &QStackedWidget::setCurrentIndex);
156 
157  const auto *editWebSeedsHotkey = new QShortcut(Qt::Key_F2, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
158  connect(editWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::editWebSeed);
159  const auto *deleteWebSeedsHotkey = new QShortcut(QKeySequence::Delete, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
160  connect(deleteWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::deleteSelectedUrlSeeds);
161  connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed);
162 
163  const auto *renameFileHotkey = new QShortcut(Qt::Key_F2, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
164  connect(renameFileHotkey, &QShortcut::activated, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
165  const auto *openFileHotkeyReturn = new QShortcut(Qt::Key_Return, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
166  connect(openFileHotkeyReturn, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile);
167  const auto *openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
168  connect(openFileHotkeyEnter, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile);
169 
170  configure();
172 }
173 
175 {
176  delete m_tabBar;
177  delete m_ui;
178 }
179 
181 {
182  QMenu *menu = new QMenu(this);
183  menu->setAttribute(Qt::WA_DeleteOnClose);
184 
185  for (int i = 0; i < TorrentContentModelItem::TreeItemColumns::NB_COL; ++i)
186  {
187  QAction *myAct = menu->addAction(m_propListModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
188  myAct->setCheckable(true);
189  myAct->setChecked(!m_ui->filesList->isColumnHidden(i));
190  if (i == TorrentContentModelItem::TreeItemColumns::COL_NAME)
191  myAct->setEnabled(false);
192 
193  connect(myAct, &QAction::toggled, this, [this, i](const bool checked)
194  {
195  m_ui->filesList->setColumnHidden(i, !checked);
196 
197  if (!m_ui->filesList->isColumnHidden(i) && (m_ui->filesList->columnWidth(i) <= 5))
198  m_ui->filesList->resizeColumnToContents(i);
199 
200  saveSettings();
201  });
202  }
203 
204  menu->popup(QCursor::pos());
205 }
206 
208 {
209  m_ui->labelPiecesAvailability->setVisible(show);
210  m_piecesAvailability->setVisible(show);
211  m_ui->labelAverageAvailabilityVal->setVisible(show);
212  if (show || !m_downloadedPieces->isVisible())
213  m_ui->lineBelowBars->setVisible(show);
214 }
215 
217 {
218  m_ui->labelDownloadedPieces->setVisible(show);
219  m_downloadedPieces->setVisible(show);
220  m_ui->labelProgressVal->setVisible(show);
221  if (show || !m_piecesAvailability->isVisible())
222  m_ui->lineBelowBars->setVisible(show);
223 }
224 
225 void PropertiesWidget::setVisibility(const bool visible)
226 {
227  if (!visible && (m_state == VISIBLE))
228  {
229  const int tabBarHeight = m_tabBar->geometry().height(); // take height before hiding
230  auto *hSplitter = static_cast<QSplitter *>(parentWidget());
231  m_ui->stackedProperties->setVisible(false);
232  m_slideSizes = hSplitter->sizes();
233  hSplitter->handle(1)->setVisible(false);
234  hSplitter->handle(1)->setDisabled(true);
235  m_handleWidth = hSplitter->handleWidth();
236  hSplitter->setHandleWidth(0);
237  const QList<int> sizes {(hSplitter->geometry().height() - tabBarHeight), tabBarHeight};
238  hSplitter->setSizes(sizes);
239  setMaximumSize(maximumSize().width(), tabBarHeight);
240  m_state = REDUCED;
241  return;
242  }
243 
244  if (visible && (m_state == REDUCED))
245  {
246  m_ui->stackedProperties->setVisible(true);
247  auto *hSplitter = static_cast<QSplitter *>(parentWidget());
248  if (m_handleWidth != -1)
249  hSplitter->setHandleWidth(m_handleWidth);
250  hSplitter->handle(1)->setDisabled(false);
251  hSplitter->handle(1)->setVisible(true);
252  hSplitter->setSizes(m_slideSizes);
253  m_state = VISIBLE;
254  setMaximumSize(maximumSize().width(), QWIDGETSIZE_MAX);
255  // Force refresh
256  loadDynamicData();
257  }
258 }
259 
261 {
262  qDebug("Clearing torrent properties");
263  m_ui->labelSavePathVal->clear();
264  m_ui->labelCreatedOnVal->clear();
265  m_ui->labelTotalPiecesVal->clear();
266  m_ui->labelInfohash1Val->clear();
267  m_ui->labelInfohash2Val->clear();
268  m_ui->labelCommentVal->clear();
269  m_ui->labelProgressVal->clear();
270  m_ui->labelAverageAvailabilityVal->clear();
271  m_ui->labelWastedVal->clear();
272  m_ui->labelUpTotalVal->clear();
273  m_ui->labelDlTotalVal->clear();
274  m_ui->labelUpLimitVal->clear();
275  m_ui->labelDlLimitVal->clear();
276  m_ui->labelElapsedVal->clear();
277  m_ui->labelConnectionsVal->clear();
278  m_ui->labelReannounceInVal->clear();
279  m_ui->labelShareRatioVal->clear();
280  m_ui->listWebSeeds->clear();
281  m_ui->labelETAVal->clear();
282  m_ui->labelSeedsVal->clear();
283  m_ui->labelPeersVal->clear();
284  m_ui->labelDlSpeedVal->clear();
285  m_ui->labelUpSpeedVal->clear();
286  m_ui->labelTotalSizeVal->clear();
287  m_ui->labelCompletedOnVal->clear();
288  m_ui->labelLastSeenCompleteVal->clear();
289  m_ui->labelCreatedByVal->clear();
290  m_ui->labelAddedOnVal->clear();
291  m_trackerList->clear();
294  m_peerList->clear();
295  m_contentFilterLine->clear();
297 }
298 
300 {
301  return m_torrent;
302 }
303 
305 {
306  return m_trackerList;
307 }
308 
310 {
311  return m_peerList;
312 }
313 
315 {
316  return m_ui->filesList;
317 }
318 
320 {
321  if (torrent == m_torrent)
322  m_ui->labelSavePathVal->setText(Utils::Fs::toNativePath(m_torrent->savePath()));
323 }
324 
326 {
327  if (torrent == m_torrent)
329 }
330 
332 {
333  if (torrent == m_torrent)
335 }
336 
338 {
339  clear();
340  m_torrent = torrent;
343  if (!m_torrent) return;
344 
345  // Save path
347  // Info hashes
348  m_ui->labelInfohash1Val->setText(m_torrent->infoHash().v1().isValid() ? m_torrent->infoHash().v1().toString() : tr("N/A"));
349  m_ui->labelInfohash2Val->setText(m_torrent->infoHash().v2().isValid() ? m_torrent->infoHash().v2().toString() : tr("N/A"));
351  if (m_torrent->hasMetadata())
352  {
353  // Creation date
354  m_ui->labelCreatedOnVal->setText(QLocale().toString(m_torrent->creationDate(), QLocale::ShortFormat));
355 
356  m_ui->labelTotalSizeVal->setText(Utils::Misc::friendlyUnit(m_torrent->totalSize()));
357 
358  // Comment
359  m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
360 
361  // URL seeds
362  loadUrlSeeds();
363 
364  m_ui->labelCreatedByVal->setText(m_torrent->creator());
365  }
366  // Load dynamic data
367  loadDynamicData();
368 }
369 
371 {
372  const Preferences *const pref = Preferences::instance();
373  // Restore splitter sizes
374  QStringList sizesStr = pref->getPropSplitterSizes().split(',');
375  if (sizesStr.size() == 2)
376  {
377  m_slideSizes << sizesStr.first().toInt();
378  m_slideSizes << sizesStr.last().toInt();
379  auto *hSplitter = static_cast<QSplitter *>(parentWidget());
380  hSplitter->setSizes(m_slideSizes);
381  }
382  const int currentTab = pref->getPropCurTab();
383  const bool visible = pref->getPropVisible();
384  m_ui->filesList->header()->restoreState(pref->getPropFileListState());
385  m_tabBar->setCurrentIndex(currentTab);
386  if (!visible)
387  setVisibility(false);
388 }
389 
391 {
392  Preferences *const pref = Preferences::instance();
393  pref->setPropVisible(m_state == VISIBLE);
394  // Splitter sizes
395  auto *hSplitter = static_cast<QSplitter *>(parentWidget());
396  QList<int> sizes;
397  if (m_state == VISIBLE)
398  sizes = hSplitter->sizes();
399  else
400  sizes = m_slideSizes;
401 
402  if (sizes.size() == 2)
403  pref->setPropSplitterSizes(QString::number(sizes.first()) + ',' + QString::number(sizes.last()));
404  pref->setPropFileListState(m_ui->filesList->header()->saveState());
405  // Remember current tab
407 }
408 
410 {
411  // Take program preferences into consideration
414 }
415 
417 {
418  // Refresh only if the torrent handle is valid and visible
419  if (!m_torrent || (m_state != VISIBLE)) return;
420 
421  // Transfer infos
422  switch (m_ui->stackedProperties->currentIndex())
423  {
424  case PropTabBar::MainTab:
425  {
426  m_ui->labelWastedVal->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize()));
427 
428  m_ui->labelUpTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload())
430 
431  m_ui->labelDlTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalDownload())
433 
434  m_ui->labelUpLimitVal->setText(m_torrent->uploadLimit() <= 0 ? QString::fromUtf8(C_INFINITY) : Utils::Misc::friendlyUnit(m_torrent->uploadLimit(), true));
435 
436  m_ui->labelDlLimitVal->setText(m_torrent->downloadLimit() <= 0 ? QString::fromUtf8(C_INFINITY) : Utils::Misc::friendlyUnit(m_torrent->downloadLimit(), true));
437 
438  QString elapsedString;
439  if (m_torrent->isSeed())
440  elapsedString = tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
443  else
445  m_ui->labelElapsedVal->setText(elapsedString);
446 
447  m_ui->labelConnectionsVal->setText(tr("%1 (%2 max)", "%1 and %2 are numbers, e.g. 3 (10 max)")
448  .arg(m_torrent->connectionsCount())
449  .arg(m_torrent->connectionsLimit() < 0 ? QString::fromUtf8(C_INFINITY) : QString::number(m_torrent->connectionsLimit())));
450 
451  m_ui->labelETAVal->setText(Utils::Misc::userFriendlyDuration(m_torrent->eta(), MAX_ETA));
452 
453  // Update next announce time
454  m_ui->labelReannounceInVal->setText(Utils::Misc::userFriendlyDuration(m_torrent->nextAnnounce()));
455 
456  // Update ratio info
457  const qreal ratio = m_torrent->realRatio();
458  m_ui->labelShareRatioVal->setText(ratio > BitTorrent::Torrent::MAX_RATIO ? QString::fromUtf8(C_INFINITY) : Utils::String::fromDouble(ratio, 2));
459 
460  m_ui->labelSeedsVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
461  .arg(QString::number(m_torrent->seedsCount())
462  , QString::number(m_torrent->totalSeedsCount())));
463 
464  m_ui->labelPeersVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
465  .arg(QString::number(m_torrent->leechsCount())
466  , QString::number(m_torrent->totalLeechersCount())));
467 
468  const qlonglong dlDuration = m_torrent->activeTime() - m_torrent->finishedTime();
469  const QString dlAvg = Utils::Misc::friendlyUnit((m_torrent->totalDownload() / ((dlDuration == 0) ? -1 : dlDuration)), true);
470  m_ui->labelDlSpeedVal->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
472 
473  const qlonglong ulDuration = m_torrent->activeTime();
474  const QString ulAvg = Utils::Misc::friendlyUnit((m_torrent->totalUpload() / ((ulDuration == 0) ? -1 : ulDuration)), true);
475  m_ui->labelUpSpeedVal->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
476  .arg(Utils::Misc::friendlyUnit(m_torrent->uploadPayloadRate(), true), ulAvg));
477 
478  m_ui->labelLastSeenCompleteVal->setText(m_torrent->lastSeenComplete().isValid() ? QLocale().toString(m_torrent->lastSeenComplete(), QLocale::ShortFormat) : tr("Never"));
479 
480  m_ui->labelCompletedOnVal->setText(m_torrent->completedTime().isValid() ? QLocale().toString(m_torrent->completedTime(), QLocale::ShortFormat) : QString {});
481 
482  m_ui->labelAddedOnVal->setText(QLocale().toString(m_torrent->addedTime(), QLocale::ShortFormat));
483 
484  if (m_torrent->hasMetadata())
485  {
486  m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave()));
487 
489  {
490  // Pieces availability
493  m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3));
494  }
495  else
496  {
497  showPiecesAvailability(false);
498  }
499 
500  // Progress
501  qreal progress = m_torrent->progress() * 100.;
502  m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + '%');
504  }
505  else
506  {
507  showPiecesAvailability(false);
508  }
509  }
510  break;
512  // Trackers
514  break;
516  // Load peers
518  break;
520  // Files progress
521  if (m_torrent->hasMetadata())
522  {
523  qDebug("Updating priorities in files tab");
524  m_ui->filesList->setUpdatesEnabled(false);
525 
526  // Load torrent content if not yet done so
527  const bool isContentInitialized = m_propListModel->model()->hasIndex(0, 0);
528  if (!isContentInitialized)
529  {
530  // List files in torrent
532  // Load file priorities
534  // Update file progress/availability
537 
538  // Expand single-item folders recursively.
539  // This will trigger sorting and filtering so do it after all relevant data is loaded.
540  QModelIndex currentIndex;
541  while (m_propListModel->rowCount(currentIndex) == 1)
542  {
543  currentIndex = m_propListModel->index(0, 0, currentIndex);
544  m_ui->filesList->setExpanded(currentIndex, true);
545  }
546  }
547  else
548  {
549  // Torrent content was loaded already, only make some updates
550 
553  // XXX: We don't update file priorities regularly for performance
554  // reasons. This means that priorities will not be updated if
555  // set from the Web UI.
556  // m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
557  }
558 
559  m_ui->filesList->setUpdatesEnabled(true);
560  }
561  break;
562  default:;
563  }
564 }
565 
567 {
568  if (!m_torrent)
569  return;
570 
571  m_ui->listWebSeeds->clear();
572  qDebug("Loading URL seeds");
573  const QVector<QUrl> hcSeeds = m_torrent->urlSeeds();
574  // Add url seeds
575  for (const QUrl &hcSeed : hcSeeds)
576  {
577  qDebug("Loading URL seed: %s", qUtf8Printable(hcSeed.toString()));
578  new QListWidgetItem(hcSeed.toString(), m_ui->listWebSeeds);
579  }
580 }
581 
582 QString PropertiesWidget::getFullPath(const QModelIndex &index) const
583 {
584  const QDir saveDir {m_torrent->actualStorageLocation()};
585 
587  {
588  const int fileIdx = m_propListModel->getFileIndex(index);
589  const QString filename {m_torrent->filePath(fileIdx)};
590  const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(filename))};
591  return fullPath;
592  }
593 
594  // folder type
595  const QModelIndex nameIndex {index.sibling(index.row(), TorrentContentModelItem::COL_NAME)};
596  QString folderPath {nameIndex.data().toString()};
597  for (QModelIndex modelIdx = m_propListModel->parent(nameIndex); modelIdx.isValid(); modelIdx = modelIdx.parent())
598  folderPath.prepend(modelIdx.data().toString() + '/');
599 
600  const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(folderPath))};
601  return fullPath;
602 }
603 
604 void PropertiesWidget::openItem(const QModelIndex &index) const
605 {
606  if (!index.isValid())
607  return;
608 
609  m_torrent->flushCache(); // Flush data
611 }
612 
613 void PropertiesWidget::openParentFolder(const QModelIndex &index) const
614 {
615  const QString path = getFullPath(index);
616  m_torrent->flushCache(); // Flush data
617 #ifdef Q_OS_MACOS
618  MacUtils::openFiles({path});
619 #else
621 #endif
622 }
623 
625 {
626  if (!m_torrent) return;
627 
628  const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
629  if (selectedRows.empty()) return;
630 
631  QMenu *menu = new QMenu(this);
632  menu->setAttribute(Qt::WA_DeleteOnClose);
633 
634  if (selectedRows.size() == 1)
635  {
636  const QModelIndex index = selectedRows[0];
637 
638  menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")
639  , this, [this, index]() { openItem(index); });
640  menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")
641  , this, [this, index]() { openParentFolder(index); });
642  menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
643  , this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
644  menu->addSeparator();
645  }
646 
647  if (!m_torrent->isSeed())
648  {
649  const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
650  {
651  const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
652  for (const QModelIndex &index : selectedRows)
653  {
654  m_propListModel->setData(index.sibling(index.row(), PRIORITY)
655  , static_cast<int>(prio));
656  }
657 
658  // Save changes
659  this->applyPriorities();
660  };
661 
662  QMenu *subMenu = menu->addMenu(tr("Priority"));
663 
664  subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
665  {
667  });
668  subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
669  {
671  });
672  subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
673  {
675  });
676  subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
677  {
679  });
680  subMenu->addSeparator();
681  subMenu->addAction(tr("By shown file order"), subMenu, [this]()
682  {
683  // Equally distribute the selected items into groups and for each group assign
684  // a download priority that will apply to each item. The number of groups depends on how
685  // many "download priority" are available to be assigned
686 
687  const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
688 
689  const qsizetype priorityGroups = 3;
690  const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
691 
692  for (qsizetype i = 0; i < selectedRows.length(); ++i)
693  {
695  switch (i / priorityGroupSize)
696  {
697  case 0:
699  break;
700  case 1:
702  break;
703  default:
704  case 2:
706  break;
707  }
708 
709  const QModelIndex &index = selectedRows[i];
710  m_propListModel->setData(index.sibling(index.row(), PRIORITY)
711  , static_cast<int>(priority));
712 
713  // Save changes
714  this->applyPriorities();
715  }
716  });
717  }
718 
719  // The selected torrent might have disappeared during exec()
720  // so we just close menu when an appropriate model is reset
721  connect(m_ui->filesList->model(), &QAbstractItemModel::modelAboutToBeReset
722  , menu, [menu]()
723  {
724  menu->setActiveAction(nullptr);
725  menu->close();
726  });
727 
728  menu->popup(QCursor::pos());
729 }
730 
732 {
733  if (!m_torrent) return;
734 
735  const QModelIndexList rows = m_ui->listWebSeeds->selectionModel()->selectedRows();
736 
737  QMenu *menu = new QMenu(this);
738  menu->setAttribute(Qt::WA_DeleteOnClose);
739 
740  menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
741 
742  if (!rows.isEmpty())
743  {
744  menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")
746  menu->addSeparator();
747  menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")
749  menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")
751  }
752 
753  menu->popup(QCursor::pos());
754 }
755 
757 {
758  const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0);
759  if (selectedIndexes.size() != 1)
760  return;
761  openItem(selectedIndexes.first());
762 }
763 
765 {
766  // Speed widget
767  if (Preferences::instance()->isSpeedWidgetEnabled())
768  {
769  if (!qobject_cast<SpeedWidget *>(m_speedWidget))
770  {
771  if (m_speedWidget)
772  {
773  m_ui->speedLayout->removeWidget(m_speedWidget);
774  delete m_speedWidget;
775  }
776 
777  m_speedWidget = new SpeedWidget(this);
778  m_ui->speedLayout->addWidget(m_speedWidget);
779  }
780  }
781  else
782  {
783  if (!qobject_cast<QLabel *>(m_speedWidget))
784  {
785  if (m_speedWidget)
786  {
787  m_ui->speedLayout->removeWidget(m_speedWidget);
788  delete m_speedWidget;
789  }
790 
791  const auto displayText = QString::fromLatin1("<center><b>%1</b><p>%2</p></center>")
792  .arg(tr("Speed graphs are disabled"), tr("You can enable it in Advanced Options"));
793  auto *label = new QLabel(displayText, this);
794  label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
795  m_speedWidget = label;
796  m_ui->speedLayout->addWidget(m_speedWidget);
797  }
798  }
799 }
800 
802 {
803  bool ok = false;
804  // Ask user for a new url seed
805  const QString urlSeed = AutoExpandableDialog::getText(this, tr("New URL seed", "New HTTP source"),
806  tr("New URL seed:"), QLineEdit::Normal,
807  QLatin1String("http://www."), &ok);
808  if (!ok) return;
809  qDebug("Adding %s web seed", qUtf8Printable(urlSeed));
810  if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty())
811  {
812  QMessageBox::warning(this, "qBittorrent",
813  tr("This URL seed is already in the list."),
814  QMessageBox::Ok);
815  return;
816  }
817  if (m_torrent)
818  m_torrent->addUrlSeeds({urlSeed});
819  // Refresh the seeds list
820  loadUrlSeeds();
821 }
822 
824 {
825  const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
826  if (selectedItems.isEmpty()) return;
827 
828  QVector<QUrl> urlSeeds;
829  urlSeeds.reserve(selectedItems.size());
830 
831  for (const QListWidgetItem *item : selectedItems)
832  urlSeeds << item->text();
833 
834  m_torrent->removeUrlSeeds(urlSeeds);
835  // Refresh list
836  loadUrlSeeds();
837 }
838 
840 {
841  const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
842  if (selectedItems.isEmpty()) return;
843 
844  QStringList urlsToCopy;
845  for (const QListWidgetItem *item : selectedItems)
846  urlsToCopy << item->text();
847 
848  QApplication::clipboard()->setText(urlsToCopy.join('\n'));
849 }
850 
852 {
853  const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
854  if (selectedItems.size() != 1) return;
855 
856  const QListWidgetItem *selectedItem = selectedItems.last();
857  const QString oldSeed = selectedItem->text();
858  bool result;
859  const QString newSeed = AutoExpandableDialog::getText(this, tr("Web seed editing"),
860  tr("Web seed URL:"), QLineEdit::Normal,
861  oldSeed, &result);
862  if (!result) return;
863 
864  if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
865  {
866  QMessageBox::warning(this, QLatin1String("qBittorrent"),
867  tr("This URL seed is already in the list."),
868  QMessageBox::Ok);
869  return;
870  }
871 
872  m_torrent->removeUrlSeeds({oldSeed});
873  m_torrent->addUrlSeeds({newSeed});
874  loadUrlSeeds();
875 }
876 
878 {
880 }
881 
883 {
884  if (m_torrent)
885  applyPriorities();
886 }
887 
888 void PropertiesWidget::filterText(const QString &filter)
889 {
890  const QString pattern = Utils::String::wildcardToRegexPattern(filter);
891  m_propListModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
892  if (filter.isEmpty())
893  {
894  m_ui->filesList->collapseAll();
895  m_ui->filesList->expand(m_propListModel->index(0, 0));
896  }
897  else
898  {
899  m_ui->filesList->expandAll();
900  }
901 }
static QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode=QLineEdit::Normal, const QString &text={}, bool *ok=nullptr, bool excludeExtension=false, Qt::InputMethodHints inputMethodHints=Qt::ImhNone)
virtual QString filePath(int index) const =0
SHA1Hash v1() const
Definition: infohash.cpp:44
SHA256Hash v2() const
Definition: infohash.cpp:53
static Session * instance()
Definition: session.cpp:997
void torrentSavePathChanged(Torrent *torrent)
void torrentMetadataReceived(Torrent *torrent)
virtual qlonglong totalSize() const =0
virtual qreal distributedCopies() const =0
virtual int uploadPayloadRate() const =0
virtual QVector< int > pieceAvailability() const =0
virtual int seedsCount() const =0
virtual int downloadLimit() const =0
virtual void flushCache() const =0
virtual qlonglong nextAnnounce() const =0
virtual QVector< DownloadPriority > filePriorities() const =0
virtual bool isChecking() const =0
virtual QVector< qreal > filesProgress() const =0
virtual qlonglong totalUpload() const =0
virtual int piecesHave() const =0
virtual QDateTime creationDate() const =0
virtual QString comment() const =0
virtual qlonglong pieceLength() const =0
virtual qlonglong activeTime() const =0
virtual qlonglong totalPayloadDownload() const =0
virtual InfoHash infoHash() const =0
virtual void prioritizeFiles(const QVector< DownloadPriority > &priorities)=0
virtual qlonglong finishedTime() const =0
virtual int piecesCount() const =0
virtual int uploadLimit() const =0
virtual int connectionsLimit() const =0
virtual int totalSeedsCount() const =0
virtual bool isQueued() const =0
virtual qlonglong totalDownload() const =0
virtual QString creator() const =0
virtual QString savePath() const =0
virtual qreal realRatio() const =0
virtual void addUrlSeeds(const QVector< QUrl > &urlSeeds)=0
virtual QString actualStorageLocation() const =0
virtual QBitArray downloadingPieces() const =0
virtual bool isPaused() const =0
virtual QDateTime addedTime() const =0
virtual qlonglong totalPayloadUpload() const =0
virtual QBitArray pieces() const =0
virtual QVector< qreal > availableFileFractions() const =0
fraction of file pieces that are available at least from one peer
virtual int connectionsCount() const =0
virtual QDateTime completedTime() const =0
virtual QVector< QUrl > urlSeeds() const =0
virtual qlonglong wastedSize() const =0
virtual QDateTime lastSeenComplete() const =0
virtual int leechsCount() const =0
virtual qreal progress() const =0
virtual bool isSeed() const =0
virtual qlonglong eta() const =0
static const qreal MAX_RATIO
Definition: torrent.h:110
virtual void removeUrlSeeds(const QVector< QUrl > &urlSeeds)=0
virtual int downloadPayloadRate() const =0
virtual int totalLeechersCount() const =0
virtual bool hasMetadata() const =0
QString toString() const
Definition: digest32.h:85
bool isValid() const
Definition: digest32.h:58
void setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces)
void updatePeerCountryResolutionState()
void loadPeers(const BitTorrent::Torrent *torrent)
void updatePeerHostNameResolutionState()
void setAvailability(const QVector< int > &avail)
void setTorrent(const BitTorrent::Torrent *torrent)
Definition: piecesbar.cpp:120
static Preferences * instance()
void setPropCurTab(int tab)
int getPropCurTab() const
void changed()
QString getPropSplitterSizes() const
bool getPropVisible() const
void setPropVisible(bool visible)
void setPropFileListState(const QByteArray &state)
void setPropSplitterSizes(const QString &sizes)
QByteArray getPropFileListState() const
void filteredFilesChanged() const
void setCurrentIndex(int index)
Definition: proptabbar.cpp:115
void tabChanged(int index)
int currentIndex() const
Definition: proptabbar.cpp:110
void visibilityToggled(bool visible)
BitTorrent::Torrent * m_torrent
void openParentFolder(const QModelIndex &index) const
QList< int > m_slideSizes
DownloadedPiecesBar * m_downloadedPieces
void showPiecesAvailability(bool show)
QString getFullPath(const QModelIndex &index) const
void loadTorrentInfos(BitTorrent::Torrent *const torrent)
PeerListWidget * m_peerList
void displayWebSeedListMenu(const QPoint &)
TrackerListWidget * m_trackerList
PropertiesWidget(QWidget *parent)
TrackerListWidget * getTrackerList() const
void filterText(const QString &filter)
BitTorrent::Torrent * getCurrentTorrent() const
void loadTrackers(BitTorrent::Torrent *const torrent)
void showPiecesDownloaded(bool show)
PieceAvailabilityBar * m_piecesAvailability
LineEdit * m_contentFilterLine
QTreeView * getFilesList() const
void openItem(const QModelIndex &index) const
void setVisibility(bool visible)
void displayFilesListMenu(const QPoint &)
void updateTorrentInfos(BitTorrent::Torrent *const torrent)
Ui::PropertiesWidget * m_ui
PropListDelegate * m_propListDelegate
void updateSavePath(BitTorrent::Torrent *const torrent)
PeerListWidget * getPeerList() const
~PropertiesWidget() override
PropTabBar * m_tabBar
void copySelectedWebSeedsToClipboard() const
TorrentContentFilterModel * m_propListModel
TorrentContentModelItem::ItemType itemType(const QModelIndex &index) const
TorrentContentModel * model() const
int getFileIndex(const QModelIndex &index) const
QModelIndex parent(const QModelIndex &child) const override
void updateFilesPriorities(const QVector< BitTorrent::DownloadPriority > &fprio)
QVector< BitTorrent::DownloadPriority > getFilePriorities() const
void updateFilesProgress(const QVector< qreal > &fp)
void setupModelData(const BitTorrent::AbstractFileStorage &info)
void updateFilesAvailability(const QVector< qreal > &fa)
static UIThemeManager * instance()
QIcon getIcon(const QString &iconId, const QString &fallback={}) const
void openFiles(const QSet< QString > &pathsList)
Definition: macutilities.mm:98
QString expandPath(const QString &path)
Definition: fs.cpp:300
QString toNativePath(const QString &path)
Definition: fs.cpp:64
T scaledSize(const QWidget *widget, const T &size)
Definition: utils.h:46
QSize smallIconSize(const QWidget *widget=nullptr)
Definition: utils.cpp:97
void openFolderSelect(const QString &absolutePath)
Definition: utils.cpp:158
void openPath(const QString &absolutePath)
Definition: utils.cpp:146
QString parseHtmlLinks(const QString &rawText)
Definition: misc.cpp:404
QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap=-1)
Definition: misc.cpp:353
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
QString fromDouble(double n, int precision)
Definition: string.cpp:44
QString wildcardToRegexPattern(const QString &pattern)
Definition: string.cpp:57
QString toString(const lt::socket_type_t socketType)
Definition: session.cpp:183
@ PRIORITY
const qlonglong MAX_ETA
Definition: types.h:33
const char C_INFINITY[]