refactor: don't load the same stock more than once (#1041)

* refactor: don't load the same stock more than once

Some actions share an icon -- for example, "start all", "start now", and
"start" each use "media-playback-start". When this happens, get the icon
once and cache it to avoid hitting the disk more often than necessary.

In addition, the statusbar's network transfer icon was being reloaded in
a periodic upkeep timer, reloading with QIcon::fromTheme each time. Only
give icons are needed, so load them once and cache them.

* refactor: better lookup of torrent mime-type icons

filename-to-mime-type and mime-type-to-icon lookups are both expensive,
so do a better job of detecting top-level folders and caching the icons
based on file suffixes.

This also lets find a good mime icon even if the torrent doesn't have
its 'files' property populated yet from RPC.
This commit is contained in:
Charles Kerr 2019-11-06 15:09:04 -06:00 committed by GitHub
parent edbdeae623
commit aa9b752cd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 42 deletions

View File

@ -89,10 +89,8 @@ QIcon MainWindow::getStockIcon(QString const& name, int fallback)
return icon;
}
QIcon MainWindow::getStockIcon(QString const& name, int fallback, QStringList const& emblemNames)
QIcon MainWindow::addEmblem(QIcon baseIcon, QStringList const& emblemNames)
{
QIcon baseIcon = getStockIcon(name, fallback);
if (baseIcon.isNull())
{
return baseIcon;
@ -168,20 +166,23 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
ui.listView->setAttribute(Qt::WA_MacShowFocusRect, false);
// icons
ui.action_OpenFile->setIcon(getStockIcon(QLatin1String("document-open"), QStyle::SP_DialogOpenButton));
ui.action_AddURL->setIcon(getStockIcon(QLatin1String("document-open"), QStyle::SP_DialogOpenButton,
QIcon const iconPlay = getStockIcon(QLatin1String("media-playback-start"), QStyle::SP_MediaPlay);
QIcon const iconPause = getStockIcon(QLatin1String("media-playback-pause"), QStyle::SP_MediaPause);
QIcon const iconOpen = getStockIcon(QLatin1String("document-open"), QStyle::SP_DialogOpenButton);
ui.action_OpenFile->setIcon(iconOpen);
ui.action_AddURL->setIcon(addEmblem(iconOpen,
QStringList() << QLatin1String("emblem-web") << QLatin1String("applications-internet")));
ui.action_New->setIcon(getStockIcon(QLatin1String("document-new"), QStyle::SP_DesktopIcon));
ui.action_Properties->setIcon(getStockIcon(QLatin1String("document-properties"), QStyle::SP_DesktopIcon));
ui.action_OpenFolder->setIcon(getStockIcon(QLatin1String("folder-open"), QStyle::SP_DirOpenIcon));
ui.action_Start->setIcon(getStockIcon(QLatin1String("media-playback-start"), QStyle::SP_MediaPlay));
ui.action_StartNow->setIcon(getStockIcon(QLatin1String("media-playback-start"), QStyle::SP_MediaPlay));
ui.action_Start->setIcon(iconPlay);
ui.action_StartNow->setIcon(iconPlay);
ui.action_Announce->setIcon(getStockIcon(QLatin1String("network-transmit-receive")));
ui.action_Pause->setIcon(getStockIcon(QLatin1String("media-playback-pause"), QStyle::SP_MediaPause));
ui.action_Pause->setIcon(iconPause);
ui.action_Remove->setIcon(getStockIcon(QLatin1String("list-remove"), QStyle::SP_TrashIcon));
ui.action_Delete->setIcon(getStockIcon(QLatin1String("edit-delete"), QStyle::SP_TrashIcon));
ui.action_StartAll->setIcon(getStockIcon(QLatin1String("media-playback-start"), QStyle::SP_MediaPlay));
ui.action_PauseAll->setIcon(getStockIcon(QLatin1String("media-playback-pause"), QStyle::SP_MediaPause));
ui.action_StartAll->setIcon(iconPlay);
ui.action_PauseAll->setIcon(iconPause);
ui.action_Quit->setIcon(getStockIcon(QLatin1String("application-exit")));
ui.action_SelectAll->setIcon(getStockIcon(QLatin1String("edit-select-all")));
ui.action_ReverseSortOrder->setIcon(getStockIcon(QLatin1String("view-sort-ascending"), QStyle::SP_ArrowDown));
@ -193,6 +194,18 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
ui.action_QueueMoveDown->setIcon(getStockIcon(QLatin1String("go-down"), QStyle::SP_ArrowDown));
ui.action_QueueMoveBottom->setIcon(getStockIcon(QLatin1String("go-bottom")));
auto makeNetworkPixmap = [this](char const* nameIn, QSize size = QSize(16, 16))
{
QString const name = QLatin1String(nameIn);
QIcon const icon = getStockIcon(name, QStyle::SP_DriveNetIcon);
return icon.pixmap(size);
};
myPixmapNetworkError = makeNetworkPixmap("network-error");
myPixmapNetworkIdle = makeNetworkPixmap("network-idle");
myPixmapNetworkReceive = makeNetworkPixmap("network-receive");
myPixmapNetworkTransmit = makeNetworkPixmap("network-transmit");
myPixmapNetworkTransmitReceive = makeNetworkPixmap("network-transmit-receive");
// ui signals
connect(ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
connect(ui.action_Filterbar, SIGNAL(toggled(bool)), this, SLOT(setFilterbarVisible(bool)));
@ -1433,32 +1446,29 @@ void MainWindow::updateNetworkIcon()
time_t const secondsSinceLastRead = now - myLastReadTime;
bool const isSending = secondsSinceLastSend <= period;
bool const isReading = secondsSinceLastRead <= period;
char const* key;
QPixmap pixmap;
if (myNetworkError)
{
key = "network-error";
pixmap = myPixmapNetworkError;
}
else if (isSending && isReading)
{
key = "network-transmit-receive";
pixmap = myPixmapNetworkTransmitReceive;
}
else if (isSending)
{
key = "network-transmit";
pixmap = myPixmapNetworkTransmit;
}
else if (isReading)
{
key = "network-receive";
pixmap = myPixmapNetworkReceive;
}
else
{
key = "network-idle";
pixmap = myPixmapNetworkIdle;
}
QIcon const icon = getStockIcon(QLatin1String(key), QStyle::SP_DriveNetIcon);
QPixmap const pixmap = icon.pixmap(16, 16);
QString tip;
QString const url = mySession.getRemoteUrl().host();

View File

@ -91,7 +91,7 @@ protected:
private:
QIcon getStockIcon(QString const&, int fallback = -1);
QIcon getStockIcon(QString const&, int fallback, QStringList const& emblemNames);
QIcon addEmblem(QIcon icon, QStringList const& emblemNames);
QSet<int> getSelectedTorrents(bool withMetadataOnly = false) const;
void updateNetworkIcon();
@ -149,6 +149,12 @@ private:
Prefs& myPrefs;
TorrentModel& myModel;
QPixmap myPixmapNetworkError;
QPixmap myPixmapNetworkIdle;
QPixmap myPixmapNetworkReceive;
QPixmap myPixmapNetworkTransmit;
QPixmap myPixmapNetworkTransmitReceive;
Ui_MainWindow ui;
time_t myLastFullUpdateTime;

View File

@ -10,11 +10,7 @@
#include <iostream>
#include <QApplication>
#include <QFileIconProvider>
#include <QFileInfo>
#include <QSet>
#include <QString>
#include <QStyle>
#include <QUrl>
#include <QVariant>
@ -41,7 +37,7 @@ Torrent::Torrent(Prefs const& prefs, int id) :
#endif
setInt(ID, id);
setIcon(MIME_ICON, qApp->style()->standardIcon(QStyle::SP_FileIcon));
setIcon(MIME_ICON, Utils::getFileIcon());
}
Torrent::~Torrent()
@ -484,7 +480,7 @@ void Torrent::updateMimeIcon()
if (files.size() > 1)
{
icon = QFileIconProvider().icon(QFileIconProvider::Folder);
icon = Utils::getFolderIcon();
}
else if (files.size() == 1)
{
@ -492,7 +488,7 @@ void Torrent::updateMimeIcon()
}
else
{
icon = QIcon();
icon = Utils::guessMimeIcon(name());
}
setIcon(MIME_ICON, icon);
@ -581,7 +577,13 @@ void Torrent::update(tr_variant* d)
if (tr_variantGetStr(child, &val, nullptr))
{
changed |= setString(property_index, val);
bool const field_changed = setString(property_index, val);
changed |= field_changed;
if (field_changed && key == TR_KEY_name)
{
updateMimeIcon();
}
}
break;

View File

@ -6,6 +6,9 @@
*
*/
#include <map>
#include <set>
#ifdef _WIN32
#include <windows.h>
#include <shellapi.h>
@ -16,7 +19,7 @@
#include <QColor>
#include <QDataStream>
#include <QFile>
#include <QFileDialog>
#include <QFileIconProvider>
#include <QFileInfo>
#include <QHeaderView>
#include <QIcon>
@ -25,7 +28,6 @@
#include <QMimeType>
#include <QObject>
#include <QPixmapCache>
#include <QSet>
#include <QStyle>
#ifdef _WIN32
@ -85,12 +87,87 @@ bool isSlashChar(QChar const& c)
return c == QLatin1Char('/') || c == QLatin1Char('\\');
}
QIcon folderIcon()
{
static QIcon icon;
if (icon.isNull())
{
icon = QFileIconProvider().icon(QFileIconProvider::Folder);
}
return icon;
}
QIcon fileIcon()
{
static QIcon icon;
if (icon.isNull())
{
icon = QFileIconProvider().icon(QFileIconProvider::File);
}
return icon;
}
std::map<QString, QIcon> iconCache;
QIcon const getMimeIcon(QString const& filename)
{
// If the suffix doesn't match a mime type, treat it as a folder.
// This heuristic is fast and yields good results for torrent names.
static std::set<QString> suffixes;
if (suffixes.empty())
{
for (auto const& type : QMimeDatabase().allMimeTypes())
{
auto const tmp = type.suffixes();
suffixes.insert(tmp.begin(), tmp.end());
}
}
QString const ext = QFileInfo(filename).suffix();
if (suffixes.count(ext) == 0)
{
return folderIcon();
}
QIcon& icon = iconCache[ext];
if (icon.isNull()) // cache miss
{
QMimeDatabase mimeDb;
QMimeType type = mimeDb.mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
if (icon.isNull())
{
icon = QIcon::fromTheme(type.iconName());
}
if (icon.isNull())
{
icon = QIcon::fromTheme(type.genericIconName());
}
if (icon.isNull())
{
icon = fileIcon();
}
}
return icon;
}
} // namespace
QIcon Utils::getFolderIcon()
{
return folderIcon();
}
QIcon Utils::getFileIcon()
{
return fileIcon();
}
QIcon Utils::guessMimeIcon(QString const& filename)
{
static QIcon const fallback = qApp->style()->standardIcon(QStyle::SP_FileIcon);
#ifdef _WIN32
QIcon icon;
@ -109,17 +186,11 @@ QIcon Utils::guessMimeIcon(QString const& filename)
return icon;
}
#else
return getMimeIcon(filename);
#endif
QMimeDatabase mimeDb;
QMimeType mimeType = mimeDb.mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
if (mimeType.isValid())
{
return QIcon::fromTheme(mimeType.iconName(), QIcon::fromTheme(mimeType.genericIconName(), fallback));
}
return fallback;
}
QIcon Utils::getIconFromIndex(QModelIndex const& index)

View File

@ -23,6 +23,8 @@ class QModelIndex;
class Utils
{
public:
static QIcon getFileIcon();
static QIcon getFolderIcon();
static QIcon guessMimeIcon(QString const& filename);
static QIcon getIconFromIndex(QModelIndex const& index);