Refresh search results colors once color scheme is changed
[qBittorrent.git] / src / gui / search / searchjobwidget.cpp
blobbd637262e03ce6a09e2eb62af9d34301f504c4d1
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "searchjobwidget.h"
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QDesktopServices>
35 #include <QHeaderView>
36 #include <QKeyEvent>
37 #include <QMenu>
38 #include <QPalette>
39 #include <QStandardItemModel>
40 #include <QUrl>
42 #include "base/preferences.h"
43 #include "base/search/searchdownloadhandler.h"
44 #include "base/search/searchhandler.h"
45 #include "base/search/searchpluginmanager.h"
46 #include "base/utils/misc.h"
47 #include "gui/interfaces/iguiapplication.h"
48 #include "gui/lineedit.h"
49 #include "gui/uithememanager.h"
50 #include "searchsortmodel.h"
51 #include "ui_searchjobwidget.h"
53 namespace
55 enum DataRole
57 LinkVisitedRole = Qt::UserRole + 100
60 QColor visitedRowColor()
62 return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText);
66 SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
67 : GUIApplicationComponent(app, parent)
68 , m_ui {new Ui::SearchJobWidget}
69 , m_searchHandler {searchHandler}
70 , m_nameFilteringMode {u"Search/FilteringMode"_s}
72 m_ui->setupUi(this);
74 loadSettings();
76 header()->setFirstSectionMovable(true);
77 header()->setStretchLastSection(false);
78 header()->setTextElideMode(Qt::ElideRight);
80 // Set Search results list model
81 m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this);
82 m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
83 m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, tr("Size", "i.e: file size"));
84 m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources"));
85 m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources"));
86 m_searchListModel->setHeaderData(SearchSortModel::ENGINE_NAME, Qt::Horizontal, tr("Engine"));
87 m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Engine URL"));
88 m_searchListModel->setHeaderData(SearchSortModel::PUB_DATE, Qt::Horizontal, tr("Published On"));
89 // Set columns text alignment
90 m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
91 m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
92 m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
94 m_proxyModel = new SearchSortModel(this);
95 m_proxyModel->setDynamicSortFilter(true);
96 m_proxyModel->setSourceModel(m_searchListModel);
97 m_proxyModel->setNameFilter(searchHandler->pattern());
98 m_ui->resultsBrowser->setModel(m_proxyModel);
100 m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column
101 m_ui->resultsBrowser->hideColumn(SearchSortModel::DESC_LINK);
103 m_ui->resultsBrowser->setSelectionMode(QAbstractItemView::ExtendedSelection);
104 m_ui->resultsBrowser->setRootIsDecorated(false);
105 m_ui->resultsBrowser->setAllColumnsShowFocus(true);
106 m_ui->resultsBrowser->setSortingEnabled(true);
107 m_ui->resultsBrowser->setEditTriggers(QAbstractItemView::NoEditTriggers);
109 // Ensure that at least one column is visible at all times
110 bool atLeastOne = false;
111 for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
113 if (!m_ui->resultsBrowser->isColumnHidden(i))
115 atLeastOne = true;
116 break;
119 if (!atLeastOne)
120 m_ui->resultsBrowser->setColumnHidden(SearchSortModel::NAME, false);
121 // To also mitigate the above issue, we have to resize each column when
122 // its size is 0, because explicitly 'showing' the column isn't enough
123 // in the above scenario.
124 for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
126 if ((m_ui->resultsBrowser->columnWidth(i) <= 0) && !m_ui->resultsBrowser->isColumnHidden(i))
127 m_ui->resultsBrowser->resizeColumnToContents(i);
130 header()->setContextMenuPolicy(Qt::CustomContextMenu);
131 connect(header(), &QWidget::customContextMenuRequested, this, &SearchJobWidget::displayColumnHeaderMenu);
132 connect(header(), &QHeaderView::sectionResized, this, &SearchJobWidget::saveSettings);
133 connect(header(), &QHeaderView::sectionMoved, this, &SearchJobWidget::saveSettings);
134 connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
136 fillFilterComboBoxes();
138 updateFilter();
140 m_lineEditSearchResultsFilter = new LineEdit(this);
141 m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
142 m_lineEditSearchResultsFilter->setContextMenuPolicy(Qt::CustomContextMenu);
143 connect(m_lineEditSearchResultsFilter, &QWidget::customContextMenuRequested, this, &SearchJobWidget::showFilterContextMenu);
144 connect(m_lineEditSearchResultsFilter, &LineEdit::textChanged, this, &SearchJobWidget::filterSearchResults);
145 m_ui->horizontalLayout->insertWidget(0, m_lineEditSearchResultsFilter);
147 connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged)
148 , this, &SearchJobWidget::updateFilter);
149 connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
150 connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged)
151 , this, &SearchJobWidget::updateFilter);
152 connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
153 connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged)
154 , this, &SearchJobWidget::updateFilter);
155 connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
156 connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
157 , this, &SearchJobWidget::updateFilter);
158 connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
159 connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
160 , this, &SearchJobWidget::updateFilter);
161 connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
162 , this, &SearchJobWidget::updateFilter);
163 connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
164 , this, &SearchJobWidget::updateFilter);
166 connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked);
168 connect(searchHandler, &SearchHandler::newSearchResults, this, &SearchJobWidget::appendSearchResults);
169 connect(searchHandler, &SearchHandler::searchFinished, this, &SearchJobWidget::searchFinished);
170 connect(searchHandler, &SearchHandler::searchFailed, this, &SearchJobWidget::searchFailed);
171 connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
173 setStatusTip(statusText(m_status));
175 connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
178 SearchJobWidget::~SearchJobWidget()
180 saveSettings();
181 delete m_ui;
184 void SearchJobWidget::onItemDoubleClicked(const QModelIndex &index)
186 downloadTorrent(index);
189 QHeaderView *SearchJobWidget::header() const
191 return m_ui->resultsBrowser->header();
194 // Set the color of a row in data model
195 void SearchJobWidget::setRowColor(int row, const QColor &color)
197 for (int i = 0; i < m_proxyModel->columnCount(); ++i)
198 m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
201 void SearchJobWidget::setRowVisited(const int row)
203 m_proxyModel->setDynamicSortFilter(false);
205 m_proxyModel->setData(m_proxyModel->index(row, 0), true, LinkVisitedRole);
206 setRowColor(row, visitedRowColor());
208 m_proxyModel->setDynamicSortFilter(true);
211 void SearchJobWidget::onUIThemeChanged()
213 m_proxyModel->setDynamicSortFilter(false);
215 for (int row = 0; row < m_proxyModel->rowCount(); ++row)
217 const QVariant userData = m_proxyModel->data(m_proxyModel->index(row, 0), LinkVisitedRole);
218 const bool isVisited = userData.toBool();
219 if (isVisited)
220 setRowColor(row, visitedRowColor());
223 m_proxyModel->setDynamicSortFilter(true);
226 SearchJobWidget::Status SearchJobWidget::status() const
228 return m_status;
231 int SearchJobWidget::visibleResultsCount() const
233 return m_proxyModel->rowCount();
236 LineEdit *SearchJobWidget::lineEditSearchResultsFilter() const
238 return m_lineEditSearchResultsFilter;
241 void SearchJobWidget::cancelSearch()
243 m_searchHandler->cancelSearch();
246 void SearchJobWidget::downloadTorrents(const AddTorrentOption option)
248 const QModelIndexList rows = m_ui->resultsBrowser->selectionModel()->selectedRows();
249 for (const QModelIndex &rowIndex : rows)
250 downloadTorrent(rowIndex, option);
253 void SearchJobWidget::openTorrentPages() const
255 const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
256 for (const QModelIndex &rowIndex : rows)
258 const QString descrLink = m_proxyModel->data(
259 m_proxyModel->index(rowIndex.row(), SearchSortModel::DESC_LINK)).toString();
260 if (!descrLink.isEmpty())
261 QDesktopServices::openUrl(QUrl::fromEncoded(descrLink.toUtf8()));
265 void SearchJobWidget::copyTorrentURLs() const
267 copyField(SearchSortModel::DESC_LINK);
270 void SearchJobWidget::copyTorrentDownloadLinks() const
272 copyField(SearchSortModel::DL_LINK);
275 void SearchJobWidget::copyTorrentNames() const
277 copyField(SearchSortModel::NAME);
280 void SearchJobWidget::copyField(const int column) const
282 const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
283 QStringList list;
285 for (const QModelIndex &rowIndex : rows)
287 const QString field = m_proxyModel->data(
288 m_proxyModel->index(rowIndex.row(), column)).toString();
289 if (!field.isEmpty())
290 list << field;
293 if (!list.empty())
294 QApplication::clipboard()->setText(list.join(u'\n'));
297 void SearchJobWidget::setStatus(Status value)
299 if (m_status == value) return;
301 m_status = value;
302 setStatusTip(statusText(value));
303 emit statusChanged();
306 void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorrentOption option)
308 const QString torrentUrl = m_proxyModel->data(
309 m_proxyModel->index(rowIndex.row(), SearchSortModel::DL_LINK)).toString();
310 const QString engineName = m_proxyModel->data(
311 m_proxyModel->index(rowIndex.row(), SearchSortModel::ENGINE_NAME)).toString();
313 if (torrentUrl.startsWith(u"magnet:", Qt::CaseInsensitive))
315 addTorrentToSession(torrentUrl, option);
317 else
319 SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(engineName, torrentUrl);
320 connect(downloadHandler, &SearchDownloadHandler::downloadFinished
321 , this, [this, option](const QString &source) { addTorrentToSession(source, option); });
322 connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
325 setRowVisited(rowIndex.row());
328 void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
330 app()->addTorrentManager()->addTorrent(source, {}, option);
333 void SearchJobWidget::updateResultsCount()
335 const int totalResults = m_searchListModel->rowCount();
336 const int filteredResults = m_proxyModel->rowCount();
337 m_ui->resultsLbl->setText(tr("Results (showing <i>%1</i> out of <i>%2</i>):", "i.e: Search results")
338 .arg(filteredResults).arg(totalResults));
340 m_noSearchResults = (totalResults == 0);
341 emit resultsCountUpdated();
344 void SearchJobWidget::updateFilter()
346 using Utils::Misc::SizeUnit;
348 m_proxyModel->enableNameFilter(filteringMode() == NameFilteringMode::OnlyNames);
349 // we update size and seeds filter parameters in the model even if they are disabled
350 m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
351 m_proxyModel->setSizeFilter(
352 sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())),
353 sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
355 m_nameFilteringMode = filteringMode();
357 m_proxyModel->invalidate();
358 updateResultsCount();
361 void SearchJobWidget::fillFilterComboBoxes()
363 using Utils::Misc::SizeUnit;
364 using Utils::Misc::unitString;
366 QStringList unitStrings;
367 unitStrings.append(unitString(SizeUnit::Byte));
368 unitStrings.append(unitString(SizeUnit::KibiByte));
369 unitStrings.append(unitString(SizeUnit::MebiByte));
370 unitStrings.append(unitString(SizeUnit::GibiByte));
371 unitStrings.append(unitString(SizeUnit::TebiByte));
372 unitStrings.append(unitString(SizeUnit::PebiByte));
373 unitStrings.append(unitString(SizeUnit::ExbiByte));
375 m_ui->minSizeUnit->clear();
376 m_ui->maxSizeUnit->clear();
377 m_ui->minSizeUnit->addItems(unitStrings);
378 m_ui->maxSizeUnit->addItems(unitStrings);
380 m_ui->minSize->setValue(0);
381 m_ui->minSizeUnit->setCurrentIndex(static_cast<int>(SizeUnit::MebiByte));
383 m_ui->maxSize->setValue(-1);
384 m_ui->maxSizeUnit->setCurrentIndex(static_cast<int>(SizeUnit::GibiByte));
386 m_ui->filterMode->clear();
388 m_ui->filterMode->addItem(tr("Torrent names only"), static_cast<int>(NameFilteringMode::OnlyNames));
389 m_ui->filterMode->addItem(tr("Everywhere"), static_cast<int>(NameFilteringMode::Everywhere));
391 const QVariant selectedMode = static_cast<int>(m_nameFilteringMode.get(NameFilteringMode::OnlyNames));
392 const int index = m_ui->filterMode->findData(selectedMode);
393 m_ui->filterMode->setCurrentIndex((index == -1) ? 0 : index);
396 void SearchJobWidget::filterSearchResults(const QString &name)
398 const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
399 ? name : Utils::String::wildcardToRegexPattern(name));
400 m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
401 updateResultsCount();
404 void SearchJobWidget::showFilterContextMenu()
406 const Preferences *pref = Preferences::instance();
408 QMenu *menu = m_lineEditSearchResultsFilter->createStandardContextMenu();
409 menu->setAttribute(Qt::WA_DeleteOnClose);
410 menu->addSeparator();
412 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
413 useRegexAct->setCheckable(true);
414 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForSearchJob());
415 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForSearchJob);
416 connect(useRegexAct, &QAction::toggled, this, [this]() { filterSearchResults(m_lineEditSearchResultsFilter->text()); });
418 menu->popup(QCursor::pos());
421 void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
423 auto *menu = new QMenu(this);
424 menu->setAttribute(Qt::WA_DeleteOnClose);
426 menu->addAction(UIThemeManager::instance()->getIcon(u"download"_s)
427 , tr("Open download window"), this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); });
428 menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_s, u"download"_s)
429 , tr("Download"), this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); });
430 menu->addSeparator();
431 menu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_s), tr("Open description page")
432 , this, &SearchJobWidget::openTorrentPages);
434 QMenu *copySubMenu = menu->addMenu(
435 UIThemeManager::instance()->getIcon(u"edit-copy"_s), tr("Copy"));
437 copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"name"_s, u"edit-copy"_s), tr("Name")
438 , this, &SearchJobWidget::copyTorrentNames);
439 copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"insert-link"_s, u"edit-copy"_s), tr("Download link")
440 , this, &SearchJobWidget::copyTorrentDownloadLinks);
441 copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_s, u"edit-copy"_s), tr("Description page URL")
442 , this, &SearchJobWidget::copyTorrentURLs);
444 menu->popup(event->globalPos());
447 QString SearchJobWidget::statusText(SearchJobWidget::Status st)
449 switch (st)
451 case Status::Ongoing:
452 return tr("Searching...");
453 case Status::Finished:
454 return tr("Search has finished");
455 case Status::Aborted:
456 return tr("Search aborted");
457 case Status::Error:
458 return tr("An error occurred during search...");
459 case Status::NoResults:
460 return tr("Search returned no results");
461 default:
462 return {};
466 SearchJobWidget::NameFilteringMode SearchJobWidget::filteringMode() const
468 return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
471 void SearchJobWidget::loadSettings()
473 header()->restoreState(Preferences::instance()->getSearchTabHeaderState());
476 void SearchJobWidget::saveSettings() const
478 Preferences::instance()->setSearchTabHeaderState(header()->saveState());
481 int SearchJobWidget::visibleColumnsCount() const
483 int count = 0;
484 for (int i = 0, iMax = m_ui->resultsBrowser->header()->count(); i < iMax; ++i)
486 if (!m_ui->resultsBrowser->isColumnHidden(i))
487 ++count;
490 return count;
493 void SearchJobWidget::displayColumnHeaderMenu()
495 auto *menu = new QMenu(this);
496 menu->setAttribute(Qt::WA_DeleteOnClose);
497 menu->setTitle(tr("Column visibility"));
498 menu->setToolTipsVisible(true);
500 for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
502 const auto columnName = m_searchListModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
503 QAction *action = menu->addAction(columnName, this, [this, i](const bool checked)
505 if (!checked && (visibleColumnsCount() <= 1))
506 return;
508 m_ui->resultsBrowser->setColumnHidden(i, !checked);
510 if (checked && (m_ui->resultsBrowser->columnWidth(i) <= 5))
511 m_ui->resultsBrowser->resizeColumnToContents(i);
513 saveSettings();
515 action->setCheckable(true);
516 action->setChecked(!m_ui->resultsBrowser->isColumnHidden(i));
519 menu->addSeparator();
520 QAction *resizeAction = menu->addAction(tr("Resize columns"), this, [this]()
522 for (int i = 0, count = m_ui->resultsBrowser->header()->count(); i < count; ++i)
524 if (!m_ui->resultsBrowser->isColumnHidden(i))
525 m_ui->resultsBrowser->resizeColumnToContents(i);
527 saveSettings();
529 resizeAction->setToolTip(tr("Resize all non-hidden columns to the size of their contents"));
531 menu->popup(QCursor::pos());
534 void SearchJobWidget::searchFinished(bool cancelled)
536 if (cancelled)
537 setStatus(Status::Aborted);
538 else if (m_noSearchResults)
539 setStatus(Status::NoResults);
540 else
541 setStatus(Status::Finished);
544 void SearchJobWidget::searchFailed()
546 setStatus(Status::Error);
549 void SearchJobWidget::appendSearchResults(const QList<SearchResult> &results)
551 for (const SearchResult &result : results)
553 // Add item to search result list
554 int row = m_searchListModel->rowCount();
555 m_searchListModel->insertRow(row);
557 const auto setModelData = [this, row] (const int column, const QString &displayData
558 , const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {})
560 const QMap<int, QVariant> data =
562 {Qt::DisplayRole, displayData},
563 {SearchSortModel::UnderlyingDataRole, underlyingData},
564 {Qt::TextAlignmentRole, QVariant {textAlignmentData}}
566 m_searchListModel->setItemData(m_searchListModel->index(row, column), data);
569 setModelData(SearchSortModel::NAME, result.fileName, result.fileName);
570 setModelData(SearchSortModel::DL_LINK, result.fileUrl, result.fileUrl);
571 setModelData(SearchSortModel::ENGINE_NAME, result.engineName, result.engineName);
572 setModelData(SearchSortModel::ENGINE_URL, result.siteUrl, result.siteUrl);
573 setModelData(SearchSortModel::DESC_LINK, result.descrLink, result.descrLink);
574 setModelData(SearchSortModel::SIZE, Utils::Misc::friendlyUnit(result.fileSize), result.fileSize, (Qt::AlignRight | Qt::AlignVCenter));
575 setModelData(SearchSortModel::SEEDS, QString::number(result.nbSeeders), result.nbSeeders, (Qt::AlignRight | Qt::AlignVCenter));
576 setModelData(SearchSortModel::LEECHES, QString::number(result.nbLeechers), result.nbLeechers, (Qt::AlignRight | Qt::AlignVCenter));
577 setModelData(SearchSortModel::PUB_DATE, QLocale().toString(result.pubDate.toLocalTime(), QLocale::ShortFormat), result.pubDate);
580 updateResultsCount();
583 void SearchJobWidget::keyPressEvent(QKeyEvent *event)
585 switch (event->key())
587 case Qt::Key_Enter:
588 case Qt::Key_Return:
589 downloadTorrents();
590 break;
591 default:
592 QWidget::keyPressEvent(event);