File indexing completed on 2025-01-26 05:08:07

0001 /*
0002     SPDX-FileCopyrightText: 2003-2007 Craig Drummond <craig@kde.org>
0003     SPDX-FileCopyrightText: 2019 Guo Yunhe <i@guoyunhe.me>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "FontFilter.h"
0009 #include "FontFilterProxyStyle.h"
0010 #include "FontList.h"
0011 #include <KIconLoader>
0012 #include <KSelectAction>
0013 #include <KToggleAction>
0014 #include <QApplication>
0015 #include <QLabel>
0016 #include <QMimeDatabase>
0017 #include <QMouseEvent>
0018 #include <QPainter>
0019 #include <QSet>
0020 #include <QString>
0021 #include <QStyleOption>
0022 
0023 namespace KFI
0024 {
0025 static void deselectCurrent(QActionGroup *act)
0026 {
0027     QAction *prev(act->checkedAction());
0028     if (prev) {
0029         prev->setChecked(false);
0030     }
0031 }
0032 
0033 static void deselectCurrent(KSelectAction *act)
0034 {
0035     deselectCurrent(act->selectableActionGroup());
0036 }
0037 
0038 // FIXME: Go back to using StyleSheets instead of a proxy style
0039 // once Qt has been fixed not to mess with widget font when
0040 // using StyleSheets
0041 class CFontFilterStyle : public CFontFilterProxyStyle
0042 {
0043 public:
0044     CFontFilterStyle(CFontFilter *parent, int ol)
0045         : CFontFilterProxyStyle(parent)
0046         , overlap(ol)
0047     {
0048     }
0049 
0050     QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const override;
0051 
0052     int overlap;
0053 };
0054 
0055 QRect CFontFilterStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
0056 {
0057     if (SE_LineEditContents == element) {
0058         QRect rect(style()->subElementRect(SE_LineEditContents, option, widget));
0059 
0060         return rect.adjusted(overlap, 0, -overlap, 0);
0061     }
0062 
0063     return CFontFilterProxyStyle::subElementRect(element, option, widget);
0064 }
0065 
0066 struct SortAction {
0067     SortAction(QAction *a)
0068         : action(a)
0069     {
0070     }
0071     bool operator<(const SortAction &o) const
0072     {
0073         return action->text().localeAwareCompare(o.action->text()) < 0;
0074     }
0075     QAction *action;
0076 };
0077 
0078 static void sortActions(KSelectAction *group)
0079 {
0080     if (group->actions().count() > 1) {
0081         QList<QAction *> actions = group->actions();
0082         QList<QAction *>::ConstIterator it(actions.constBegin()), end(actions.constEnd());
0083         QList<SortAction> sorted;
0084 
0085         for (; it != end; ++it) {
0086             sorted.append(SortAction(*it));
0087             group->removeAction(*it);
0088         }
0089 
0090         std::sort(sorted.begin(), sorted.end());
0091         QList<SortAction>::ConstIterator s(sorted.constBegin()), sEnd(sorted.constEnd());
0092 
0093         for (; s != sEnd; ++s) {
0094             group->addAction((*s).action);
0095         }
0096     }
0097 }
0098 
0099 CFontFilter::CFontFilter(QWidget *parent)
0100     : QWidget(parent)
0101 {
0102     m_icons[CRIT_FAMILY] = QIcon::fromTheme("draw-text");
0103     m_texts[CRIT_FAMILY] = i18n("Family");
0104     m_icons[CRIT_STYLE] = QIcon::fromTheme("format-text-bold");
0105     m_texts[CRIT_STYLE] = i18n("Style");
0106     m_icons[CRIT_FOUNDRY] = QIcon::fromTheme("user-identity");
0107     m_texts[CRIT_FOUNDRY] = i18n("Foundry");
0108     m_icons[CRIT_FONTCONFIG] = QIcon::fromTheme("system-search");
0109     m_texts[CRIT_FONTCONFIG] = i18n("FontConfig Match");
0110     m_icons[CRIT_FILETYPE] = QIcon::fromTheme("preferences-desktop-font-installer");
0111     m_texts[CRIT_FILETYPE] = i18n("File Type");
0112     m_icons[CRIT_FILENAME] = QIcon::fromTheme("application-x-font-type1");
0113     m_texts[CRIT_FILENAME] = i18n("File Name");
0114     m_icons[CRIT_LOCATION] = QIcon::fromTheme("folder");
0115     m_texts[CRIT_LOCATION] = i18n("File Location");
0116     m_icons[CRIT_WS] = QIcon::fromTheme("character-set");
0117     m_texts[CRIT_WS] = i18n("Writing System");
0118 
0119     m_layout = new QHBoxLayout(this);
0120     setLayout(m_layout);
0121 
0122     m_lineEdit = new QLineEdit(this);
0123     m_lineEdit->setClearButtonEnabled(true);
0124     m_layout->addWidget(m_lineEdit);
0125 
0126     m_menuButton = new QPushButton(this);
0127     m_menuButton->setIcon(QIcon::fromTheme("view-filter"));
0128     m_menuButton->setText(i18n("Set Criteria"));
0129     m_layout->addWidget(m_menuButton);
0130 
0131     connect(m_lineEdit, &QLineEdit::textChanged, this, &CFontFilter::textChanged);
0132 
0133     m_menu = new QMenu(this);
0134     m_menuButton->setMenu(m_menu);
0135 
0136     m_actionGroup = new QActionGroup(this);
0137     addAction(CRIT_FAMILY, true);
0138     addAction(CRIT_STYLE, false);
0139 
0140     KSelectAction *foundryMenu = new KSelectAction(m_icons[CRIT_FOUNDRY], m_texts[CRIT_FOUNDRY], this);
0141     m_actions[CRIT_FOUNDRY] = foundryMenu;
0142     m_menu->addAction(m_actions[CRIT_FOUNDRY]);
0143     foundryMenu->setData((int)CRIT_FOUNDRY);
0144     foundryMenu->setVisible(false);
0145     connect(foundryMenu, &KSelectAction::textTriggered, this, &CFontFilter::foundryChanged);
0146 
0147     addAction(CRIT_FONTCONFIG, false);
0148 
0149     KSelectAction *ftMenu = new KSelectAction(m_icons[CRIT_FILETYPE], m_texts[CRIT_FILETYPE], this);
0150     m_actions[CRIT_FILETYPE] = ftMenu;
0151     m_menu->addAction(m_actions[CRIT_FILETYPE]);
0152     ftMenu->setData((int)CRIT_FILETYPE);
0153 
0154     QStringList::ConstIterator it(CFontList::fontMimeTypes.constBegin()), end(CFontList::fontMimeTypes.constEnd());
0155     QMimeDatabase db;
0156     for (; it != end; ++it) {
0157         if ((*it) != "application/vnd.kde.fontspackage") {
0158             QMimeType mime = db.mimeTypeForName(*it);
0159 
0160             KToggleAction *act = new KToggleAction(QIcon::fromTheme(mime.iconName()), mime.comment(), this);
0161 
0162             ftMenu->addAction(act);
0163             act->setChecked(false);
0164 
0165             QStringList mimes;
0166             foreach (QString pattern, mime.globPatterns())
0167                 mimes.append(pattern.remove(QStringLiteral("*.")));
0168             act->setData(mimes);
0169         }
0170     }
0171 
0172     sortActions(ftMenu);
0173     connect(ftMenu, &KSelectAction::textTriggered, this, &CFontFilter::ftChanged);
0174     m_currentFileTypes.clear();
0175 
0176     addAction(CRIT_FILENAME, false);
0177     addAction(CRIT_LOCATION, false);
0178 
0179     KSelectAction *wsMenu = new KSelectAction(m_icons[CRIT_WS], m_texts[CRIT_WS], this);
0180     m_actions[CRIT_WS] = wsMenu;
0181     m_menu->addAction(m_actions[CRIT_WS]);
0182     wsMenu->setData((int)CRIT_WS);
0183 
0184     m_currentWs = QFontDatabase::Any;
0185     for (int i = QFontDatabase::Latin; i < QFontDatabase::WritingSystemsCount; ++i) {
0186         KToggleAction *wsAct =
0187             new KToggleAction(QFontDatabase::Other == i ? i18n("Symbol/Other") : QFontDatabase::writingSystemName((QFontDatabase::WritingSystem)i), this);
0188 
0189         wsMenu->addAction(wsAct);
0190         wsAct->setChecked(false);
0191         wsAct->setData(i);
0192     }
0193     sortActions(wsMenu);
0194     connect(wsMenu, &KSelectAction::textTriggered, this, &CFontFilter::wsChanged);
0195 
0196     setCriteria(CRIT_FAMILY);
0197     setStyle(new CFontFilterStyle(this, m_menuButton->width()));
0198 }
0199 
0200 void CFontFilter::setFoundries(const QSet<QString> &currentFoundries)
0201 {
0202     QAction *act(((KSelectAction *)m_actions[CRIT_FOUNDRY])->currentAction());
0203     QString prev(act && act->isChecked() ? act->text() : QString());
0204     bool changed(false);
0205     QList<QAction *> prevFoundries(((KSelectAction *)m_actions[CRIT_FOUNDRY])->actions());
0206     QList<QAction *>::ConstIterator fIt(prevFoundries.constBegin()), fEnd(prevFoundries.constEnd());
0207     QSet<QString> foundries(currentFoundries);
0208 
0209     // Determine which of 'foundries' are new ones, and which old ones need to be removed...
0210     for (; fIt != fEnd; ++fIt) {
0211         if (foundries.contains((*fIt)->text())) {
0212             foundries.remove((*fIt)->text());
0213         } else {
0214             ((KSelectAction *)m_actions[CRIT_FOUNDRY])->removeAction(*fIt);
0215             (*fIt)->deleteLater();
0216             changed = true;
0217         }
0218     }
0219 
0220     if (!foundries.isEmpty()) {
0221         // Add foundries to menu - replacing '&' with '&&', as '&' is taken to be
0222         // a shortcut!
0223         QSet<QString>::ConstIterator it(foundries.begin()), end(foundries.end());
0224 
0225         for (; it != end; ++it) {
0226             QString foundry(*it);
0227 
0228             foundry.replace("&", "&&");
0229             ((KSelectAction *)m_actions[CRIT_FOUNDRY])->addAction(foundry);
0230         }
0231         changed = true;
0232     }
0233 
0234     if (changed) {
0235         sortActions((KSelectAction *)m_actions[CRIT_FOUNDRY]);
0236         if (!prev.isEmpty()) {
0237             act = ((KSelectAction *)m_actions[CRIT_FOUNDRY])->action(prev);
0238             if (act) {
0239                 ((KSelectAction *)m_actions[CRIT_FOUNDRY])->setCurrentAction(act);
0240             } else {
0241                 ((KSelectAction *)m_actions[CRIT_FOUNDRY])->setCurrentItem(0);
0242             }
0243         }
0244 
0245         m_actions[CRIT_FOUNDRY]->setVisible(((KSelectAction *)m_actions[CRIT_FOUNDRY])->actions().count());
0246     }
0247 }
0248 
0249 void CFontFilter::filterChanged()
0250 {
0251     QAction *act(m_actionGroup->checkedAction());
0252 
0253     if (act) {
0254         ECriteria crit((ECriteria)act->data().toInt());
0255 
0256         if (m_currentCriteria != crit) {
0257             deselectCurrent((KSelectAction *)m_actions[CRIT_FOUNDRY]);
0258             deselectCurrent((KSelectAction *)m_actions[CRIT_FILETYPE]);
0259             deselectCurrent((KSelectAction *)m_actions[CRIT_WS]);
0260             m_lineEdit->setText(QString());
0261             m_currentWs = QFontDatabase::Any;
0262             m_currentFileTypes.clear();
0263 
0264             setCriteria(crit);
0265             m_lineEdit->setPlaceholderText(i18n("Filter by %1…", act->text()));
0266             m_lineEdit->setReadOnly(false);
0267         }
0268     }
0269 }
0270 
0271 void CFontFilter::ftChanged(const QString &ft)
0272 {
0273     deselectCurrent((KSelectAction *)m_actions[CRIT_FOUNDRY]);
0274     deselectCurrent((KSelectAction *)m_actions[CRIT_WS]);
0275     deselectCurrent(m_actionGroup);
0276 
0277     QAction *act(((KSelectAction *)m_actions[CRIT_FILETYPE])->currentAction());
0278 
0279     if (act) {
0280         m_currentFileTypes = act->data().toStringList();
0281     }
0282     m_currentCriteria = CRIT_FILETYPE;
0283     m_lineEdit->setReadOnly(true);
0284     setCriteria(m_currentCriteria);
0285     m_lineEdit->setText(ft);
0286     m_lineEdit->setPlaceholderText(m_lineEdit->text());
0287 }
0288 
0289 void CFontFilter::wsChanged(const QString &writingSystemName)
0290 {
0291     deselectCurrent((KSelectAction *)m_actions[CRIT_FOUNDRY]);
0292     deselectCurrent((KSelectAction *)m_actions[CRIT_FILETYPE]);
0293     deselectCurrent(m_actionGroup);
0294 
0295     QAction *act(((KSelectAction *)m_actions[CRIT_WS])->currentAction());
0296 
0297     if (act) {
0298         m_currentWs = (QFontDatabase::WritingSystem)act->data().toInt();
0299     }
0300     m_currentCriteria = CRIT_WS;
0301     m_lineEdit->setReadOnly(true);
0302     setCriteria(m_currentCriteria);
0303     m_lineEdit->setText(writingSystemName);
0304     m_lineEdit->setPlaceholderText(m_lineEdit->text());
0305 }
0306 
0307 void CFontFilter::foundryChanged(const QString &foundry)
0308 {
0309     deselectCurrent((KSelectAction *)m_actions[CRIT_WS]);
0310     deselectCurrent((KSelectAction *)m_actions[CRIT_FILETYPE]);
0311     deselectCurrent(m_actionGroup);
0312 
0313     m_currentCriteria = CRIT_FOUNDRY;
0314     m_lineEdit->setReadOnly(true);
0315     m_lineEdit->setText(foundry);
0316     m_lineEdit->setPlaceholderText(m_lineEdit->text());
0317     setCriteria(m_currentCriteria);
0318 }
0319 
0320 void CFontFilter::textChanged(const QString &text)
0321 {
0322     Q_EMIT queryChanged(text);
0323 }
0324 
0325 void CFontFilter::addAction(ECriteria crit, bool on)
0326 {
0327     m_actions[crit] = new KToggleAction(m_icons[crit], m_texts[crit], this);
0328     m_menu->addAction(m_actions[crit]);
0329     m_actionGroup->addAction(m_actions[crit]);
0330     m_actions[crit]->setData((int)crit);
0331     m_actions[crit]->setChecked(on);
0332     if (on) {
0333         m_lineEdit->setPlaceholderText(i18n("Filter by %1…", m_texts[crit]));
0334     }
0335     connect(m_actions[crit], &QAction::toggled, this, &CFontFilter::filterChanged);
0336 }
0337 
0338 void CFontFilter::setCriteria(ECriteria crit)
0339 {
0340     m_currentCriteria = crit;
0341 
0342     Q_EMIT criteriaChanged(crit, ((qulonglong)1) << (int)m_currentWs, m_currentFileTypes);
0343 }
0344 
0345 }