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