Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
appfontdialog.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
6
7#include <iconloader_p.h>
8
9#include <QtDesigner/abstractsettings.h>
10
11#include <QtGui/qfontdatabase.h>
12#include <QtGui/qstandarditemmodel.h>
13
14#include <QtWidgets/qboxlayout.h>
15#include <QtWidgets/qdialogbuttonbox.h>
16#include <QtWidgets/qfiledialog.h>
17#include <QtWidgets/qlayoutitem.h>
18#include <QtWidgets/qmessagebox.h>
19#include <QtWidgets/qtoolbutton.h>
20#include <QtWidgets/qtreeview.h>
21
22#include <QtCore/qalgorithms.h>
23#include <QtCore/qcoreapplication.h>
24#include <QtCore/qdebug.h>
25#include <QtCore/qfileinfo.h>
26#include <QtCore/qlist.h>
27#include <QtCore/qsettings.h>
28#include <QtCore/qstringlist.h>
29
30#include <algorithm>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36enum {FileNameRole = Qt::UserRole + 1, IdRole = Qt::UserRole + 2 };
37enum { debugAppFontWidget = 0 };
38
39static constexpr auto fontFileKeyC = "fontFiles"_L1;
40
41// AppFontManager: Singleton that maintains the mapping of loaded application font
42// ids to the file names (which are not stored in QFontDatabase)
43// and provides API for loading/unloading fonts as well for saving/restoring settings.
44
46{
49public:
51
52 void save(QDesignerSettingsInterface *s, const QString &prefix) const;
53 void restore(const QDesignerSettingsInterface *s, const QString &prefix);
54
55 // Return id or -1
56 int add(const QString &fontFile, QString *errorMessage);
57
58 bool remove(int id, QString *errorMessage);
59 bool remove(const QString &fontFile, QString *errorMessage);
60 bool removeAt(int index, QString *errorMessage);
61
62 // Store loaded fonts as pair of file name and Id
63 using FileNameFontIdPair = std::pair<QString, int>;
65 const FileNameFontIdPairs &fonts() const;
66
67private:
68 FileNameFontIdPairs m_fonts;
69};
70
71AppFontManager::AppFontManager() = default;
72
74{
75 static AppFontManager rc;
76 return rc;
77}
78
79void AppFontManager::save(QDesignerSettingsInterface *s, const QString &prefix) const
80{
81 // Store as list of file names
82 QStringList fontFiles;
83 for (const auto &fnp : m_fonts)
84 fontFiles.push_back(fnp.first);
85
86 s->beginGroup(prefix);
87 s->setValue(fontFileKeyC, fontFiles);
88 s->endGroup();
89
91 qDebug() << "AppFontManager::saved" << fontFiles.size() << "fonts under " << prefix;
92}
93
94void AppFontManager::restore(const QDesignerSettingsInterface *s, const QString &prefix)
95{
96 const QString key = prefix + u'/' + fontFileKeyC;
97 const QStringList fontFiles = s->value(key, QStringList()).toStringList();
98
100 qDebug() << "AppFontManager::restoring" << fontFiles.size() << "fonts from " << prefix;
101 if (!fontFiles.isEmpty()) {
102 QString errorMessage;
103 for (const auto &ff : fontFiles) {
104 if (add(ff, &errorMessage) == -1)
105 qWarning("%s", qPrintable(errorMessage));
106 }
107 }
108}
109
110int AppFontManager::add(const QString &fontFile, QString *errorMessage)
111{
112 const QFileInfo inf(fontFile);
113 if (!inf.isFile()) {
114 *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a file.").arg(fontFile);
115 return -1;
116 }
117 if (!inf.isReadable()) {
118 *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' does not have read permissions.").arg(fontFile);
119 return -1;
120 }
121 const QString fullPath = inf.absoluteFilePath();
122 // Check if already loaded
123 for (const auto &fnp : std::as_const(m_fonts)) {
124 if (fnp.first == fullPath) {
125 *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' is already loaded.").arg(fontFile);
126 return -1;
127 }
128 }
129
130 const int id = QFontDatabase::addApplicationFont(fullPath);
131 if (id == -1) {
132 *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' could not be loaded.").arg(fontFile);
133 return -1;
134 }
135
137 qDebug() << "AppFontManager::add" << fontFile << id;
138 m_fonts.push_back(FileNameFontIdPair(fullPath, id));
139 return id;
140}
141
142bool AppFontManager::remove(int id, QString *errorMessage)
143{
144 for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i)
145 if (m_fonts.at(i).second == id)
146 return removeAt(i, errorMessage);
147
148 *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a valid font id.").arg(id);
149 return false;
150}
151
152bool AppFontManager::remove(const QString &fontFile, QString *errorMessage)
153{
154 for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i)
155 if (m_fonts.at(i).first == fontFile)
156 return removeAt(i, errorMessage);
157
158 *errorMessage = QCoreApplication::translate("AppFontManager", "There is no loaded font matching the id '%1'.").arg(fontFile);
159 return false;
160}
161
162bool AppFontManager::removeAt(int index, QString *errorMessage)
163{
164 Q_ASSERT(index >= 0 && index < m_fonts.size());
165
166 const QString fontFile = m_fonts[index].first;
167 const int id = m_fonts[index].second;
168
170 qDebug() << "AppFontManager::removeAt" << index << '(' << fontFile << id << ')';
171
172 if (!QFontDatabase::removeApplicationFont(id)) {
173 *errorMessage = QCoreApplication::translate("AppFontManager", "The font '%1' (%2) could not be unloaded.").arg(fontFile).arg(id);
174 return false;
175 }
176 m_fonts.removeAt(index);
177 return true;
178}
179
181{
182 return m_fonts;
183}
184
185// ------------- AppFontModel
188public:
190
191 void init(const AppFontManager &mgr);
192 void add(const QString &fontFile, int id);
193 int idAt(const QModelIndex &idx) const;
194};
195
196AppFontModel::AppFontModel(QObject * parent) :
197 QStandardItemModel(parent)
198{
199 setHorizontalHeaderLabels(QStringList(AppFontWidget::tr("Fonts")));
200}
201
203{
204 using FileNameFontIdPairs = AppFontManager::FileNameFontIdPairs;
205
206 const FileNameFontIdPairs &fonts = mgr.fonts();
207 for (const auto &fnp : fonts)
208 add(fnp.first, fnp.second);
209}
210
211void AppFontModel::add(const QString &fontFile, int id)
212{
213 const QFileInfo inf(fontFile);
214 // Root item with base name
215 auto *fileItem = new QStandardItem(inf.completeBaseName());
216 const QString fullPath = inf.absoluteFilePath();
217 fileItem->setData(fullPath, FileNameRole);
218 fileItem->setToolTip(fullPath);
219 fileItem->setData(id, IdRole);
220 fileItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
221
222 appendRow(fileItem);
223 const QStringList families = QFontDatabase::applicationFontFamilies(id);
224 for (const auto &fam : families) {
225 auto *familyItem = new QStandardItem(fam);
226 familyItem->setToolTip(fullPath);
227 familyItem->setFont(QFont(fam));
228 familyItem->setFlags(Qt::ItemIsEnabled);
229 fileItem->appendRow(familyItem);
230 }
231}
232
233int AppFontModel::idAt(const QModelIndex &idx) const
234{
235 if (const QStandardItem *item = itemFromIndex(idx))
236 return item->data(IdRole).toInt();
237 return -1;
238}
239
240// ------------- AppFontWidget
241AppFontWidget::AppFontWidget(QWidget *parent) :
242 QGroupBox(parent),
243 m_view(new QTreeView),
244 m_addButton(new QToolButton),
245 m_removeButton(new QToolButton),
246 m_removeAllButton(new QToolButton),
247 m_model(new AppFontModel(this))
248{
250 m_view->setModel(m_model);
251 m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
252 m_view->expandAll();
253 connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppFontWidget::selectionChanged);
254
255 m_addButton->setToolTip(tr("Add font files"));
256 m_addButton->setIcon(qdesigner_internal::createIconSet("plus.png"_L1));
257 connect(m_addButton, &QAbstractButton::clicked, this, &AppFontWidget::addFiles);
258
259 m_removeButton->setEnabled(false);
260 m_removeButton->setToolTip(tr("Remove current font file"));
261 m_removeButton->setIcon(qdesigner_internal::createIconSet("minus.png"_L1));
262 connect(m_removeButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveFiles);
263
264 m_removeAllButton->setToolTip(tr("Remove all font files"));
265 m_removeAllButton->setIcon(qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditDelete,
266 "editdelete.png"_L1));
267 connect(m_removeAllButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveAll);
268
269 auto *hLayout = new QHBoxLayout;
270 hLayout->addWidget(m_addButton);
271 hLayout->addWidget(m_removeButton);
272 hLayout->addWidget(m_removeAllButton);
273 hLayout->addItem(new QSpacerItem(0, 0,QSizePolicy::MinimumExpanding));
274
275 auto *vLayout = new QVBoxLayout;
276 vLayout->addWidget(m_view);
277 vLayout->addLayout(hLayout);
278 setLayout(vLayout);
279}
280
281void AppFontWidget::addFiles()
282{
283 const QStringList files =
284 QFileDialog::getOpenFileNames(this, tr("Add Font Files"), QString(),
285 tr("Font files (*.ttf)"));
286 if (files.isEmpty())
287 return;
288
289 QString errorMessage;
290
292 for (const auto &f : files) {
293 const int id = fmgr.add(f, &errorMessage);
294 if (id != -1) {
295 m_model->add(f, id);
296 } else {
297 QMessageBox::critical(this, tr("Error Adding Fonts"), errorMessage);
298 }
299 }
300 m_view->expandAll();
301}
302
303static void removeFonts(const QModelIndexList &selectedIndexes, AppFontModel *model, QWidget *dialogParent)
304{
305 if (selectedIndexes.isEmpty())
306 return;
307
308 // Reverse sort top level rows and remove
310 QList<int> rows;
311 rows.reserve(selectedIndexes.size());
312
313 QString errorMessage;
314 for (const auto &mi : selectedIndexes) {
315 const int id = model->idAt(mi);
316 if (id != -1) {
317 if (fmgr.remove(id, &errorMessage)) {
318 rows.append(mi.row());
319 } else {
320 QMessageBox::critical(dialogParent, AppFontWidget::tr("Error Removing Fonts"), errorMessage);
321 }
322 }
323 }
324
325 std::stable_sort(rows.begin(), rows.end());
326 for (qsizetype i = rows.size() - 1; i >= 0; --i)
327 model->removeRow(rows.at(i));
328}
329
330void AppFontWidget::slotRemoveFiles()
331{
332 removeFonts(m_view->selectionModel()->selectedIndexes(), m_model, this);
333}
334
335void AppFontWidget::slotRemoveAll()
336{
337 const int count = m_model->rowCount();
338 if (!count)
339 return;
340
341 const QMessageBox::StandardButton answer =
342 QMessageBox::question(this, tr("Remove Fonts"), tr("Would you like to remove all fonts?"),
343 QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
344 if (answer == QMessageBox::No)
345 return;
346
347 QModelIndexList topLevels;
348 for (int i = 0; i < count; i++)
349 topLevels.push_back(m_model->index(i, 0));
350 removeFonts(topLevels, m_model, this);
351}
352
353void AppFontWidget::selectionChanged(const QItemSelection &selected, const QItemSelection & /*deselected*/)
354{
355 m_removeButton->setEnabled(!selected.indexes().isEmpty());
356}
357
358void AppFontWidget::save(QDesignerSettingsInterface *s, const QString &prefix)
359{
361}
362
363void AppFontWidget::restore(const QDesignerSettingsInterface *s, const QString &prefix)
364{
366}
367
368// ------------ AppFontDialog
369AppFontDialog::AppFontDialog(QWidget *parent) :
370 QDialog(parent),
371 m_appFontWidget(new AppFontWidget)
372{
373 setAttribute(Qt::WA_DeleteOnClose, true);
374 setWindowTitle(tr("Additional Fonts"));
375 setModal(false);
376 auto *vl = new QVBoxLayout(this);
377 vl->addWidget(m_appFontWidget);
378
379 auto *bb = new QDialogButtonBox(QDialogButtonBox::Close);
380 QDialog::connect(bb, &QDialogButtonBox::rejected, this, &AppFontDialog::reject);
381 vl->addWidget(bb);
382}
383
384QT_END_NAMESPACE
static void removeFonts(const QModelIndexList &selectedIndexes, AppFontModel *model, QWidget *dialogParent)
@ debugAppFontWidget
static constexpr auto fontFileKeyC
@ IdRole
@ FileNameRole
std::pair< QString, int > FileNameFontIdPair
bool removeAt(int index, QString *errorMessage)
void save(QDesignerSettingsInterface *s, const QString &prefix) const
int add(const QString &fontFile, QString *errorMessage)
static AppFontManager & instance()
void restore(const QDesignerSettingsInterface *s, const QString &prefix)
bool remove(int id, QString *errorMessage)
const FileNameFontIdPairs & fonts() const
bool remove(const QString &fontFile, QString *errorMessage)
void init(const AppFontManager &mgr)
int idAt(const QModelIndex &idx) const
void add(const QString &fontFile, int id)