// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-FileCopyrightText: 2025 OpenCloud FontIcon Collector Tool #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fonticon.h" using namespace Qt::Literals::StringLiterals; using namespace OCC::Resources; struct FontIconUsage { QString filePath; int lineNumber; QString sourceContext; QChar glyph; FontIcon::FontFamily family; FontIcon::Size size; QString fullConstructorCall; }; class FontIconCollectorWidget : public QWidget { Q_OBJECT public: explicit FontIconCollectorWidget(QWidget *parent = nullptr) : QWidget(parent) { setupUI(); scanForFontIconUsages(); } private Q_SLOTS: void onUsageSelected(int row) { if (row < 0 || row >= m_usages.size()) { return; } const auto &usage = m_usages[row]; // Update the glyph display FontIcon icon(usage.family, usage.glyph, usage.size); QPixmap pixmap = icon.pixmap(QSize(64, 64)); m_glyphLabel->setPixmap(pixmap); // Update the details m_detailsText->clear(); m_detailsText->append(u"File: %1"_s.arg(usage.filePath)); m_detailsText->append(u"Line: %1"_s.arg(usage.lineNumber)); m_detailsText->append(u"Glyph: %1 (0x%2)"_s.arg(usage.glyph).arg(static_cast(usage.glyph.unicode()), 4, 16, QChar('0'_L1))); m_detailsText->append(u"Family: %1"_s.arg(usage.family == FontIcon::FontFamily::FontAwesome ? u"FontAwesome"_s : u"RemixIcon"_s)); m_detailsText->append(u"Size: %1"_s.arg(usage.size == FontIcon::Size::Normal ? u"Normal"_s : u"Half"_s)); m_detailsText->append(u"\n"_s); m_detailsText->append(u"Context:"_s); m_detailsText->append(usage.sourceContext); } private: void setupUI() { setWindowTitle(u"FontIcon Usage Collector"_s); resize(1200, 800); auto *layout = new QHBoxLayout(this); auto *splitter = new QSplitter(Qt::Horizontal, this); // Left side: List of usages auto *leftWidget = new QWidget; auto *leftLayout = new QVBoxLayout(leftWidget); leftLayout->addWidget(new QLabel(u"FontIcon Usages Found:"_s)); m_usagesList = new QListWidget; leftLayout->addWidget(m_usagesList); connect(m_usagesList, &QListWidget::currentRowChanged, this, &FontIconCollectorWidget::onUsageSelected); // Right side: Details and glyph display auto *rightWidget = new QWidget; auto *rightLayout = new QVBoxLayout(rightWidget); // Glyph display auto *glyphGroup = new QGroupBox(u"Glyph Preview"_s); auto *glyphLayout = new QVBoxLayout(glyphGroup); m_glyphLabel = new QLabel; m_glyphLabel->setAlignment(Qt::AlignCenter); m_glyphLabel->setStyleSheet(u"border: 1px solid gray; padding: 10px;"_s); m_glyphLabel->setMinimumSize(100, 100); glyphLayout->addWidget(m_glyphLabel); // Details text auto *detailsGroup = new QGroupBox(u"Details"_s); auto *detailsLayout = new QVBoxLayout(detailsGroup); m_detailsText = new QTextEdit; m_detailsText->setReadOnly(true); m_detailsText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); detailsLayout->addWidget(m_detailsText); rightLayout->addWidget(glyphGroup); rightLayout->addWidget(detailsGroup); splitter->addWidget(leftWidget); splitter->addWidget(rightWidget); splitter->setSizes({400, 800}); layout->addWidget(splitter); } void scanForFontIconUsages() { QDir srcDir = QDir(QStringLiteral(SRC_DIR)); if (!srcDir.exists()) { qWarning() << "Could not find src directory"; return; } qDebug() << "Scanning directory:" << srcDir.absolutePath(); // Recursively scan for C++ files QDirIterator it(srcDir.absolutePath(), QStringList() << u"*.cpp"_s << u"*.h"_s, QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { QString filePath = it.next(); scanFile(filePath); } qDebug() << "Found" << m_usages.size() << "FontIcon usages"; // Populate the list widget for (const auto &usage : m_usages) { QString displayText = u"%1:%2 - %3"_s.arg(QFileInfo(usage.filePath).fileName(), QString::number(usage.lineNumber), usage.glyph); m_usagesList->addItem(displayText); } if (!m_usages.isEmpty()) { m_usagesList->setCurrentRow(0); } } void scanFile(const QString &filePath) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } QTextStream in(&file); QString content = in.readAll(); QStringList lines = content.split('\n'_L1); // Regular expressions to match FontIcon constructor calls QRegularExpression fontIconRegex(uR"(FontIcon\s*\(\s*([^)]+)\s*\))"_s); QRegularExpression glyphRegex(uR"(u'(.)')"_s); QRegularExpression familyRegex(uR"(FontIcon::FontFamily::(\w+))"_s); QRegularExpression sizeRegex(uR"(FontIcon::Size::(\w+))"_s); for (int lineNum = 0; lineNum < lines.size(); ++lineNum) { const QString &line = lines[lineNum]; auto fontIconMatch = fontIconRegex.match(line); if (fontIconMatch.hasMatch()) { FontIconUsage usage; usage.filePath = filePath; usage.lineNumber = lineNum + 1; usage.fullConstructorCall = fontIconMatch.captured(0); // Extract glyph auto glyphMatch = glyphRegex.match(line); if (glyphMatch.hasMatch()) { QString glyphStr = glyphMatch.captured(1); if (!glyphStr.isEmpty()) { usage.glyph = glyphStr[0]; } } else { // Look for hex unicode like u'\uf015' QRegularExpression hexGlyphRegex(uR"(u'\\u([0-9a-fA-F]{4})')"_s); auto hexMatch = hexGlyphRegex.match(line); if (hexMatch.hasMatch()) { bool ok; uint unicode = hexMatch.captured(1).toUInt(&ok, 16); if (ok) { usage.glyph = QChar(unicode); } } else { // Try to find the glyph directly in the line QRegularExpression directGlyphRegex(uR"(u'(.)')"_s); auto directMatch = directGlyphRegex.match(line); if (directMatch.hasMatch()) { usage.glyph = directMatch.captured(1)[0]; } else { usage.glyph = QChar('?'_L1); // Unknown glyph } } } // Extract family (default to FontAwesome) auto familyMatch = familyRegex.match(line); if (familyMatch.hasMatch()) { QString familyStr = familyMatch.captured(1); usage.family = (familyStr == u"RemixIcon"_s) ? FontIcon::FontFamily::RemixIcon : FontIcon::FontFamily::FontAwesome; } else { usage.family = FontIcon::FontFamily::FontAwesome; } // Extract size (default to Normal) auto sizeMatch = sizeRegex.match(line); if (sizeMatch.hasMatch()) { QString sizeStr = sizeMatch.captured(1); usage.size = (sizeStr == u"Half"_s) ? FontIcon::Size::Half : FontIcon::Size::Normal; } else { usage.size = FontIcon::Size::Normal; } // Get context (current line and a few lines around it) QStringList contextLines; for (int ctx = qMax(0, lineNum - 2); ctx <= qMin(lines.size() - 1, lineNum + 2); ++ctx) { QString prefix = (ctx == lineNum) ? u">>> "_s : u" "_s; contextLines << u"%1%2: %3"_s.arg(prefix, QString::number(ctx + 1), lines[ctx]); } usage.sourceContext = contextLines.join('\n'_L1); m_usages.append(usage); } } } private: QListWidget *m_usagesList = nullptr; QLabel *m_glyphLabel = nullptr; QTextEdit *m_detailsText = nullptr; QList m_usages; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationName(u"FontIcon Collector"_s); app.setApplicationVersion(u"1.0"_s); const QString cascadiaCodeFontPath = u"Cascadia Code NF"_s; if (QFontDatabase::hasFamily(cascadiaCodeFontPath)) { QApplication::setFont(cascadiaCodeFontPath); } QCommandLineParser parser; parser.setApplicationDescription(u"Collects and displays FontIcon usages from the QSfera Desktop codebase"_s); parser.addHelpOption(); parser.addVersionOption(); parser.process(app); FontIconCollectorWidget window; window.show(); return app.exec(); } #include "fonticon_collector.moc"