Warning, file /frameworks/kconfigwidgets/src/krecentfilesaction.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org> 0004 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org> 0005 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org> 0006 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org> 0007 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org> 0008 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org> 0009 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org> 0010 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org> 0011 SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org> 0012 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org> 0013 0014 SPDX-License-Identifier: LGPL-2.0-only 0015 */ 0016 0017 #include "krecentfilesaction.h" 0018 #include "krecentfilesaction_p.h" 0019 0020 #include <QActionGroup> 0021 #include <QDir> 0022 #include <QGuiApplication> 0023 #include <QMenu> 0024 #include <QMimeDatabase> 0025 #include <QMimeType> 0026 #include <QScreen> 0027 0028 #include <KConfig> 0029 #include <KConfigGroup> 0030 #include <KLocalizedString> 0031 0032 #include <set> 0033 0034 KRecentFilesAction::KRecentFilesAction(QObject *parent) 0035 : KSelectAction(parent) 0036 , d_ptr(new KRecentFilesActionPrivate(this)) 0037 { 0038 Q_D(KRecentFilesAction); 0039 d->init(); 0040 } 0041 0042 KRecentFilesAction::KRecentFilesAction(const QString &text, QObject *parent) 0043 : KSelectAction(parent) 0044 , d_ptr(new KRecentFilesActionPrivate(this)) 0045 { 0046 Q_D(KRecentFilesAction); 0047 d->init(); 0048 0049 // Want to keep the ampersands 0050 setText(text); 0051 } 0052 0053 KRecentFilesAction::KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent) 0054 : KSelectAction(parent) 0055 , d_ptr(new KRecentFilesActionPrivate(this)) 0056 { 0057 Q_D(KRecentFilesAction); 0058 d->init(); 0059 0060 setIcon(icon); 0061 // Want to keep the ampersands 0062 setText(text); 0063 } 0064 0065 void KRecentFilesActionPrivate::init() 0066 { 0067 Q_Q(KRecentFilesAction); 0068 delete q->menu(); 0069 q->setMenu(new QMenu()); 0070 q->setToolBarMode(KSelectAction::MenuMode); 0071 m_noEntriesAction = q->menu()->addAction(i18n("No Entries")); 0072 m_noEntriesAction->setObjectName(QStringLiteral("no_entries")); 0073 m_noEntriesAction->setEnabled(false); 0074 clearSeparator = q->menu()->addSeparator(); 0075 clearSeparator->setVisible(false); 0076 clearSeparator->setObjectName(QStringLiteral("separator")); 0077 clearAction = q->menu()->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Clear List"), q, &KRecentFilesAction::clear); 0078 clearAction->setObjectName(QStringLiteral("clear_action")); 0079 clearAction->setVisible(false); 0080 q->setEnabled(false); 0081 q->connect(q, qOverload<QAction *>(&KSelectAction::triggered), q, [this](QAction *action) { 0082 urlSelected(action); 0083 }); 0084 } 0085 0086 KRecentFilesAction::~KRecentFilesAction() = default; 0087 0088 void KRecentFilesActionPrivate::urlSelected(QAction *action) 0089 { 0090 Q_Q(KRecentFilesAction); 0091 0092 auto it = findByAction(action); 0093 0094 Q_ASSERT(it != m_recentActions.cend()); // Should never happen 0095 0096 const QUrl url = it->url; // BUG: 461448; see iterator invalidation rules 0097 Q_EMIT q->urlSelected(url); 0098 } 0099 0100 void KRecentFilesActionPrivate::removeAction(std::vector<RecentActionInfo>::iterator it) 0101 { 0102 Q_Q(KRecentFilesAction); 0103 delete q->KSelectAction::removeAction(it->action); 0104 m_recentActions.erase(it); 0105 } 0106 0107 int KRecentFilesAction::maxItems() const 0108 { 0109 Q_D(const KRecentFilesAction); 0110 return d->m_maxItems; 0111 } 0112 0113 void KRecentFilesAction::setMaxItems(int maxItems) 0114 { 0115 Q_D(KRecentFilesAction); 0116 // set new maxItems 0117 d->m_maxItems = std::max(maxItems, 0); 0118 0119 // Remove all excess items, oldest (i.e. first added) first 0120 const int difference = static_cast<int>(d->m_recentActions.size()) - d->m_maxItems; 0121 if (difference > 0) { 0122 auto beginIt = d->m_recentActions.begin(); 0123 auto endIt = d->m_recentActions.begin() + difference; 0124 for (auto it = beginIt; it < endIt; ++it) { 0125 // Remove the action from the menus, action groups ...etc 0126 d->removeAction(it); 0127 } 0128 } 0129 } 0130 0131 static QString titleWithSensibleWidth(const QString &nameValue, const QString &value) 0132 { 0133 // Calculate 3/4 of screen geometry, we do not want 0134 // action titles to be bigger than that 0135 // Since we do not know in which screen we are going to show 0136 // we choose the min of all the screens 0137 int maxWidthForTitles = INT_MAX; 0138 const auto screens = QGuiApplication::screens(); 0139 for (QScreen *screen : screens) { 0140 maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4); 0141 } 0142 const QFontMetrics fontMetrics = QFontMetrics(QFont()); 0143 0144 QString title = nameValue + QLatin1String(" [") + value + QLatin1Char(']'); 0145 const int nameWidth = fontMetrics.boundingRect(title).width(); 0146 if (nameWidth > maxWidthForTitles) { 0147 // If it does not fit, try to cut only the whole path, though if the 0148 // name is too long (more than 3/4 of the whole text) we cut it a bit too 0149 const int nameValueMaxWidth = maxWidthForTitles * 3 / 4; 0150 QString cutNameValue; 0151 QString cutValue; 0152 if (nameWidth > nameValueMaxWidth) { 0153 cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth); 0154 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth); 0155 } else { 0156 cutNameValue = nameValue; 0157 cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth); 0158 } 0159 title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']'); 0160 } 0161 return title; 0162 } 0163 0164 void KRecentFilesAction::addUrl(const QUrl &url, const QString &name) 0165 { 0166 Q_D(KRecentFilesAction); 0167 0168 // ensure we never add items if we want none 0169 if (d->m_maxItems == 0) { 0170 return; 0171 } 0172 0173 if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) { 0174 return; 0175 } 0176 0177 // Remove url if it already exists in the list 0178 removeUrl(url); 0179 0180 // Remove oldest item if already maxItems in list 0181 Q_ASSERT(d->m_maxItems > 0); 0182 if (static_cast<int>(d->m_recentActions.size()) == d->m_maxItems) { 0183 d->removeAction(d->m_recentActions.begin()); 0184 } 0185 0186 const QString pathOrUrl(url.toDisplayString(QUrl::PreferLocalFile)); 0187 const QString tmpName = !name.isEmpty() ? name : url.fileName(); 0188 #ifdef Q_OS_WIN 0189 const QString file = url.isLocalFile() ? QDir::toNativeSeparators(pathOrUrl) : pathOrUrl; 0190 #else 0191 const QString file = pathOrUrl; 0192 #endif 0193 0194 d->m_noEntriesAction->setVisible(false); 0195 d->clearSeparator->setVisible(true); 0196 d->clearAction->setVisible(true); 0197 setEnabled(true); 0198 // add file to list 0199 const QString title = titleWithSensibleWidth(tmpName, file); 0200 0201 QAction *action = new QAction(title, selectableActionGroup()); 0202 addAction(action, url, tmpName); 0203 } 0204 0205 void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &name) 0206 { 0207 Q_D(KRecentFilesAction); 0208 0209 const QMimeType mimeType = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension); 0210 if (!mimeType.isDefault()) { 0211 action->setIcon(QIcon::fromTheme(mimeType.iconName())); 0212 } 0213 menu()->insertAction(menu()->actions().value(0), action); 0214 d->m_recentActions.push_back({action, url, name}); 0215 } 0216 0217 QAction *KRecentFilesAction::removeAction(QAction *action) 0218 { 0219 Q_D(KRecentFilesAction); 0220 auto it = d->findByAction(action); 0221 Q_ASSERT(it != d->m_recentActions.cend()); 0222 d->m_recentActions.erase(it); 0223 return KSelectAction::removeAction(action); 0224 } 0225 0226 void KRecentFilesAction::removeUrl(const QUrl &url) 0227 { 0228 Q_D(KRecentFilesAction); 0229 0230 auto it = d->findByUrl(url); 0231 0232 if (it != d->m_recentActions.cend()) { 0233 d->removeAction(it); 0234 }; 0235 } 0236 0237 QList<QUrl> KRecentFilesAction::urls() const 0238 { 0239 Q_D(const KRecentFilesAction); 0240 0241 QList<QUrl> list; 0242 list.reserve(d->m_recentActions.size()); 0243 0244 using Info = KRecentFilesActionPrivate::RecentActionInfo; 0245 // Reverse order to match how the actions appear in the menu 0246 std::transform(d->m_recentActions.crbegin(), d->m_recentActions.crend(), std::back_inserter(list), [](const Info &info) { 0247 return info.url; 0248 }); 0249 0250 return list; 0251 } 0252 0253 void KRecentFilesAction::clear() 0254 { 0255 clearEntries(); 0256 Q_EMIT recentListCleared(); 0257 } 0258 0259 void KRecentFilesAction::clearEntries() 0260 { 0261 Q_D(KRecentFilesAction); 0262 KSelectAction::clear(); 0263 d->m_recentActions.clear(); 0264 d->m_noEntriesAction->setVisible(true); 0265 d->clearSeparator->setVisible(false); 0266 d->clearAction->setVisible(false); 0267 setEnabled(false); 0268 } 0269 0270 void KRecentFilesAction::loadEntries(const KConfigGroup &_config) 0271 { 0272 Q_D(KRecentFilesAction); 0273 clearEntries(); 0274 0275 QString key; 0276 QString value; 0277 QString nameKey; 0278 QString nameValue; 0279 QString title; 0280 QUrl url; 0281 0282 KConfigGroup cg = _config; 0283 // "<default>" means the group was constructed with an empty name 0284 if (cg.name() == QLatin1String("<default>")) { 0285 cg = KConfigGroup(cg.config(), "RecentFiles"); 0286 } 0287 0288 std::set<QUrl> seenUrls; 0289 0290 bool thereAreEntries = false; 0291 // read file list 0292 for (int i = 1; i <= d->m_maxItems; i++) { 0293 key = QStringLiteral("File%1").arg(i); 0294 value = cg.readPathEntry(key, QString()); 0295 if (value.isEmpty()) { 0296 continue; 0297 } 0298 url = QUrl::fromUserInput(value); 0299 0300 auto [it, isNewUrl] = seenUrls.insert(url); 0301 // Don't restore if this url has already been restored (e.g. broken config) 0302 if (!isNewUrl) { 0303 continue; 0304 } 0305 0306 #ifdef Q_OS_WIN 0307 // convert to backslashes 0308 if (url.isLocalFile()) { 0309 value = QDir::toNativeSeparators(value); 0310 } 0311 #endif 0312 0313 nameKey = QStringLiteral("Name%1").arg(i); 0314 nameValue = cg.readPathEntry(nameKey, url.fileName()); 0315 title = titleWithSensibleWidth(nameValue, value); 0316 if (!value.isNull()) { 0317 thereAreEntries = true; 0318 addAction(new QAction(title, selectableActionGroup()), url, nameValue); 0319 } 0320 } 0321 if (thereAreEntries) { 0322 d->m_noEntriesAction->setVisible(false); 0323 d->clearSeparator->setVisible(true); 0324 d->clearAction->setVisible(true); 0325 setEnabled(true); 0326 } 0327 } 0328 0329 void KRecentFilesAction::saveEntries(const KConfigGroup &_cg) 0330 { 0331 Q_D(KRecentFilesAction); 0332 0333 KConfigGroup cg = _cg; 0334 // "<default>" means the group was constructed with an empty name 0335 if (cg.name() == QLatin1String("<default>")) { 0336 cg = KConfigGroup(cg.config(), "RecentFiles"); 0337 } 0338 0339 cg.deleteGroup(); 0340 0341 // write file list 0342 int i = 1; 0343 for (const auto &[action, url, shortName] : d->m_recentActions) { 0344 cg.writePathEntry(QStringLiteral("File%1").arg(i), url.toDisplayString(QUrl::PreferLocalFile)); 0345 cg.writePathEntry(QStringLiteral("Name%1").arg(i), shortName); 0346 0347 ++i; 0348 } 0349 } 0350 0351 #include "moc_krecentfilesaction.cpp"