diff --git a/src/base/addtorrentmanager.cpp b/src/base/addtorrentmanager.cpp index a4bfe8047..ce446a72b 100644 --- a/src/base/addtorrentmanager.cpp +++ b/src/base/addtorrentmanager.cpp @@ -195,11 +195,9 @@ void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_p m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard)); } -void AddTorrentManager::releaseTorrentFileGuard(const QString &source) +std::shared_ptr AddTorrentManager::releaseTorrentFileGuard(const QString &source) { - auto torrentFileGuard = m_guardedTorrentFiles.take(source); - if (torrentFileGuard) - torrentFileGuard->setAutoRemove(false); + return m_guardedTorrentFiles.take(source); } bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr diff --git a/src/base/addtorrentmanager.h b/src/base/addtorrentmanager.h index 9f1f61039..0e2fc6f49 100644 --- a/src/base/addtorrentmanager.h +++ b/src/base/addtorrentmanager.h @@ -74,7 +74,7 @@ protected: void handleAddTorrentFailed(const QString &source, const QString &reason); void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent); void setTorrentFileGuard(const QString &source, std::shared_ptr torrentFileGuard); - void releaseTorrentFileGuard(const QString &source); + std::shared_ptr releaseTorrentFileGuard(const QString &source); private: void onDownloadFinished(const Net::DownloadResult &result); diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 492bfac3b..295b6434b 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -4065,14 +4065,29 @@ bool SessionImpl::isPaused() const void SessionImpl::pause() { - if (!m_isPaused) - { - if (isRestored()) - m_nativeSession->pause(); + if (m_isPaused) + return; - m_isPaused = true; - emit paused(); + if (isRestored()) + { + m_nativeSession->pause(); + + for (TorrentImpl *torrent : asConst(m_torrents)) + { + torrent->resetTrackerEntryStatuses(); + + const QList trackers = torrent->trackers(); + QHash updatedTrackers; + updatedTrackers.reserve(trackers.size()); + + for (const TrackerEntryStatus &status : trackers) + updatedTrackers.emplace(status.url, status); + emit trackerEntryStatusesUpdated(torrent, updatedTrackers); + } } + + m_isPaused = true; + emit paused(); } void SessionImpl::resume() @@ -5215,9 +5230,6 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath) if (torrent) { torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob); - // The torrent may become "finished" at the end of the move if it was moved - // from the "incomplete" location after downloading finished. - processPendingFinishedTorrents(); } else if (!torrentHasOutstandingJob) { @@ -5481,6 +5493,11 @@ void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value) // Read alerts sent by libtorrent session void SessionImpl::readAlerts() { + // cache current datetime of Qt and libtorrent clocks in order + // to optimize conversion of time points from lt to Qt clocks + m_ltNow = lt::clock_type::now(); + m_qNow = QDateTime::currentDateTime(); + const std::vector alerts = getPendingAlerts(); Q_ASSERT(m_loadedTorrents.isEmpty()); @@ -5507,6 +5524,9 @@ void SessionImpl::readAlerts() } } + // Some torrents may become "finished" after different alerts handling. + processPendingFinishedTorrents(); + processTrackerStatuses(); } @@ -6146,8 +6166,6 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert) if (!updatedTorrents.isEmpty()) emit torrentsUpdated(updatedTorrents); - processPendingFinishedTorrents(); - if (m_needSaveTorrentsQueue) saveTorrentsQueue(); @@ -6344,3 +6362,9 @@ void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString m_removingTorrents.erase(removingTorrentDataIter); } + +QDateTime SessionImpl::fromLTTimePoint32(const libtorrent::time_point32 &timePoint) const +{ + const auto secsSinceNow = lt::duration_cast(timePoint - m_ltNow + lt::milliseconds(500)).count(); + return m_qNow.addSecs(secsSinceNow); +} diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index b387b52a1..ef0c6b401 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -475,6 +475,8 @@ namespace BitTorrent void addMappedPorts(const QSet &ports); void removeMappedPorts(const QSet &ports); + QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) const; + template void invoke(Func &&func) { @@ -820,6 +822,9 @@ namespace BitTorrent QList m_pendingFinishedTorrents; + QDateTime m_qNow; + lt::clock_type::time_point m_ltNow; + friend void Session::initInstance(); friend void Session::freeInstance(); friend Session *Session::instance(); diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 9cd370839..a3b82eff5 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -92,37 +93,28 @@ namespace return entry; } - QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) - { - const auto ltNow = lt::clock_type::now(); - const auto qNow = QDateTime::currentDateTime(); - const auto secsSinceNow = lt::duration_cast(timePoint - ltNow + lt::milliseconds(500)).count(); - - return qNow.addSecs(secsSinceNow); - } - QString toString(const lt::tcp::endpoint <TCPEndpoint) { - return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str()); + static QCache cache; + + if (const QString *endpointName = cache.object(ltTCPEndpoint)) + return *endpointName; + + const std::string tmp = (std::ostringstream() << ltTCPEndpoint).str(); + const auto endpointName = QString::fromLatin1(tmp.c_str(), tmp.size()); + cache.insert(ltTCPEndpoint, new QString(endpointName)); + return endpointName; } + template void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry - , const QSet &btProtocols, const QHash> &updateInfo) + , const QSet &btProtocols, const QHash> &updateInfo + , const FromLTTimePoint32Func &fromLTTimePoint32) { Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url)); trackerEntryStatus.tier = nativeEntry.tier; - // remove outdated endpoints - trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash, TrackerEndpointStatus>::iterator &iter) - { - return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend() - , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint) - { - return (endpointName == toString(existingEndpoint.local_endpoint)); - }); - }); - const auto numEndpoints = static_cast(nativeEntry.endpoints.size()) * btProtocols.size(); int numUpdating = 0; @@ -205,6 +197,19 @@ namespace } } + if (trackerEntryStatus.endpoints.size() > numEndpoints) + { + // remove outdated endpoints + trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash, TrackerEndpointStatus>::iterator &iter) + { + return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend() + , [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint) + { + return (endpointName == toString(existingEndpoint.local_endpoint)); + }); + }); + } + if (numEndpoints > 0) { if (numUpdating > 0) @@ -1632,9 +1637,19 @@ void TorrentImpl::forceRecheck() return; m_nativeHandle.force_recheck(); + // We have to force update the cached state, otherwise someone will be able to get // an incorrect one during the interval until the cached state is updated in a regular way. m_nativeStatus.state = lt::torrent_status::checking_resume_data; + m_nativeStatus.pieces.clear_all(); + m_nativeStatus.num_pieces = 0; + m_ltAddTorrentParams.have_pieces.clear(); + m_ltAddTorrentParams.verified_pieces.clear(); + m_ltAddTorrentParams.unfinished_pieces.clear(); + m_completedFiles.fill(false); + m_filesProgress.fill(0); + m_pieces.fill(false); + m_unchecked = false; if (m_hasMissingFiles) { @@ -1647,14 +1662,6 @@ void TorrentImpl::forceRecheck() } } - m_unchecked = false; - - m_completedFiles.fill(false); - m_filesProgress.fill(0); - m_pieces.fill(false); - m_nativeStatus.pieces.clear_all(); - m_nativeStatus.num_pieces = 0; - if (isStopped()) { // When "force recheck" is applied on Stopped torrent, we start them to perform checking @@ -1759,7 +1766,13 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr #else const QSet btProtocols {1}; #endif - ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo); + + const auto fromLTTimePoint32 = [this](const lt::time_point32 &timePoint) + { + return m_session->fromLTTimePoint32(timePoint); + }; + ::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo, fromLTTimePoint32); + return *it; } @@ -2142,6 +2155,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) m_ltAddTorrentParams.have_pieces.clear(); m_ltAddTorrentParams.verified_pieces.clear(); + m_ltAddTorrentParams.unfinished_pieces.clear(); m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti; @@ -2184,23 +2198,37 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) { - if (m_hasMissingFiles) { - const auto havePieces = m_ltAddTorrentParams.have_pieces; - const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces; - const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces; + decltype(params.have_pieces) havePieces; + decltype(params.unfinished_pieces) unfinishedPieces; + decltype(params.verified_pieces) verifiedPieces; + + // The resume data obtained from libtorrent contains an empty "progress" in the following cases: + // 1. when it was requested at a time when the initial resume data has not yet been checked, + // 2. when initial resume data was rejected + // We should preserve the initial "progress" in such cases. + const bool needPreserveProgress = m_hasMissingFiles + || (!m_ltAddTorrentParams.have_pieces.empty() && params.have_pieces.empty()); + const bool preserveSeedMode = !m_hasMissingFiles && !hasMetadata() + && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode); + + if (needPreserveProgress) + { + havePieces = std::move(m_ltAddTorrentParams.have_pieces); + unfinishedPieces = std::move(m_ltAddTorrentParams.unfinished_pieces); + verifiedPieces = std::move(m_ltAddTorrentParams.verified_pieces); + } - // Update recent resume data but preserve existing progress - m_ltAddTorrentParams = params; - m_ltAddTorrentParams.have_pieces = havePieces; - m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces; - m_ltAddTorrentParams.verified_pieces = verifiedPieces; - } - else - { - const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode)); // Update recent resume data m_ltAddTorrentParams = params; + + if (needPreserveProgress) + { + m_ltAddTorrentParams.have_pieces = std::move(havePieces); + m_ltAddTorrentParams.unfinished_pieces = std::move(unfinishedPieces); + m_ltAddTorrentParams.verified_pieces = std::move(verifiedPieces); + } + if (preserveSeedMode) m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode; } @@ -2350,7 +2378,8 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) // only apply Mark-of-the-Web to new download files - if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading()) + if (Preferences::instance()->isMarkOfTheWebEnabled() + && (m_nativeStatus.state == lt::torrent_status::downloading)) { const Path fullpath = actualStorageLocation() / actualPath; Utils::OS::applyMarkOfTheWeb(fullpath); diff --git a/src/base/utils/os.cpp b/src/base/utils/os.cpp index ee542e48e..2f1c77adb 100644 --- a/src/base/utils/os.cpp +++ b/src/base/utils/os.cpp @@ -31,6 +31,8 @@ #include "os.h" #ifdef Q_OS_WIN +#include + #include #include #include @@ -42,6 +44,8 @@ #include #endif // Q_OS_MACOS +#include + #ifdef QBT_USES_DBUS #include #endif // QBT_USES_DBUS @@ -283,34 +287,49 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230 // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955 + const CFStringRef fileString = file.toString().toCFString(); + [[maybe_unused]] const auto fileStringGuard = qScopeGuard([&fileString] { ::CFRelease(fileString); }); + const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault + , fileString, kCFURLPOSIXPathStyle, false); + [[maybe_unused]] const auto fileURLGuard = qScopeGuard([&fileURL] { ::CFRelease(fileURL); }); + + if (CFDictionaryRef currentProperties = nullptr; + ::CFURLCopyResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, ¤tProperties, NULL) + && currentProperties) + { + [[maybe_unused]] const auto currentPropertiesGuard = qScopeGuard([¤tProperties] { ::CFRelease(currentProperties); }); + + if (CFStringRef quarantineType = nullptr; + ::CFDictionaryGetValueIfPresent(currentProperties, kLSQuarantineTypeKey, reinterpret_cast(&quarantineType)) + && quarantineType) + { + if (::CFStringCompare(quarantineType, kLSQuarantineTypeOtherDownload, 0) == kCFCompareEqualTo) + return true; + } + } + CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - if (properties == NULL) + if (!properties) return false; + [[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); }); ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload); if (!url.isEmpty()) ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString()); - const CFStringRef fileString = file.toString().toCFString(); - const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault - , fileString, kCFURLPOSIXPathStyle, false); - const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey , properties, NULL); - - ::CFRelease(fileURL); - ::CFRelease(fileString); - ::CFRelease(properties); - return success; #elif defined(Q_OS_WIN) const QString zoneIDStream = file.toString() + u":Zone.Identifier"; - HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE + + HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), (GENERIC_READ | GENERIC_WRITE) , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE) , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) return false; + [[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); }); // 5.6.1 Zone.Identifier Stream Name // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 @@ -318,10 +337,27 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n") + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8(); + if (LARGE_INTEGER streamSize = {0}; + ::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0)) + { + const DWORD expectedReadSize = std::min(streamSize.QuadPart, 1024); + QByteArray buf {expectedReadSize, '\0'}; + + if (DWORD actualReadSize = 0; + ::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize)) + { + if (buf.startsWith("[ZoneTransfer]\r\n") && buf.contains("\r\nZoneId=3\r\n") && buf.contains("\r\nHostUrl=")) + return true; + } + } + + if (!::SetFilePointerEx(handle, {0}, nullptr, FILE_BEGIN)) + return false; + if (!::SetEndOfFile(handle)) + return false; + DWORD written = 0; const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr); - ::CloseHandle(handle); - return writeResult && (written == zoneID.size()); #endif } diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 07eb4cc16..5417e6dbe 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -821,6 +821,8 @@ void AddNewTorrentDialog::reject() if (!m_currentContext) [[unlikely]] return; + emit torrentRejected(m_currentContext->torrentDescr); + const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr; const bool hasMetadata = torrentDescr.info().has_value(); if (!hasMetadata) diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index f81bd305b..bc530953b 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -66,6 +66,7 @@ public: signals: void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams); + void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor); private slots: void updateDiskSpaceLabel(); diff --git a/src/gui/guiaddtorrentmanager.cpp b/src/gui/guiaddtorrentmanager.cpp index c99d4afce..d600a4e17 100644 --- a/src/gui/guiaddtorrentmanager.cpp +++ b/src/gui/guiaddtorrentmanager.cpp @@ -235,15 +235,22 @@ bool GUIAddTorrentManager::processTorrent(const QString &source dlg->setAttribute(Qt::WA_DeleteOnClose); m_dialogs[infoHash] = dlg; connect(dlg, &AddNewTorrentDialog::torrentAccepted, this - , [this, source](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams) - { - addTorrentToSession(source, torrentDescr, addTorrentParams); - }); - connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg] + , [this, source, dlg](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams) { if (dlg->isDoNotDeleteTorrentChecked()) - releaseTorrentFileGuard(source); + { + if (auto torrentFileGuard = releaseTorrentFileGuard(source)) + torrentFileGuard->setAutoRemove(false); + } + addTorrentToSession(source, torrentDescr, addTorrentParams); + }); + connect(dlg, &AddNewTorrentDialog::torrentRejected, this, [this, source] + { + releaseTorrentFileGuard(source); + }); + connect(dlg, &QDialog::finished, this, [this, source, infoHash] + { m_dialogs.remove(infoHash); }); diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 60c1c8aef..3dc0d113f 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -1703,11 +1703,10 @@ void OptionsDialog::initializeStyleCombo() QStringList styleNames = QStyleFactory::keys(); std::sort(styleNames.begin(), styleNames.end(), Utils::Compare::NaturalLessThan()); m_ui->comboStyle->addItems(styleNames); + const QString prefStyleName = Preferences::instance()->getStyle(); const QString selectedStyleName = prefStyleName.isEmpty() ? QApplication::style()->name() : prefStyleName; - - if (selectedStyleName.compare(u"system"_s, Qt::CaseInsensitive) != 0) - m_ui->comboStyle->setCurrentText(selectedStyleName); + m_ui->comboStyle->setCurrentIndex(m_ui->comboStyle->findText(selectedStyleName, Qt::MatchFixedString)); #else m_ui->labelStyle->hide(); m_ui->comboStyle->hide(); diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index 1128a561e..cabd8a135 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -1,7 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2024 Vladimir Golovnev * Copyright (C) 2020, Will Da Silva - * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -120,6 +120,7 @@ SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow) #endif connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::tabChanged); + connect(m_ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, &SearchWidget::tabMoved); const auto *searchManager = SearchPluginManager::instance(); const auto onPluginChanged = [this]() @@ -262,6 +263,11 @@ void SearchWidget::tabChanged(const int index) m_currentSearchTab = ((index < 0) ? nullptr : m_allTabs.at(m_ui->tabWidget->currentIndex())); } +void SearchWidget::tabMoved(const int from, const int to) +{ + m_allTabs.move(from, to); +} + void SearchWidget::selectMultipleBox([[maybe_unused]] const int index) { if (selectedPlugin() == u"multi") diff --git a/src/gui/search/searchwidget.h b/src/gui/search/searchwidget.h index 06544308c..a1b18e470 100644 --- a/src/gui/search/searchwidget.h +++ b/src/gui/search/searchwidget.h @@ -1,7 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2024 Vladimir Golovnev * Copyright (C) 2020, Will Da Silva - * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -66,6 +66,7 @@ private slots: private: bool eventFilter(QObject *object, QEvent *event) override; void tabChanged(int index); + void tabMoved(int from, int to); void closeTab(int index); void closeAllTabs(); void tabStatusChanged(QWidget *tab); diff --git a/src/gui/transferlistfilters/trackersfilterwidget.cpp b/src/gui/transferlistfilters/trackersfilterwidget.cpp index a166c7da7..20fc4ac1d 100644 --- a/src/gui/transferlistfilters/trackersfilterwidget.cpp +++ b/src/gui/transferlistfilters/trackersfilterwidget.cpp @@ -394,15 +394,11 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren { if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Working) { + // remove tracker from "error" and "tracker error" categories if (errorHashesIt != m_errors.end()) - { errorHashesIt->remove(trackerEntryStatus.url); - } - if (trackerErrorHashesIt != m_trackerErrors.end()) - { trackerErrorHashesIt->remove(trackerEntryStatus.url); - } const bool hasNoWarningMessages = std::all_of(trackerEntryStatus.endpoints.cbegin(), trackerEntryStatus.endpoints.cend() , [](const BitTorrent::TrackerEndpointStatus &endpointEntry) @@ -426,12 +422,24 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren else if ((trackerEntryStatus.state == BitTorrent::TrackerEndpointState::NotWorking) || (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Unreachable)) { + // remove tracker from "tracker error" and "warning" categories + if (warningHashesIt != m_warnings.end()) + warningHashesIt->remove(trackerEntryStatus.url); + if (trackerErrorHashesIt != m_trackerErrors.end()) + trackerErrorHashesIt->remove(trackerEntryStatus.url); + if (errorHashesIt == m_errors.end()) errorHashesIt = m_errors.insert(id, {}); errorHashesIt->insert(trackerEntryStatus.url); } else if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::TrackerError) { + // remove tracker from "error" and "warning" categories + if (warningHashesIt != m_warnings.end()) + warningHashesIt->remove(trackerEntryStatus.url); + if (errorHashesIt != m_errors.end()) + errorHashesIt->remove(trackerEntryStatus.url); + if (trackerErrorHashesIt == m_trackerErrors.end()) trackerErrorHashesIt = m_trackerErrors.insert(id, {}); trackerErrorHashesIt->insert(trackerEntryStatus.url); diff --git a/src/gui/uithememanager.cpp b/src/gui/uithememanager.cpp index 4a31f2928..f20f5e0a3 100644 --- a/src/gui/uithememanager.cpp +++ b/src/gui/uithememanager.cpp @@ -47,16 +47,9 @@ namespace { bool isDarkTheme() { - switch (qApp->styleHints()->colorScheme()) - { - case Qt::ColorScheme::Dark: - return true; - case Qt::ColorScheme::Light: - return false; - default: - // fallback to custom method - return (qApp->palette().color(QPalette::Active, QPalette::Base).lightness() < 127); - } + const QPalette palette = qApp->palette(); + const QColor &color = palette.color(QPalette::Active, QPalette::Base); + return (color.lightness() < 127); } }