File indexing completed on 2025-02-16 13:11:49
0001 // This file is part of the KDE libraries 0002 // SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de> 0003 // SPDX-License-Identifier: LGPL-2.1-or-later 0004 0005 #include "krecentfilesmenu.h" 0006 0007 #include <QGuiApplication> 0008 #include <QIcon> 0009 #include <QScreen> 0010 #include <QSettings> 0011 #include <QStandardPaths> 0012 0013 class RecentFilesEntry 0014 { 0015 public: 0016 QUrl url; 0017 QString displayName; 0018 QAction *action = nullptr; 0019 0020 QString titleWithSensibleWidth(QWidget *widget) const 0021 { 0022 const QString urlString = url.toDisplayString(QUrl::PreferLocalFile); 0023 // Calculate 3/4 of screen geometry, we do not want 0024 // action titles to be bigger than that 0025 // Since we do not know in which screen we are going to show 0026 // we choose the min of all the screens 0027 int maxWidthForTitles = INT_MAX; 0028 const auto screens = QGuiApplication::screens(); 0029 for (QScreen *screen : screens) { 0030 maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4); 0031 } 0032 0033 const QFontMetrics fontMetrics = widget->fontMetrics(); 0034 0035 QString title = displayName + QLatin1String(" [") + urlString + QLatin1Char(']'); 0036 const int nameWidth = fontMetrics.boundingRect(title).width(); 0037 if (nameWidth > maxWidthForTitles) { 0038 // If it does not fit, try to cut only the whole path, though if the 0039 // name is too long (more than 3/4 of the whole text) we cut it a bit too 0040 const int displayNameMaxWidth = maxWidthForTitles * 3 / 4; 0041 QString cutNameValue; 0042 QString cutValue; 0043 if (nameWidth > displayNameMaxWidth) { 0044 cutNameValue = fontMetrics.elidedText(displayName, Qt::ElideMiddle, displayNameMaxWidth); 0045 cutValue = fontMetrics.elidedText(urlString, Qt::ElideMiddle, maxWidthForTitles - displayNameMaxWidth); 0046 } else { 0047 cutNameValue = displayName; 0048 cutValue = fontMetrics.elidedText(urlString, Qt::ElideMiddle, maxWidthForTitles - nameWidth); 0049 } 0050 title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']'); 0051 } 0052 return title; 0053 } 0054 0055 explicit RecentFilesEntry(const QUrl &_url, const QString &_displayName, KRecentFilesMenu *menu) 0056 : url(_url) 0057 , displayName(_displayName) 0058 { 0059 action = new QAction(titleWithSensibleWidth(menu)); 0060 QObject::connect(action, &QAction::triggered, action, [this, menu]() { 0061 Q_EMIT menu->urlTriggered(url); 0062 }); 0063 } 0064 0065 ~RecentFilesEntry() 0066 { 0067 delete action; 0068 } 0069 }; 0070 0071 class KRecentFilesMenuPrivate 0072 { 0073 public: 0074 explicit KRecentFilesMenuPrivate(KRecentFilesMenu *q_ptr); 0075 0076 std::vector<RecentFilesEntry *>::iterator findEntry(const QUrl &url); 0077 void recentFilesChanged() const; 0078 0079 KRecentFilesMenu *const q; 0080 QString m_group = QStringLiteral("RecentFiles"); 0081 std::vector<RecentFilesEntry *> m_entries; 0082 QSettings *m_settings; 0083 size_t m_maximumItems = 10; 0084 QAction *m_noEntriesAction; 0085 QAction *m_clearAction; 0086 }; 0087 0088 KRecentFilesMenuPrivate::KRecentFilesMenuPrivate(KRecentFilesMenu *q_ptr) 0089 : q(q_ptr) 0090 {} 0091 0092 std::vector<RecentFilesEntry *>::iterator KRecentFilesMenuPrivate::findEntry(const QUrl &url) 0093 { 0094 return std::find_if(m_entries.begin(), m_entries.end(), [url](RecentFilesEntry *entry) { 0095 return entry->url == url; 0096 }); 0097 } 0098 0099 void KRecentFilesMenuPrivate::recentFilesChanged() const 0100 { 0101 q->rebuildMenu(); 0102 Q_EMIT q->recentFilesChanged(); 0103 } 0104 0105 KRecentFilesMenu::KRecentFilesMenu(const QString &title, QWidget *parent) 0106 : QMenu(title, parent) 0107 , d(new KRecentFilesMenuPrivate(this)) 0108 { 0109 setIcon(QIcon::fromTheme(QStringLiteral("document-open-recent"))); 0110 const QString fileName = 0111 QStringLiteral("%1/%2_recentfiles").arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QCoreApplication::applicationName()); 0112 d->m_settings = new QSettings(fileName, QSettings::Format::IniFormat, this); 0113 0114 d->m_noEntriesAction = new QAction(tr("No Entries")); 0115 d->m_noEntriesAction->setDisabled(true); 0116 0117 d->m_clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), tr("Clear List")); 0118 0119 readFromFile(); 0120 } 0121 0122 KRecentFilesMenu::KRecentFilesMenu(QWidget *parent) 0123 : KRecentFilesMenu(tr("Recent Files"), parent) 0124 { 0125 } 0126 0127 KRecentFilesMenu::~KRecentFilesMenu() 0128 { 0129 writeToFile(); 0130 qDeleteAll(d->m_entries); 0131 delete d->m_clearAction; 0132 delete d->m_noEntriesAction; 0133 } 0134 0135 void KRecentFilesMenu::readFromFile() 0136 { 0137 qDeleteAll(d->m_entries); 0138 d->m_entries.clear(); 0139 0140 d->m_settings->beginGroup(d->m_group); 0141 const int size = d->m_settings->beginReadArray(QStringLiteral("files")); 0142 0143 d->m_entries.reserve(size); 0144 0145 for (int i = 0; i < size; ++i) { 0146 d->m_settings->setArrayIndex(i); 0147 0148 const QUrl url = d->m_settings->value(QStringLiteral("url")).toUrl(); 0149 RecentFilesEntry *entry = new RecentFilesEntry(url, d->m_settings->value(QStringLiteral("displayName")).toString(), this); 0150 d->m_entries.push_back(entry); 0151 } 0152 0153 d->m_settings->endArray(); 0154 d->m_settings->endGroup(); 0155 0156 d->recentFilesChanged(); 0157 } 0158 0159 void KRecentFilesMenu::addUrl(const QUrl &url, const QString &name) 0160 { 0161 if (d->m_entries.size() == d->m_maximumItems) { 0162 delete d->m_entries.back(); 0163 d->m_entries.pop_back(); 0164 } 0165 0166 // If it's already there remove the old one and reinsert so it appears as new 0167 auto it = d->findEntry(url); 0168 if (it != d->m_entries.cend()) { 0169 delete *it; 0170 d->m_entries.erase(it); 0171 } 0172 0173 QString displayName = name; 0174 0175 if (displayName.isEmpty()) { 0176 displayName = url.fileName(); 0177 } 0178 0179 RecentFilesEntry *entry = new RecentFilesEntry(url, displayName, this); 0180 d->m_entries.insert(d->m_entries.begin(), entry); 0181 0182 d->recentFilesChanged(); 0183 } 0184 0185 void KRecentFilesMenu::removeUrl(const QUrl &url) 0186 { 0187 auto it = d->findEntry(url); 0188 0189 if (it == d->m_entries.end()) { 0190 return; 0191 } 0192 0193 delete *it; 0194 d->m_entries.erase(it); 0195 0196 d->recentFilesChanged(); 0197 } 0198 0199 void KRecentFilesMenu::rebuildMenu() 0200 { 0201 clear(); 0202 0203 if (d->m_entries.empty()) { 0204 addAction(d->m_noEntriesAction); 0205 return; 0206 } 0207 0208 for (const RecentFilesEntry *entry : d->m_entries) { 0209 addAction(entry->action); 0210 } 0211 0212 addSeparator(); 0213 addAction(d->m_clearAction); 0214 0215 // reconnect d->m_clearAction, since it was disconnected in clear() 0216 connect(d->m_clearAction, &QAction::triggered, this, &KRecentFilesMenu::clearRecentFiles); 0217 } 0218 0219 void KRecentFilesMenu::writeToFile() 0220 { 0221 d->m_settings->remove(QString()); 0222 d->m_settings->beginGroup(d->m_group); 0223 d->m_settings->beginWriteArray(QStringLiteral("files")); 0224 0225 int index = 0; 0226 for (const RecentFilesEntry *entry : d->m_entries) { 0227 d->m_settings->setArrayIndex(index); 0228 d->m_settings->setValue(QStringLiteral("url"), entry->url); 0229 d->m_settings->setValue(QStringLiteral("displayName"), entry->displayName); 0230 ++index; 0231 } 0232 0233 d->m_settings->endArray(); 0234 d->m_settings->endGroup(); 0235 d->m_settings->sync(); 0236 } 0237 0238 QString KRecentFilesMenu::group() const 0239 { 0240 return d->m_group; 0241 } 0242 0243 void KRecentFilesMenu::setGroup(const QString &group) 0244 { 0245 d->m_group = group; 0246 readFromFile(); 0247 } 0248 0249 int KRecentFilesMenu::maximumItems() const 0250 { 0251 return d->m_maximumItems; 0252 } 0253 0254 void KRecentFilesMenu::setMaximumItems(size_t maximumItems) 0255 { 0256 d->m_maximumItems = maximumItems; 0257 0258 // Truncate if there are more entries than the new maximum 0259 if (d->m_entries.size() > maximumItems) { 0260 qDeleteAll(d->m_entries.begin() + maximumItems, d->m_entries.end()); 0261 d->m_entries.erase(d->m_entries.begin() + maximumItems, d->m_entries.end()); 0262 0263 d->recentFilesChanged(); 0264 } 0265 } 0266 0267 QList<QUrl> KRecentFilesMenu::recentFiles() const 0268 { 0269 QList<QUrl> urls; 0270 urls.reserve(d->m_entries.size()); 0271 std::transform(d->m_entries.cbegin(), d->m_entries.cend(), std::back_inserter(urls), [](const RecentFilesEntry *entry) { 0272 return entry->url; 0273 }); 0274 0275 return urls; 0276 } 0277 0278 void KRecentFilesMenu::clearRecentFiles() 0279 { 0280 qDeleteAll(d->m_entries); 0281 d->m_entries.clear(); 0282 0283 d->recentFilesChanged(); 0284 } 0285 0286 #include "moc_krecentfilesmenu.cpp"