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

0001 /*
0002     SPDX-FileCopyrightText: 2003-2007 Craig Drummond <craig@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "FontList.h"
0007 #include "Fc.h"
0008 #include "FontInstInterface.h"
0009 #include "GroupList.h"
0010 #include "KfiConstants.h"
0011 #include <KColorScheme>
0012 #include <KIconLoader>
0013 #include <KMessageBox>
0014 #include <QDBusServiceWatcher>
0015 #include <QDir>
0016 #include <QDrag>
0017 #include <QDropEvent>
0018 #include <QFile>
0019 #include <QFont>
0020 #include <QHeaderView>
0021 #include <QIcon>
0022 #include <QMenu>
0023 #include <QMimeData>
0024 #include <QMimeDatabase>
0025 #include <QPixmap>
0026 #include <QProcess>
0027 #include <QTimer>
0028 #include <stdlib.h>
0029 #include <unistd.h>
0030 #include <utime.h>
0031 
0032 namespace KFI
0033 {
0034 const QStringList CFontList::fontMimeTypes(QStringList() << "font/ttf"
0035                                                          << "font/otf"
0036                                                          << "font/collection"
0037                                                          << "application/x-font-ttf"
0038                                                          << "application/x-font-otf"
0039                                                          << "application/x-font-type1"
0040                                                          << "application/x-font-pcf"
0041                                                          << "application/x-font-bdf"
0042                                                          << "application/vnd.kde.fontspackage");
0043 
0044 static const int constMaxSlowed = 250;
0045 
0046 static void decompose(const QString &name, QString &family, QString &style)
0047 {
0048     int commaPos = name.lastIndexOf(',');
0049 
0050     family = -1 == commaPos ? name : name.left(commaPos);
0051     style = -1 == commaPos ? KFI_WEIGHT_REGULAR.untranslatedText() : name.mid(commaPos + 2);
0052 }
0053 
0054 static void addFont(CFontItem *font,
0055                     CJobRunner::ItemList &urls,
0056                     QStringList &fontNames,
0057                     QSet<Misc::TFont> *fonts,
0058                     QSet<CFontItem *> &usedFonts,
0059                     bool getEnabled,
0060                     bool getDisabled)
0061 {
0062     if (!usedFonts.contains(font) && ((getEnabled && font->isEnabled()) || (getDisabled && !font->isEnabled()))) {
0063         urls.append(CJobRunner::Item(font->url(), font->name(), !font->isEnabled()));
0064         fontNames.append(font->name());
0065         usedFonts.insert(font);
0066         if (fonts) {
0067             fonts->insert(Misc::TFont(font->family(), font->styleInfo()));
0068         }
0069     }
0070 }
0071 
0072 static QString replaceEnvVar(const QString &text)
0073 {
0074     QString mod(text);
0075     int endPos(text.indexOf('/'));
0076 
0077     if (endPos == -1) {
0078         endPos = text.length() - 1;
0079     } else {
0080         endPos--;
0081     }
0082 
0083     if (endPos > 0) {
0084         QString envVar(text.mid(1, endPos));
0085 
0086         const char *val = getenv(envVar.toLatin1().constData());
0087 
0088         if (val) {
0089             mod = Misc::fileSyntax(QFile::decodeName(val) + mod.mid(endPos + 1));
0090         }
0091     }
0092 
0093     return mod;
0094 }
0095 
0096 //
0097 // Convert from list such as:
0098 //
0099 //    Arial
0100 //    Arial, Bold
0101 //    Courier
0102 //    Times
0103 //    Times, Italic
0104 //
0105 // To:
0106 //
0107 //    Arial (Regular, Bold)
0108 //    Coutier
0109 //    Times (Regular, Italic)
0110 QStringList CFontList::compact(const QStringList &fonts)
0111 {
0112     QString lastFamily, entry;
0113     QStringList::ConstIterator it(fonts.begin()), end(fonts.end());
0114     QStringList compacted;
0115     QSet<QString> usedStyles;
0116 
0117     for (; it != end; ++it) {
0118         QString family, style;
0119 
0120         decompose(*it, family, style);
0121 
0122         if (family != lastFamily) {
0123             usedStyles.clear();
0124             if (entry.length()) {
0125                 entry += ')';
0126                 compacted.append(entry);
0127             }
0128             entry = QString(family + " (");
0129             lastFamily = family;
0130         }
0131         if (!usedStyles.contains(style)) {
0132             usedStyles.clear();
0133             if (entry.length() && '(' != entry[entry.length() - 1]) {
0134                 entry += ", ";
0135             }
0136             entry += style;
0137             usedStyles.insert(style);
0138         }
0139     }
0140 
0141     if (entry.length()) {
0142         entry += ')';
0143         compacted.append(entry);
0144     }
0145 
0146     return compacted;
0147 }
0148 
0149 QString capitaliseFoundry(const QString &foundry)
0150 {
0151     QString f(foundry.toLower());
0152 
0153     if (f == QLatin1String("ibm")) {
0154         return QLatin1String("IBM");
0155     } else if (f == QLatin1String("urw")) {
0156         return QLatin1String("URW");
0157     } else if (f == QLatin1String("itc")) {
0158         return QLatin1String("ITC");
0159     } else if (f == QLatin1String("nec")) {
0160         return QLatin1String("NEC");
0161     } else if (f == QLatin1String("b&h")) {
0162         return QLatin1String("B&H");
0163     } else if (f == QLatin1String("dec")) {
0164         return QLatin1String("DEC");
0165     } else {
0166         QChar *ch(f.data());
0167         int len(f.length());
0168         bool isSpace(true);
0169 
0170         while (len--) {
0171             if (isSpace) {
0172                 *ch = ch->toUpper();
0173             }
0174 
0175             isSpace = ch->isSpace();
0176             ++ch;
0177         }
0178     }
0179 
0180     return f;
0181 }
0182 
0183 inline bool isSysFolder(const QString &sect)
0184 {
0185     return KFI_KIO_FONTS_SYS.toString() == sect || KFI_KIO_FONTS_SYS.untranslatedText() == sect;
0186 }
0187 
0188 CFontItem::CFontItem(CFontModelItem *p, const Style &s, bool sys)
0189     : CFontModelItem(p)
0190     , m_styleName(FC::createStyleName(s.value()))
0191     , m_style(s)
0192 {
0193     refresh();
0194     if (!Misc::root()) {
0195         setIsSystem(sys);
0196     }
0197 }
0198 
0199 void CFontItem::refresh()
0200 {
0201     FileCont::ConstIterator it(m_style.files().begin()), end(m_style.files().end());
0202 
0203     m_enabled = false;
0204     for (; it != end; ++it) {
0205         if (!Misc::isHidden(Misc::getFile((*it).path()))) {
0206             m_enabled = true;
0207             break;
0208         }
0209     }
0210 }
0211 
0212 CFamilyItem::CFamilyItem(CFontList &p, const Family &f, bool sys)
0213     : CFontModelItem(nullptr)
0214     , m_name(f.name())
0215     , m_status(ENABLED)
0216     , m_realStatus(ENABLED)
0217     , m_regularFont(nullptr)
0218     , m_parent(p)
0219 {
0220     addFonts(f.styles(), sys);
0221     // updateStatus();
0222 }
0223 
0224 CFamilyItem::~CFamilyItem()
0225 {
0226     qDeleteAll(m_fonts);
0227     m_fonts.clear();
0228 }
0229 
0230 bool CFamilyItem::addFonts(const StyleCont &styles, bool sys)
0231 {
0232     StyleCont::ConstIterator it(styles.begin()), end(styles.end());
0233     bool modified = false;
0234 
0235     for (; it != end; ++it) {
0236         CFontItem *font = findFont((*it).value(), sys);
0237 
0238         if (!font) {
0239             // New font style!
0240             m_fonts.append(new CFontItem(this, *it, sys));
0241             modified = true;
0242         } else {
0243             int before = (*it).files().size();
0244 
0245             font->addFiles((*it).files());
0246 
0247             if ((*it).files().size() != before) {
0248                 modified = true;
0249                 font->refresh();
0250             }
0251         }
0252     }
0253     return modified;
0254 }
0255 
0256 CFontItem *CFamilyItem::findFont(quint32 style, bool sys)
0257 {
0258     CFontItemCont::ConstIterator fIt(m_fonts.begin()), fEnd(m_fonts.end());
0259 
0260     for (; fIt != fEnd; ++fIt) {
0261         if ((*(*fIt)).styleInfo() == style && (*(*fIt)).isSystem() == sys) {
0262             return (*fIt);
0263         }
0264     }
0265 
0266     return nullptr;
0267 }
0268 
0269 void CFamilyItem::getFoundries(QSet<QString> &foundries) const
0270 {
0271     CFontItemCont::ConstIterator it(m_fonts.begin()), end(m_fonts.end());
0272 
0273     for (; it != end; ++it) {
0274         FileCont::ConstIterator fIt((*it)->files().begin()), fEnd((*it)->files().end());
0275 
0276         for (; fIt != fEnd; ++fIt) {
0277             if (!(*fIt).foundry().isEmpty()) {
0278                 foundries.insert(capitaliseFoundry((*fIt).foundry()));
0279             }
0280         }
0281     }
0282 }
0283 
0284 bool CFamilyItem::usable(const CFontItem *font, bool root)
0285 {
0286     return (root || (font->isSystem() && m_parent.allowSys()) || (!font->isSystem() && m_parent.allowUser()));
0287 }
0288 
0289 void CFamilyItem::addFont(CFontItem *font, bool update)
0290 {
0291     m_fonts.append(font);
0292     if (update) {
0293         updateStatus();
0294         updateRegularFont(font);
0295     }
0296 }
0297 
0298 void CFamilyItem::removeFont(CFontItem *font, bool update)
0299 {
0300     m_fonts.removeAll(font);
0301     if (update) {
0302         updateStatus();
0303     }
0304     if (m_regularFont == font) {
0305         m_regularFont = nullptr;
0306         if (update) {
0307             updateRegularFont(nullptr);
0308         }
0309     }
0310     delete font;
0311 }
0312 
0313 void CFamilyItem::refresh()
0314 {
0315     updateStatus();
0316     m_regularFont = nullptr;
0317     updateRegularFont(nullptr);
0318 }
0319 
0320 bool CFamilyItem::updateStatus()
0321 {
0322     bool root(Misc::root());
0323     EStatus oldStatus(m_status);
0324     CFontItemCont::ConstIterator it(m_fonts.begin()), end(m_fonts.end());
0325     int en(0), dis(0), allEn(0), allDis(0);
0326     bool oldSys(isSystem()), sys(false);
0327 
0328     m_fontCount = 0;
0329     for (; it != end; ++it) {
0330         if (usable(*it, root)) {
0331             if ((*it)->isEnabled()) {
0332                 en++;
0333             } else {
0334                 dis++;
0335             }
0336             if (!sys) {
0337                 sys = (*it)->isSystem();
0338             }
0339             m_fontCount++;
0340         } else if ((*it)->isEnabled()) {
0341             allEn++;
0342         } else {
0343             allDis++;
0344         }
0345     }
0346 
0347     allEn += en;
0348     allDis += dis;
0349 
0350     m_status = en && dis ? PARTIAL : en ? ENABLED : DISABLED;
0351 
0352     m_realStatus = allEn && allDis ? PARTIAL : allEn ? ENABLED : DISABLED;
0353 
0354     if (!root) {
0355         setIsSystem(sys);
0356     }
0357 
0358     return m_status != oldStatus || isSystem() != oldSys;
0359 }
0360 
0361 bool CFamilyItem::updateRegularFont(CFontItem *font)
0362 {
0363     static const quint32 constRegular = FC::createStyleVal(FC_WEIGHT_REGULAR, KFI_FC_WIDTH_NORMAL, FC_SLANT_ROMAN);
0364 
0365     CFontItem *oldFont(m_regularFont);
0366     bool root(Misc::root());
0367 
0368     if (font && usable(font, root)) {
0369         if (m_regularFont) {
0370             int regDiff = abs((long)(m_regularFont->styleInfo() - constRegular)), fontDiff = abs((long)(font->styleInfo() - constRegular));
0371 
0372             if (fontDiff < regDiff) {
0373                 m_regularFont = font;
0374             }
0375         } else {
0376             m_regularFont = font;
0377         }
0378     } else // This case happens when the regular font is deleted...
0379     {
0380         CFontItemCont::ConstIterator it(m_fonts.begin()), end(m_fonts.end());
0381         quint32 current = 0x0FFFFFFF;
0382 
0383         for (; it != end; ++it) {
0384             if (usable(*it, root)) {
0385                 quint32 diff = abs((long)((*it)->styleInfo() - constRegular));
0386 
0387                 if (diff < current) {
0388                     m_regularFont = (*it);
0389                     current = diff;
0390                 }
0391             }
0392         }
0393     }
0394 
0395     return oldFont != m_regularFont;
0396 }
0397 
0398 CFontList::CFontList(QWidget *parent)
0399     : QAbstractItemModel(parent)
0400     , m_allowSys(true)
0401     , m_allowUser(true)
0402     , m_slowUpdates(false)
0403 {
0404     FontInst::registerTypes();
0405 
0406     QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()),
0407                                                            QDBusConnection::sessionBus(),
0408                                                            QDBusServiceWatcher::WatchForOwnerChange,
0409                                                            this);
0410 
0411     connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &CFontList::dbusServiceOwnerChanged);
0412     connect(CJobRunner::dbus(), &OrgKdeFontinstInterface::fontsAdded, this, &CFontList::fontsAdded);
0413     connect(CJobRunner::dbus(), &OrgKdeFontinstInterface::fontsRemoved, this, &CFontList::fontsRemoved);
0414     connect(CJobRunner::dbus(), &OrgKdeFontinstInterface::fontList, this, &CFontList::fontList);
0415 }
0416 
0417 CFontList::~CFontList()
0418 {
0419     qDeleteAll(m_families);
0420     m_families.clear();
0421     m_familyHash.clear();
0422 }
0423 
0424 void CFontList::dbusServiceOwnerChanged(const QString &name, const QString &from, const QString &to)
0425 {
0426     Q_UNUSED(from);
0427     Q_UNUSED(to);
0428 
0429     if (name == QLatin1String(OrgKdeFontinstInterface::staticInterfaceName())) {
0430         load();
0431     }
0432 }
0433 
0434 void CFontList::fontList(int pid, const QList<KFI::Families> &families)
0435 {
0436     //  printf("**** fontList:%d/%d  %d\n", pid, getpid(), families.count());
0437 
0438     if (pid == getpid()) {
0439         QList<KFI::Families>::ConstIterator it(families.begin()), end(families.end());
0440         int count(families.size());
0441 
0442         for (int i = 0; it != end; ++it, ++i) {
0443             fontsAdded(*it);
0444             Q_EMIT listingPercent(i * 100 / count);
0445         }
0446         Q_EMIT listingPercent(100);
0447     }
0448 }
0449 
0450 void CFontList::unsetSlowUpdates()
0451 {
0452     setSlowUpdates(false);
0453 }
0454 
0455 void CFontList::load()
0456 {
0457     for (int t = 0; t < NUM_MSGS_TYPES; ++t) {
0458         for (int f = 0; f < FontInst::FOLDER_COUNT; ++f) {
0459             m_slowedMsgs[t][f].clear();
0460         }
0461     }
0462 
0463     setSlowUpdates(false);
0464 
0465     Q_EMIT layoutAboutToBeChanged();
0466     //     beginRemoveRows(QModelIndex(), 0, m_families.count());
0467     m_families.clear();
0468     m_familyHash.clear();
0469     //     endRemoveRows();
0470     Q_EMIT layoutChanged();
0471     Q_EMIT listingPercent(0);
0472 
0473     CJobRunner::startDbusService();
0474     CJobRunner::dbus()->list(FontInst::SYS_MASK | FontInst::USR_MASK, getpid());
0475 }
0476 
0477 void CFontList::setSlowUpdates(bool slow)
0478 {
0479     if (m_slowUpdates != slow) {
0480         if (!slow) {
0481             for (int i = 0; i < FontInst::FOLDER_COUNT; ++i) {
0482                 actionSlowedUpdates(i == FontInst::FOLDER_SYS);
0483             }
0484         }
0485         m_slowUpdates = slow;
0486     }
0487 }
0488 
0489 int CFontList::columnCount(const QModelIndex &) const
0490 {
0491     return NUM_COLS;
0492 }
0493 
0494 QVariant CFontList::data(const QModelIndex &, int) const
0495 {
0496     return QVariant();
0497 }
0498 
0499 Qt::ItemFlags CFontList::flags(const QModelIndex &index) const
0500 {
0501     return !index.isValid() ? Qt::ItemIsEnabled | Qt::ItemIsDropEnabled
0502                             : Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
0503 }
0504 
0505 Qt::DropActions CFontList::supportedDropActions() const
0506 {
0507     return Qt::CopyAction | Qt::MoveAction;
0508 }
0509 
0510 QMimeData *CFontList::mimeData(const QModelIndexList &indexes) const
0511 {
0512     QMimeData *mimeData = new QMimeData();
0513     QByteArray encodedData;
0514     QModelIndexList::ConstIterator it(indexes.begin()), end(indexes.end());
0515     QSet<QString> families;
0516     QDataStream ds(&encodedData, QIODevice::WriteOnly);
0517 
0518     for (; it != end; ++it) {
0519         if ((*it).isValid()) {
0520             if ((static_cast<CFontModelItem *>((*it).internalPointer()))->isFont()) {
0521                 CFontItem *font = static_cast<CFontItem *>((*it).internalPointer());
0522 
0523                 families.insert(font->family());
0524             } else {
0525                 CFamilyItem *fam = static_cast<CFamilyItem *>((*it).internalPointer());
0526 
0527                 families.insert(fam->name());
0528             }
0529         }
0530     }
0531 
0532     ds << families;
0533     mimeData->setData(KFI_FONT_DRAG_MIME, encodedData);
0534     return mimeData;
0535 }
0536 
0537 QStringList CFontList::mimeTypes() const
0538 {
0539     QStringList types;
0540 
0541     types << "text/uri-list";
0542     return types;
0543 }
0544 
0545 QVariant CFontList::headerData(int section, Qt::Orientation orientation, int role) const
0546 {
0547     if (orientation == Qt::Horizontal) {
0548         switch (role) {
0549         case Qt::DisplayRole:
0550             switch (section) {
0551             case COL_FONT:
0552                 return i18n("Font");
0553             case COL_STATUS:
0554                 return i18n("Status");
0555             default:
0556                 break;
0557             }
0558             break;
0559             //             case Qt::DecorationRole:
0560             //                 if(COL_STATUS==section)
0561             //                     return QIcon::fromTheme("fontstatus");
0562             //                 break;
0563         case Qt::TextAlignmentRole:
0564             return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
0565         case Qt::ToolTipRole:
0566             if (COL_STATUS == section) {
0567                 return i18n(
0568                     "This column shows the status of the font family, and of the "
0569                     "individual font styles.");
0570             }
0571             break;
0572         case Qt::WhatsThisRole:
0573             return whatsThis();
0574         default:
0575             break;
0576         }
0577     }
0578 
0579     return QVariant();
0580 }
0581 
0582 QModelIndex CFontList::index(int row, int column, const QModelIndex &parent) const
0583 {
0584     if (parent.isValid()) // Then font...
0585     {
0586         CFamilyItem *fam = static_cast<CFamilyItem *>(parent.internalPointer());
0587 
0588         if (row < fam->fonts().count()) {
0589             return createIndex(row, column, fam->fonts().at(row));
0590         }
0591     } else // Family....
0592         if (row < m_families.count()) {
0593             return createIndex(row, column, m_families.at(row));
0594         }
0595 
0596     return QModelIndex();
0597 }
0598 
0599 QModelIndex CFontList::parent(const QModelIndex &index) const
0600 {
0601     if (!index.isValid()) {
0602         return QModelIndex();
0603     }
0604 
0605     CFontModelItem *mi = static_cast<CFontModelItem *>(index.internalPointer());
0606 
0607     if (mi->isFamily()) {
0608         return QModelIndex();
0609     } else {
0610         CFontItem *font = static_cast<CFontItem *>(index.internalPointer());
0611 
0612         return createIndex(m_families.indexOf((static_cast<CFamilyItem *>(font->parent()))), 0, font->parent());
0613     }
0614 }
0615 
0616 int CFontList::rowCount(const QModelIndex &parent) const
0617 {
0618     if (parent.isValid()) {
0619         CFontModelItem *mi = static_cast<CFontModelItem *>(parent.internalPointer());
0620 
0621         if (mi->isFont()) {
0622             return 0;
0623         }
0624 
0625         CFamilyItem *fam = static_cast<CFamilyItem *>(parent.internalPointer());
0626 
0627         return fam->fonts().count();
0628     } else {
0629         return m_families.count();
0630     }
0631 }
0632 
0633 void CFontList::refresh(bool allowSys, bool allowUser)
0634 {
0635     m_allowSys = allowSys;
0636     m_allowUser = allowUser;
0637     CFamilyItemCont::ConstIterator it(m_families.begin()), end(m_families.end());
0638 
0639     for (; it != end; ++it) {
0640         (*it)->refresh();
0641     }
0642 }
0643 
0644 void CFontList::getFamilyStats(QSet<QString> &enabled, QSet<QString> &disabled, QSet<QString> &partial)
0645 {
0646     CFamilyItemCont::ConstIterator it(m_families.begin()), end(m_families.end());
0647 
0648     for (; it != end; ++it) {
0649         switch ((*it)->realStatus()) {
0650         case CFamilyItem::ENABLED:
0651             enabled.insert((*it)->name());
0652             break;
0653         case CFamilyItem::PARTIAL:
0654             partial.insert((*it)->name());
0655             break;
0656         case CFamilyItem::DISABLED:
0657             disabled.insert((*it)->name());
0658             break;
0659         }
0660     }
0661 }
0662 
0663 void CFontList::getFoundries(QSet<QString> &foundries) const
0664 {
0665     CFamilyItemCont::ConstIterator it(m_families.begin()), end(m_families.end());
0666 
0667     for (; it != end; ++it) {
0668         (*it)->getFoundries(foundries);
0669     }
0670 }
0671 
0672 QString CFontList::whatsThis() const
0673 {
0674     return i18n(
0675         "<p>This list shows your installed fonts. The fonts are grouped by family, and the"
0676         " number in square brackets represents the number of styles in which the family is"
0677         " available. e.g.</p>"
0678         "<ul>"
0679         "<li>Times [4]"
0680         "<ul><li>Regular</li>"
0681         "<li>Bold</li>"
0682         "<li>Bold Italic</li>"
0683         "<li>Italic</li>"
0684         "</ul>"
0685         "</li>"
0686         "</ul>");
0687 }
0688 
0689 void CFontList::fontsAdded(const KFI::Families &families)
0690 {
0691     // printf("**** FONT ADDED:%d\n", families.items.count());
0692     if (m_slowUpdates) {
0693         storeSlowedMessage(families, MSG_ADD);
0694     } else {
0695         addFonts(families.items, families.isSystem && !Misc::root());
0696     }
0697 }
0698 
0699 void CFontList::fontsRemoved(const KFI::Families &families)
0700 {
0701     // printf("**** FONT REMOVED:%d\n", families.items.count());
0702     if (m_slowUpdates) {
0703         storeSlowedMessage(families, MSG_DEL);
0704     } else {
0705         removeFonts(families.items, families.isSystem && !Misc::root());
0706     }
0707 }
0708 
0709 void CFontList::storeSlowedMessage(const Families &families, EMsgType type)
0710 {
0711     int folder = families.isSystem ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER;
0712     bool playOld = false;
0713 
0714     for (int i = 0; i < NUM_MSGS_TYPES && !playOld; ++i) {
0715         if (m_slowedMsgs[i][folder].count() > constMaxSlowed) {
0716             playOld = true;
0717         }
0718     }
0719 
0720     if (playOld) {
0721         actionSlowedUpdates(families.isSystem);
0722     }
0723 
0724     FamilyCont::ConstIterator family(families.items.begin()), fend(families.items.end());
0725 
0726     for (; family != fend; ++family) {
0727         FamilyCont::ConstIterator f = m_slowedMsgs[type][folder].find(*family);
0728 
0729         if (f != m_slowedMsgs[type][folder].end()) {
0730             StyleCont::ConstIterator style((*family).styles().begin()), send((*family).styles().end());
0731 
0732             for (; style != send; ++style) {
0733                 StyleCont::ConstIterator st = (*f).styles().find(*style);
0734 
0735                 if (st == (*f).styles().end()) {
0736                     (*f).add(*style);
0737                 } else {
0738                     (*st).addFiles((*style).files());
0739                 }
0740             }
0741         } else {
0742             m_slowedMsgs[type][folder].insert(*family);
0743         }
0744     }
0745 }
0746 
0747 void CFontList::actionSlowedUpdates(bool sys)
0748 {
0749     int folder = sys ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER;
0750 
0751     for (int i = 0; i < NUM_MSGS_TYPES; ++i) {
0752         if (!m_slowedMsgs[i][folder].isEmpty()) {
0753             if (MSG_ADD == i) {
0754                 addFonts(m_slowedMsgs[i][folder], sys && !Misc::root());
0755             } else {
0756                 removeFonts(m_slowedMsgs[i][folder], sys && !Misc::root());
0757             }
0758             m_slowedMsgs[i][folder].clear();
0759         }
0760     }
0761 }
0762 
0763 void CFontList::addFonts(const FamilyCont &families, bool sys)
0764 {
0765     //     bool emitLayout=!m_slowUpdates || m_families.isEmpty();
0766     //
0767     //     if(emitLayout)
0768     //         Q_EMIT layoutAboutToBeChanged();
0769 
0770     FamilyCont::ConstIterator family(families.begin()), end(families.end());
0771     int famRowFrom = m_families.count();
0772     QSet<CFamilyItem *> modifiedFamilies;
0773 
0774     for (; family != end; ++family) {
0775         if ((*family).styles().count() > 0) {
0776             CFamilyItem *famItem = findFamily((*family).name());
0777 
0778             if (famItem) {
0779                 int rowFrom = famItem->fonts().count();
0780                 if (famItem->addFonts((*family).styles(), sys)) {
0781                     int rowTo = famItem->fonts().count();
0782 
0783                     if (rowTo != rowFrom) {
0784                         beginInsertRows(createIndex(famItem->rowNumber(), 0, famItem), rowFrom, rowTo);
0785                         endInsertRows();
0786                     }
0787 
0788                     modifiedFamilies.insert(famItem);
0789                 }
0790             } else {
0791                 famItem = new CFamilyItem(*this, *family, sys);
0792                 m_families.append(famItem);
0793                 m_familyHash[famItem->name()] = famItem;
0794                 modifiedFamilies.insert(famItem);
0795             }
0796         }
0797     }
0798 
0799     int famRowTo = m_families.count();
0800     if (famRowTo != famRowFrom) {
0801         beginInsertRows(QModelIndex(), famRowFrom, famRowTo);
0802         endInsertRows();
0803     }
0804 
0805     if (!modifiedFamilies.isEmpty()) {
0806         QSet<CFamilyItem *>::Iterator it(modifiedFamilies.begin()), end(modifiedFamilies.end());
0807 
0808         for (; it != end; ++it) {
0809             (*it)->refresh();
0810         }
0811     }
0812 
0813     //     if(emitLayout)
0814     //         Q_EMIT layoutChanged();
0815 }
0816 
0817 void CFontList::removeFonts(const FamilyCont &families, bool sys)
0818 {
0819     //     if(!m_slowUpdates)
0820     //         Q_EMIT layoutAboutToBeChanged();
0821 
0822     FamilyCont::ConstIterator family(families.begin()), end(families.end());
0823     QSet<CFamilyItem *> modifiedFamilies;
0824 
0825     for (; family != end; ++family) {
0826         if ((*family).styles().count() > 0) {
0827             CFamilyItem *famItem = findFamily((*family).name());
0828 
0829             if (famItem) {
0830                 StyleCont::ConstIterator it((*family).styles().begin()), end((*family).styles().end());
0831 
0832                 for (; it != end; ++it) {
0833                     CFontItem *fontItem = famItem->findFont((*it).value(), sys);
0834 
0835                     if (fontItem) {
0836                         int before = fontItem->files().count();
0837 
0838                         fontItem->removeFiles((*it).files());
0839 
0840                         if (fontItem->files().count() != before) {
0841                             if (fontItem->files().isEmpty()) {
0842                                 int row = -1;
0843                                 if (1 != famItem->fonts().count()) {
0844                                     row = fontItem->rowNumber();
0845                                     beginRemoveRows(createIndex(famItem->rowNumber(), 0, famItem), row, row);
0846                                 }
0847                                 famItem->removeFont(fontItem, false);
0848                                 if (-1 != row) {
0849                                     endRemoveRows();
0850                                 }
0851                             } else {
0852                                 fontItem->refresh();
0853                             }
0854                         }
0855                     }
0856                 }
0857 
0858                 if (famItem->fonts().isEmpty()) {
0859                     int row = famItem->rowNumber();
0860                     beginRemoveRows(QModelIndex(), row, row);
0861                     m_familyHash.remove(famItem->name());
0862                     m_families.removeAt(row);
0863                     endRemoveRows();
0864                 } else {
0865                     modifiedFamilies.insert(famItem);
0866                 }
0867             }
0868         }
0869     }
0870 
0871     if (!modifiedFamilies.isEmpty()) {
0872         QSet<CFamilyItem *>::Iterator it(modifiedFamilies.begin()), end(modifiedFamilies.end());
0873 
0874         for (; it != end; ++it) {
0875             (*it)->refresh();
0876         }
0877     }
0878 
0879     //     if(!m_slowUpdates)
0880     //         Q_EMIT layoutChanged();
0881 }
0882 
0883 CFamilyItem *CFontList::findFamily(const QString &familyName)
0884 {
0885     CFamilyItemHash::Iterator it = m_familyHash.find(familyName);
0886 
0887     return it == m_familyHash.end() ? nullptr : *it;
0888 }
0889 
0890 inline bool matchString(const QString &str, const QString &pattern)
0891 {
0892     return pattern.isEmpty() || -1 != str.indexOf(pattern, 0, Qt::CaseInsensitive);
0893 }
0894 
0895 CFontListSortFilterProxy::CFontListSortFilterProxy(QObject *parent, QAbstractItemModel *model)
0896     : QSortFilterProxyModel(parent)
0897     , m_group(nullptr)
0898     , m_filterCriteria(CFontFilter::CRIT_FAMILY)
0899     , m_filterWs(0)
0900     , m_fcQuery(nullptr)
0901 {
0902     setSourceModel(model);
0903     setSortCaseSensitivity(Qt::CaseInsensitive);
0904     setFilterKeyColumn(0);
0905     setDynamicSortFilter(false);
0906     m_timer = new QTimer(this);
0907     connect(m_timer, &QTimer::timeout, this, &CFontListSortFilterProxy::timeout);
0908     connect(model, &QAbstractItemModel::layoutChanged, this, &QSortFilterProxyModel::invalidate);
0909     m_timer->setSingleShot(true);
0910 }
0911 
0912 QVariant CFontListSortFilterProxy::data(const QModelIndex &idx, int role) const
0913 {
0914     if (!idx.isValid()) {
0915         return QVariant();
0916     }
0917 
0918     static const int constMaxFiles = 20;
0919 
0920     QModelIndex index(mapToSource(idx));
0921     CFontModelItem *mi = static_cast<CFontModelItem *>(index.internalPointer());
0922 
0923     if (!mi) {
0924         return QVariant();
0925     }
0926 
0927     switch (role) {
0928     case Qt::ToolTipRole:
0929         if (CFontFilter::CRIT_FILENAME == m_filterCriteria || CFontFilter::CRIT_LOCATION == m_filterCriteria
0930             || CFontFilter::CRIT_FONTCONFIG == m_filterCriteria) {
0931             if (mi->isFamily()) {
0932                 CFamilyItem *fam = static_cast<CFamilyItem *>(index.internalPointer());
0933                 CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end());
0934                 FileCont allFiles;
0935                 QString tip("<b>" + fam->name() + "</b>");
0936                 bool markMatch(CFontFilter::CRIT_FONTCONFIG == m_filterCriteria);
0937                 tip += "<p style='white-space:pre'><table>";
0938 
0939                 for (; it != end; ++it) {
0940                     allFiles += (*it)->files();
0941                 }
0942 
0943                 // qSort(allFiles);
0944                 FileCont::ConstIterator fit(allFiles.begin()), fend(allFiles.end());
0945 
0946                 for (int i = 0; fit != fend && i < constMaxFiles; ++fit, ++i) {
0947                     if (markMatch && m_fcQuery && (*fit).path() == m_fcQuery->file()) {
0948                         tip += "<tr><td><b>" + Misc::contractHome((*fit).path()) + "</b></td></tr>";
0949                     } else {
0950                         tip += "<tr><td>" + Misc::contractHome((*fit).path()) + "</td></tr>";
0951                     }
0952                 }
0953                 if (allFiles.count() > constMaxFiles) {
0954                     tip += "<tr><td><i>" + i18n("…plus %1 more", allFiles.count() - constMaxFiles) + "</td></tr>";
0955                 }
0956 
0957                 tip += "</table></p>";
0958                 return tip;
0959             } else {
0960                 CFontItem *font = static_cast<CFontItem *>(index.internalPointer());
0961                 QString tip("<b>" + font->name() + "</b>");
0962                 const FileCont &files(font->files());
0963                 bool markMatch(CFontFilter::CRIT_FONTCONFIG == m_filterCriteria);
0964 
0965                 tip += "<p style='white-space:pre'><table>";
0966 
0967                 // qSort(files);
0968                 FileCont::ConstIterator fit(files.begin()), fend(files.end());
0969 
0970                 for (int i = 0; fit != fend && i < constMaxFiles; ++fit, ++i) {
0971                     if (markMatch && m_fcQuery && (*fit).path() == m_fcQuery->file()) {
0972                         tip += "<tr><td><b>" + Misc::contractHome((*fit).path()) + "</b></td></tr>";
0973                     } else {
0974                         tip += "<tr><td>" + Misc::contractHome((*fit).path()) + "</td></tr>";
0975                     }
0976                 }
0977                 if (files.count() > constMaxFiles) {
0978                     tip += "<tr><td><i>" + i18n("…plus %1 more", files.count() - constMaxFiles) + "</td></tr></li>";
0979                 }
0980 
0981                 tip += "</table></p>";
0982                 return tip;
0983             }
0984         }
0985         break;
0986     case Qt::FontRole:
0987         if (COL_FONT == index.column() && mi->isSystem()) {
0988             QFont font;
0989             font.setItalic(true);
0990             return font;
0991         }
0992         break;
0993     case Qt::ForegroundRole:
0994         if (COL_FONT == index.column()
0995             && ((mi->isFont() && !(static_cast<CFontItem *>(index.internalPointer()))->isEnabled())
0996                 || (mi->isFamily() && CFamilyItem::DISABLED == (static_cast<CFamilyItem *>(index.internalPointer()))->status()))) {
0997             return KColorScheme(QPalette::Active).foreground(KColorScheme::NegativeText).color();
0998         }
0999         break;
1000     case Qt::DisplayRole:
1001         if (COL_FONT == index.column()) {
1002             if (mi->isFamily()) {
1003                 CFamilyItem *fam = static_cast<CFamilyItem *>(index.internalPointer());
1004 
1005                 return i18n("%1 [%2]", fam->name(), fam->fontCount());
1006             } else {
1007                 return (static_cast<CFontItem *>(index.internalPointer()))->style();
1008             }
1009         }
1010         break;
1011     case Qt::DecorationRole:
1012         if (mi->isFamily()) {
1013             CFamilyItem *fam = static_cast<CFamilyItem *>(index.internalPointer());
1014 
1015             switch (index.column()) {
1016             case COL_STATUS:
1017                 switch (fam->status()) {
1018                 case CFamilyItem::PARTIAL:
1019                     return QIcon::fromTheme("dialog-ok");
1020                 case CFamilyItem::ENABLED:
1021                     return QIcon::fromTheme("dialog-ok");
1022                 case CFamilyItem::DISABLED:
1023                     return QIcon::fromTheme("dialog-cancel");
1024                 }
1025                 break;
1026             default:
1027                 break;
1028             }
1029         } else if (COL_STATUS == index.column()) {
1030             return QIcon::fromTheme((static_cast<CFontItem *>(index.internalPointer()))->isEnabled() ? "dialog-ok" : "dialog-cancel");
1031         }
1032         break;
1033     case Qt::SizeHintRole:
1034         if (mi->isFamily()) {
1035             const int s = KIconLoader::global()->currentSize(KIconLoader::Small);
1036             return QSize(s, s + 4);
1037         }
1038     default:
1039         break;
1040     }
1041     return QVariant();
1042 }
1043 
1044 bool CFontListSortFilterProxy::acceptFont(CFontItem *fnt, bool checkFontText) const
1045 {
1046     if (m_group && (CGroupListItem::ALL != m_group->type() || (!filterText().isEmpty() && checkFontText))) {
1047         bool fontMatch(!checkFontText);
1048 
1049         if (!fontMatch) {
1050             switch (m_filterCriteria) {
1051             case CFontFilter::CRIT_FONTCONFIG:
1052                 fontMatch = m_fcQuery ? fnt->name() == m_fcQuery->font() // || fnt->files().contains(m_fcQuery->file())
1053                                       : false;
1054                 break;
1055             case CFontFilter::CRIT_STYLE:
1056                 fontMatch = matchString(fnt->style(), m_filterText);
1057                 break;
1058             case CFontFilter::CRIT_FOUNDRY: {
1059                 FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end());
1060 
1061                 for (; it != end && !fontMatch; ++it) {
1062                     fontMatch = 0 == (*it).foundry().compare(m_filterText, Qt::CaseInsensitive);
1063                 }
1064                 break;
1065             }
1066             case CFontFilter::CRIT_FILENAME: {
1067                 FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end());
1068 
1069                 for (; it != end && !fontMatch; ++it) {
1070                     QString file(Misc::getFile((*it).path()));
1071                     int pos(Misc::isHidden(file) ? 1 : 0);
1072 
1073                     if (pos == file.indexOf(m_filterText, pos, Qt::CaseInsensitive)) {
1074                         fontMatch = true;
1075                     }
1076                 }
1077                 break;
1078             }
1079             case CFontFilter::CRIT_LOCATION: {
1080                 FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end());
1081 
1082                 for (; it != end && !fontMatch; ++it) {
1083                     if (0 == Misc::getDir((*it).path()).indexOf(m_filterText, 0, Qt::CaseInsensitive)) {
1084                         fontMatch = true;
1085                     }
1086                 }
1087                 break;
1088             }
1089             case CFontFilter::CRIT_FILETYPE: {
1090                 FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end());
1091                 QStringList::ConstIterator mimeEnd(m_filterTypes.constEnd());
1092 
1093                 for (; it != end && !fontMatch; ++it) {
1094                     QStringList::ConstIterator mime(m_filterTypes.constBegin());
1095 
1096                     for (; mime != mimeEnd; ++mime) {
1097                         if (Misc::checkExt((*it).path(), *mime)) {
1098                             fontMatch = true;
1099                         }
1100                     }
1101                 }
1102                 break;
1103             }
1104             case CFontFilter::CRIT_WS:
1105                 fontMatch = fnt->writingSystems() & m_filterWs;
1106                 break;
1107             default:
1108                 break;
1109             }
1110         }
1111 
1112         return fontMatch && m_group->hasFont(fnt);
1113     }
1114 
1115     return true;
1116 }
1117 
1118 bool CFontListSortFilterProxy::acceptFamily(CFamilyItem *fam) const
1119 {
1120     CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end());
1121     bool familyMatch(CFontFilter::CRIT_FAMILY == m_filterCriteria && matchString(fam->name(), m_filterText));
1122 
1123     for (; it != end; ++it) {
1124         if (acceptFont(*it, !familyMatch)) {
1125             return true;
1126         }
1127     }
1128     return false;
1129 }
1130 
1131 bool CFontListSortFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
1132 {
1133     QModelIndex index(sourceModel()->index(sourceRow, 0, sourceParent));
1134 
1135     if (index.isValid()) {
1136         CFontModelItem *mi = static_cast<CFontModelItem *>(index.internalPointer());
1137 
1138         if (mi->isFont()) {
1139             CFontItem *font = static_cast<CFontItem *>(index.internalPointer());
1140 
1141             return acceptFont(font, !(CFontFilter::CRIT_FAMILY == m_filterCriteria && matchString(font->family(), m_filterText)));
1142         } else {
1143             return acceptFamily(static_cast<CFamilyItem *>(index.internalPointer()));
1144         }
1145     }
1146 
1147     return false;
1148 }
1149 
1150 bool CFontListSortFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const
1151 {
1152     if (left.isValid() && right.isValid()) {
1153         CFontModelItem *lmi = static_cast<CFontModelItem *>(left.internalPointer()), *rmi = static_cast<CFontModelItem *>(right.internalPointer());
1154 
1155         if (lmi->isFont() < rmi->isFont()) {
1156             return true;
1157         }
1158 
1159         if (lmi->isFont()) {
1160             CFontItem *lfi = static_cast<CFontItem *>(left.internalPointer()), *rfi = static_cast<CFontItem *>(right.internalPointer());
1161 
1162             if (COL_STATUS == filterKeyColumn()) {
1163                 if (lfi->isEnabled() < rfi->isEnabled() || (lfi->isEnabled() == rfi->isEnabled() && lfi->styleInfo() < rfi->styleInfo())) {
1164                     return true;
1165                 }
1166             } else if (lfi->styleInfo() < rfi->styleInfo()) {
1167                 return true;
1168             }
1169         } else {
1170             CFamilyItem *lfi = static_cast<CFamilyItem *>(left.internalPointer()), *rfi = static_cast<CFamilyItem *>(right.internalPointer());
1171 
1172             if (COL_STATUS == filterKeyColumn()) {
1173                 if (lfi->status() < rfi->status() || (lfi->status() == rfi->status() && QString::localeAwareCompare(lfi->name(), rfi->name()) < 0)) {
1174                     return true;
1175                 }
1176             } else if (QString::localeAwareCompare(lfi->name(), rfi->name()) < 0) {
1177                 return true;
1178             }
1179         }
1180     }
1181 
1182     return false;
1183 }
1184 
1185 void CFontListSortFilterProxy::setFilterGroup(CGroupListItem *grp)
1186 {
1187     if (grp != m_group) {
1188         //        bool wasNull=!m_group;
1189 
1190         m_group = grp;
1191 
1192         // if(!(wasNull && m_group && CGroupListItem::ALL==m_group->type()))
1193         invalidate();
1194     }
1195 }
1196 
1197 void CFontListSortFilterProxy::setFilterText(const QString &text)
1198 {
1199     if (text != m_filterText) {
1200         //
1201         // If we are filtering on file location, then expand ~ to /home/user, etc.
1202         if (CFontFilter::CRIT_LOCATION == m_filterCriteria && !text.isEmpty() && ('~' == text[0] || '$' == text[0])) {
1203             if ('~' == text[0]) {
1204                 m_filterText = 1 == text.length() ? QDir::homePath() : QString(text).replace(0, 1, QDir::homePath());
1205             } else {
1206                 m_filterText = replaceEnvVar(text);
1207             }
1208         } else {
1209             m_filterText = text;
1210         }
1211 
1212         if (m_filterText.isEmpty()) {
1213             m_timer->stop();
1214             timeout();
1215         } else {
1216             m_timer->start(CFontFilter::CRIT_FONTCONFIG == m_filterCriteria ? 750 : 400);
1217         }
1218     }
1219 }
1220 
1221 void CFontListSortFilterProxy::setFilterCriteria(CFontFilter::ECriteria crit, qulonglong ws, const QStringList &ft)
1222 {
1223     if (crit != m_filterCriteria || ws != m_filterWs || ft != m_filterTypes) {
1224         m_filterWs = ws;
1225         m_filterCriteria = crit;
1226         m_filterTypes = ft;
1227         if (CFontFilter::CRIT_LOCATION == m_filterCriteria) {
1228             setFilterText(m_filterText);
1229         }
1230         m_timer->stop();
1231         timeout();
1232     }
1233 }
1234 
1235 void CFontListSortFilterProxy::timeout()
1236 {
1237     if (CFontFilter::CRIT_FONTCONFIG == m_filterCriteria) {
1238         int commaPos = m_filterText.indexOf(',');
1239         QString query(m_filterText);
1240 
1241         if (-1 != commaPos) {
1242             QString style(query.mid(commaPos + 1));
1243             query.truncate(commaPos);
1244             query = std::move(query).trimmed();
1245             query += ":style=";
1246             style = std::move(style).trimmed();
1247             query += style;
1248         } else {
1249             query = std::move(query).trimmed();
1250         }
1251 
1252         if (!m_fcQuery) {
1253             m_fcQuery = new CFcQuery(this);
1254             connect(m_fcQuery, &CFcQuery::finished, this, &CFontListSortFilterProxy::fcResults);
1255         }
1256 
1257         m_fcQuery->run(query);
1258     } else {
1259         invalidate();
1260         Q_EMIT refresh();
1261     }
1262 }
1263 
1264 void CFontListSortFilterProxy::fcResults()
1265 {
1266     if (CFontFilter::CRIT_FONTCONFIG == m_filterCriteria) {
1267         invalidate();
1268         Q_EMIT refresh();
1269     }
1270 }
1271 
1272 CFontListView::CFontListView(QWidget *parent, CFontList *model)
1273     : QTreeView(parent)
1274     , m_proxy(new CFontListSortFilterProxy(this, model))
1275     , m_model(model)
1276     , m_allowDrops(false)
1277 {
1278     setModel(m_proxy);
1279     m_model = model;
1280     header()->setStretchLastSection(false);
1281     resizeColumnToContents(COL_STATUS);
1282     header()->setSectionResizeMode(COL_STATUS, QHeaderView::Fixed);
1283     header()->setSectionResizeMode(COL_FONT, QHeaderView::Stretch);
1284     setSelectionMode(QAbstractItemView::ExtendedSelection);
1285     setSelectionBehavior(QAbstractItemView::SelectRows);
1286     setSortingEnabled(true);
1287     sortByColumn(COL_FONT, Qt::AscendingOrder);
1288     setAllColumnsShowFocus(true);
1289     setAlternatingRowColors(true);
1290     setAcceptDrops(true);
1291     setDropIndicatorShown(false);
1292     setDragEnabled(true);
1293     setDragDropMode(QAbstractItemView::DragDrop);
1294     header()->setSectionsClickable(true);
1295     header()->setSortIndicatorShown(true);
1296     connect(this, &QTreeView::collapsed, this, &CFontListView::itemCollapsed);
1297     connect(header(), &QHeaderView::sectionClicked, this, &CFontListView::setSortColumn);
1298     connect(m_proxy, &CFontListSortFilterProxy::refresh, this, &CFontListView::refresh);
1299     connect(m_model, &CFontList::listingPercent, this, &CFontListView::listingPercent);
1300 
1301     setWhatsThis(model->whatsThis());
1302     header()->setWhatsThis(whatsThis());
1303     m_menu = new QMenu(this);
1304     m_deleteAct = m_menu->addAction(QIcon::fromTheme("edit-delete"), i18n("Delete"), this, &CFontListView::del);
1305     m_menu->addSeparator();
1306     m_enableAct = m_menu->addAction(QIcon::fromTheme("font-enable"), i18n("Enable"), this, &CFontListView::enable);
1307     m_disableAct = m_menu->addAction(QIcon::fromTheme("font-disable"), i18n("Disable"), this, &CFontListView::disable);
1308     if (!Misc::app(KFI_VIEWER).isEmpty()) {
1309         m_menu->addSeparator();
1310     }
1311     m_printAct = Misc::app(KFI_VIEWER).isEmpty() ? nullptr : m_menu->addAction(QIcon::fromTheme("document-print"), i18n("Print…"), this, &CFontListView::print);
1312     m_viewAct =
1313         Misc::app(KFI_VIEWER).isEmpty() ? nullptr : m_menu->addAction(QIcon::fromTheme("kfontview"), i18n("Open in Font Viewer"), this, &CFontListView::view);
1314     m_menu->addSeparator();
1315     m_menu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload"), model, &CFontList::load);
1316 }
1317 
1318 void CFontListView::getFonts(CJobRunner::ItemList &urls, QStringList &fontNames, QSet<Misc::TFont> *fonts, bool selected, bool getEnabled, bool getDisabled)
1319 {
1320     QModelIndexList selectedItems(selected ? selectedIndexes() : allIndexes());
1321     QSet<CFontItem *> usedFonts;
1322     QModelIndex index;
1323 
1324     foreach (index, selectedItems)
1325         if (index.isValid()) {
1326             QModelIndex realIndex(m_proxy->mapToSource(index));
1327 
1328             if (realIndex.isValid()) {
1329                 if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1330                     CFontItem *font = static_cast<CFontItem *>(realIndex.internalPointer());
1331 
1332                     addFont(font, urls, fontNames, fonts, usedFonts, getEnabled, getDisabled);
1333                 } else {
1334                     CFamilyItem *fam = static_cast<CFamilyItem *>(realIndex.internalPointer());
1335 
1336                     for (int ch = 0; ch < fam->fontCount(); ++ch) {
1337                         QModelIndex child(m_proxy->mapToSource(index.model()->index(ch, 0, index)));
1338 
1339                         if (child.isValid() && (static_cast<CFontModelItem *>(child.internalPointer()))->isFont()) {
1340                             CFontItem *font = static_cast<CFontItem *>(child.internalPointer());
1341 
1342                             addFont(font, urls, fontNames, fonts, usedFonts, getEnabled, getDisabled);
1343                         }
1344                     }
1345                 }
1346             }
1347         }
1348 
1349     fontNames = CFontList::compact(fontNames);
1350 }
1351 
1352 QSet<QString> CFontListView::getFiles()
1353 {
1354     QModelIndexList items(allIndexes());
1355     QModelIndex index;
1356     QSet<QString> files;
1357 
1358     foreach (index, items)
1359         if (index.isValid()) {
1360             QModelIndex realIndex(m_proxy->mapToSource(index));
1361 
1362             if (realIndex.isValid()) {
1363                 if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1364                     CFontItem *font = static_cast<CFontItem *>(realIndex.internalPointer());
1365 
1366                     FileCont::ConstIterator it(font->files().begin()), end(font->files().end());
1367 
1368                     for (; it != end; ++it) {
1369                         QStringList assoc;
1370 
1371                         files.insert((*it).path());
1372                         Misc::getAssociatedFiles((*it).path(), assoc);
1373 
1374                         QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd());
1375 
1376                         for (; ait != aend; ++ait) {
1377                             files.insert(*ait);
1378                         }
1379                     }
1380                 }
1381             }
1382         }
1383 
1384     return files;
1385 }
1386 
1387 void CFontListView::getPrintableFonts(QSet<Misc::TFont> &items, bool selected)
1388 {
1389     QModelIndexList selectedItems(selected ? selectedIndexes() : allIndexes());
1390     QModelIndex index;
1391 
1392     foreach (index, selectedItems) {
1393         CFontItem *font = nullptr;
1394 
1395         if (index.isValid() && 0 == index.column()) {
1396             QModelIndex realIndex(m_proxy->mapToSource(index));
1397 
1398             if (realIndex.isValid()) {
1399                 if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1400                     font = static_cast<CFontItem *>(realIndex.internalPointer());
1401                 } else {
1402                     CFamilyItem *fam = static_cast<CFamilyItem *>(realIndex.internalPointer());
1403                     font = fam->regularFont();
1404                 }
1405             }
1406         }
1407 
1408         if (font && !font->isBitmap() && font->isEnabled()) {
1409             items.insert(Misc::TFont(font->family(), font->styleInfo()));
1410         }
1411     }
1412 }
1413 
1414 void CFontListView::setFilterGroup(CGroupListItem *grp)
1415 {
1416     CGroupListItem *oldGrp(m_proxy->filterGroup());
1417 
1418     m_proxy->setFilterGroup(grp);
1419     m_allowDrops = grp && !grp->isCustom();
1420 
1421     if (!Misc::root()) {
1422         bool refreshStats(false);
1423 
1424         if (!grp || !oldGrp) {
1425             refreshStats = true;
1426         } else {
1427             // Check to see whether we have changed from listing all fonts,
1428             // listing just system or listing personal fonts.
1429             CGroupListItem::EType aType(CGroupListItem::CUSTOM == grp->type() || CGroupListItem::ALL == grp->type()
1430                                                 || CGroupListItem::UNCLASSIFIED == grp->type()
1431                                             ? CGroupListItem::CUSTOM
1432                                             : grp->type()),
1433                 bType(CGroupListItem::CUSTOM == oldGrp->type() || CGroupListItem::ALL == oldGrp->type() || CGroupListItem::UNCLASSIFIED == oldGrp->type()
1434                           ? CGroupListItem::CUSTOM
1435                           : oldGrp->type());
1436             refreshStats = aType != bType;
1437         }
1438 
1439         if (refreshStats) {
1440             m_model->refresh(!grp || !grp->isPersonal(), !grp || !grp->isSystem());
1441         }
1442     }
1443     // when switching groups, for some reason it is not always sorted.
1444     setSortingEnabled(true);
1445 }
1446 
1447 void CFontListView::listingPercent(int percent)
1448 {
1449     // when the font list is first loaded, for some reason it is not always sorted.
1450     // re-enabling sorting here seems to fix the issue - BUG 221610
1451     if (100 == percent) {
1452         setSortingEnabled(true);
1453     }
1454 }
1455 
1456 void CFontListView::refreshFilter()
1457 {
1458     m_proxy->invalidate();
1459 }
1460 
1461 void CFontListView::filterText(const QString &text)
1462 {
1463     m_proxy->setFilterText(text);
1464 }
1465 
1466 void CFontListView::filterCriteria(int crit, qulonglong ws, const QStringList &ft)
1467 {
1468     m_proxy->setFilterCriteria((CFontFilter::ECriteria)crit, ws, ft);
1469 }
1470 
1471 void CFontListView::stats(int &enabled, int &disabled, int &partial)
1472 {
1473     enabled = disabled = partial = 0;
1474 
1475     for (int i = 0; i < m_proxy->rowCount(); ++i) {
1476         QModelIndex idx(m_proxy->index(i, 0, QModelIndex()));
1477 
1478         if (!idx.isValid()) {
1479             break;
1480         }
1481 
1482         QModelIndex sourceIdx(m_proxy->mapToSource(idx));
1483 
1484         if (!sourceIdx.isValid()) {
1485             break;
1486         }
1487 
1488         if ((static_cast<CFontModelItem *>(sourceIdx.internalPointer()))->isFamily()) {
1489             switch ((static_cast<CFamilyItem *>(sourceIdx.internalPointer()))->status()) {
1490             case CFamilyItem::ENABLED:
1491                 enabled++;
1492                 break;
1493             case CFamilyItem::DISABLED:
1494                 disabled++;
1495                 break;
1496             case CFamilyItem::PARTIAL:
1497                 partial++;
1498                 break;
1499             }
1500         }
1501     }
1502 }
1503 
1504 void CFontListView::selectedStatus(bool &enabled, bool &disabled)
1505 {
1506     QModelIndexList selected(selectedIndexes());
1507     QModelIndex index;
1508 
1509     enabled = disabled = false;
1510 
1511     foreach (index, selected) {
1512         QModelIndex realIndex(m_proxy->mapToSource(index));
1513 
1514         if (realIndex.isValid()) {
1515             if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFamily()) {
1516                 switch ((static_cast<CFamilyItem *>(realIndex.internalPointer()))->status()) {
1517                 case CFamilyItem::ENABLED:
1518                     enabled = true;
1519                     break;
1520                 case CFamilyItem::DISABLED:
1521                     disabled = true;
1522                     break;
1523                 case CFamilyItem::PARTIAL:
1524                     enabled = true;
1525                     disabled = true;
1526                     break;
1527                 }
1528             } else {
1529                 if ((static_cast<CFontItem *>(realIndex.internalPointer()))->isEnabled()) {
1530                     enabled = true;
1531                 } else {
1532                     disabled = true;
1533                 }
1534             }
1535         }
1536         if (enabled && disabled) {
1537             break;
1538         }
1539     }
1540 }
1541 
1542 QModelIndexList CFontListView::allFonts()
1543 {
1544     QModelIndexList rv;
1545     int rowCount(m_proxy->rowCount());
1546 
1547     for (int i = 0; i < rowCount; ++i) {
1548         QModelIndex idx(m_proxy->index(i, 0, QModelIndex()));
1549         int childRowCount(m_proxy->rowCount(idx));
1550 
1551         for (int j = 0; j < childRowCount; ++j) {
1552             QModelIndex child(m_proxy->index(j, 0, idx));
1553 
1554             if (child.isValid()) {
1555                 rv.append(m_proxy->mapToSource(child));
1556             }
1557         }
1558     }
1559 
1560     return rv;
1561 }
1562 
1563 void CFontListView::selectFirstFont()
1564 {
1565     if (0 == selectedIndexes().count()) {
1566         for (int i = 0; i < NUM_COLS; ++i) {
1567             QModelIndex idx(m_proxy->index(0, i, QModelIndex()));
1568 
1569             if (idx.isValid()) {
1570                 selectionModel()->select(idx, QItemSelectionModel::Select);
1571             }
1572         }
1573     }
1574 }
1575 
1576 void CFontListView::setSortColumn(int col)
1577 {
1578     if (col != m_proxy->filterKeyColumn()) {
1579         m_proxy->setFilterKeyColumn(col);
1580         m_proxy->invalidate();
1581     }
1582 }
1583 
1584 void CFontListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
1585 {
1586     QAbstractItemView::selectionChanged(selected, deselected);
1587     if (m_model->slowUpdates()) {
1588         return;
1589     }
1590     Q_EMIT itemsSelected(getSelectedItems());
1591 }
1592 
1593 QModelIndexList CFontListView::getSelectedItems()
1594 {
1595     // Go through current selection, and for any 'font' items that are selected,
1596     // ensure 'family' item is not...
1597     QModelIndexList selectedItems(selectedIndexes()), deselectList;
1598     QModelIndex index;
1599     QSet<CFontModelItem *> selectedFamilies;
1600 
1601     foreach (index, selectedItems) {
1602         if (index.isValid()) {
1603             QModelIndex realIndex(m_proxy->mapToSource(index));
1604 
1605             if (realIndex.isValid()) {
1606                 if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1607                     CFontItem *font = static_cast<CFontItem *>(realIndex.internalPointer());
1608 
1609                     if (!selectedFamilies.contains(font->parent())) {
1610                         selectedFamilies.insert(font->parent());
1611 
1612                         for (int i = 0; i < NUM_COLS; ++i) {
1613                             deselectList.append(m_proxy->mapFromSource(m_model->createIndex(font->parent()->rowNumber(), i, font->parent())));
1614                         }
1615                     }
1616                 }
1617             }
1618         }
1619     }
1620 
1621     if (deselectList.count()) {
1622         foreach (index, deselectList) {
1623             selectionModel()->select(index, QItemSelectionModel::Deselect);
1624         }
1625     }
1626 
1627     QModelIndexList sel;
1628     QSet<void *> pointers;
1629     selectedItems = selectedIndexes();
1630     foreach (index, selectedItems) {
1631         QModelIndex idx(m_proxy->mapToSource(index));
1632 
1633         if (!pointers.contains(idx.internalPointer())) {
1634             pointers.insert(idx.internalPointer());
1635             sel.append(idx);
1636         }
1637     }
1638 
1639     return sel;
1640 }
1641 
1642 void CFontListView::itemCollapsed(const QModelIndex &idx)
1643 {
1644     if (idx.isValid()) {
1645         QModelIndex index(m_proxy->mapToSource(idx));
1646 
1647         if (index.isValid() && (static_cast<CFontModelItem *>(index.internalPointer()))->isFamily()) {
1648             CFamilyItem *fam = static_cast<CFamilyItem *>(index.internalPointer());
1649             CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end());
1650 
1651             for (; it != end; ++it) {
1652                 for (int i = 0; i < NUM_COLS; ++i) {
1653                     selectionModel()->select(m_proxy->mapFromSource(m_model->createIndex((*it)->rowNumber(), i, *it)), QItemSelectionModel::Deselect);
1654                 }
1655             }
1656         }
1657     }
1658 }
1659 
1660 static bool isScalable(const QString &str)
1661 {
1662     QByteArray cFile(QFile::encodeName(str));
1663 
1664     return Misc::checkExt(cFile, "ttf") || Misc::checkExt(cFile, "otf") || Misc::checkExt(cFile, "ttc") || Misc::checkExt(cFile, "pfa")
1665         || Misc::checkExt(cFile, "pfb");
1666 }
1667 
1668 void CFontListView::view()
1669 {
1670     // Number of fonts user has selected, before we ask if they really want to view them all...
1671     static const int constMaxBeforePrompt = 10;
1672 
1673     QModelIndexList selectedItems(selectedIndexes());
1674     QModelIndex index;
1675     QSet<CFontItem *> fonts;
1676 
1677     foreach (index, selectedItems) {
1678         QModelIndex realIndex(m_proxy->mapToSource(index));
1679 
1680         if (realIndex.isValid()) {
1681             if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1682                 CFontItem *font(static_cast<CFontItem *>(realIndex.internalPointer()));
1683 
1684                 fonts.insert(font);
1685             } else {
1686                 CFontItem *font((static_cast<CFamilyItem *>(realIndex.internalPointer()))->regularFont());
1687 
1688                 if (font) {
1689                     fonts.insert(font);
1690                 }
1691             }
1692         }
1693     }
1694 
1695     if (fonts.count()
1696         && (fonts.count() < constMaxBeforePrompt
1697             || KMessageBox::PrimaryAction
1698                 == KMessageBox::questionTwoActions(this,
1699                                                    i18n("Open all %1 fonts in font viewer?", fonts.count()),
1700                                                    QString(),
1701                                                    KStandardGuiItem::open(),
1702                                                    KStandardGuiItem::cancel()))) {
1703         QSet<CFontItem *>::ConstIterator it(fonts.begin()), end(fonts.end());
1704         QStringList args;
1705 
1706         for (; it != end; ++it) {
1707             QString file;
1708             int index(0);
1709 
1710             if (!(*it)->isEnabled()) {
1711                 // For a disabled font, we need to find the first scalable font entry in its file list...
1712                 FileCont::ConstIterator fit((*it)->files().begin()), fend((*it)->files().end());
1713 
1714                 for (; fit != fend; ++fit) {
1715                     if (isScalable((*fit).path())) {
1716                         file = (*fit).path();
1717                         index = (*fit).index();
1718                         break;
1719                     }
1720                 }
1721                 if (file.isEmpty()) {
1722                     file = (*it)->fileName();
1723                     index = (*it)->index();
1724                 }
1725             }
1726             args << FC::encode((*it)->family(), (*it)->styleInfo(), file, index).url();
1727         }
1728 
1729         QProcess::startDetached(Misc::app(KFI_VIEWER), args);
1730     }
1731 }
1732 
1733 QModelIndexList CFontListView::allIndexes()
1734 {
1735     QModelIndexList rv;
1736     int rowCount(m_proxy->rowCount());
1737 
1738     for (int i = 0; i < rowCount; ++i) {
1739         QModelIndex idx(m_proxy->index(i, 0, QModelIndex()));
1740         int childRowCount(m_proxy->rowCount(idx));
1741 
1742         rv.append(idx);
1743 
1744         for (int j = 0; j < childRowCount; ++j) {
1745             QModelIndex child(m_proxy->index(j, 0, idx));
1746 
1747             if (child.isValid()) {
1748                 rv.append(child);
1749             }
1750         }
1751     }
1752 
1753     return rv;
1754 }
1755 
1756 void CFontListView::startDrag(Qt::DropActions supportedActions)
1757 {
1758     QModelIndexList indexes(selectedIndexes());
1759 
1760     if (indexes.count()) {
1761         QMimeData *data = model()->mimeData(indexes);
1762         if (!data) {
1763             return;
1764         }
1765 
1766         QModelIndex index(m_proxy->mapToSource(indexes.first()));
1767         const char *icon = "application-x-font-pcf";
1768 
1769         if (index.isValid()) {
1770             CFontItem *font = (static_cast<CFontModelItem *>(index.internalPointer()))->isFont()
1771                 ? static_cast<CFontItem *>(index.internalPointer())
1772                 : (static_cast<CFamilyItem *>(index.internalPointer()))->regularFont();
1773 
1774             if (font && !font->isBitmap()) {
1775                 //                 if("application/x-font-type1"==font->mimetype())
1776                 //                     icon="application-x-font-type1";
1777                 //                 else
1778                 icon = "application-x-font-ttf";
1779             }
1780         }
1781 
1782         QPoint hotspot;
1783         QPixmap pix = QIcon::fromTheme(icon).pixmap(KIconLoader::SizeMedium);
1784 
1785         hotspot.setX(0); // pix.width()/2);
1786         hotspot.setY(0); // pix.height()/2);
1787 
1788         QDrag *drag = new QDrag(this);
1789         drag->setPixmap(pix);
1790         drag->setMimeData(data);
1791         drag->setHotSpot(hotspot);
1792         drag->exec(supportedActions);
1793     }
1794 }
1795 
1796 void CFontListView::dragEnterEvent(QDragEnterEvent *event)
1797 {
1798     if (m_allowDrops && event->mimeData()->hasFormat("text/uri-list")) { // "application/x-kde-urilist" ??
1799         event->acceptProposedAction();
1800     }
1801 }
1802 
1803 void CFontListView::dropEvent(QDropEvent *event)
1804 {
1805     if (m_allowDrops && event->mimeData()->hasFormat("text/uri-list")) {
1806         event->acceptProposedAction();
1807 
1808         QList<QUrl> urls(event->mimeData()->urls());
1809         QList<QUrl>::ConstIterator it(urls.begin()), end(urls.end());
1810         QSet<QUrl> kurls;
1811         QMimeDatabase db;
1812 
1813         for (; it != end; ++it) {
1814             QMimeType mime = db.mimeTypeForUrl(*it);
1815 
1816             foreach (const QString &fontMime, CFontList::fontMimeTypes) {
1817                 if (mime.inherits(fontMime)) {
1818                     kurls.insert(*it);
1819                     break;
1820                 }
1821             }
1822         }
1823 
1824         if (!kurls.isEmpty()) {
1825             Q_EMIT fontsDropped(kurls);
1826         }
1827     }
1828 }
1829 
1830 void CFontListView::contextMenuEvent(QContextMenuEvent *ev)
1831 {
1832     bool valid(indexAt(ev->pos()).isValid());
1833 
1834     m_deleteAct->setEnabled(valid);
1835 
1836     bool en(false), dis(false);
1837     QModelIndexList selectedItems(selectedIndexes());
1838     QModelIndex index;
1839 
1840     foreach (index, selectedItems) {
1841         QModelIndex realIndex(m_proxy->mapToSource(index));
1842 
1843         if (realIndex.isValid()) {
1844             if ((static_cast<CFontModelItem *>(realIndex.internalPointer()))->isFont()) {
1845                 if ((static_cast<CFontItem *>(realIndex.internalPointer())->isEnabled())) {
1846                     en = true;
1847                 } else {
1848                     dis = true;
1849                 }
1850             } else {
1851                 switch ((static_cast<CFamilyItem *>(realIndex.internalPointer()))->status()) {
1852                 case CFamilyItem::ENABLED:
1853                     en = true;
1854                     break;
1855                 case CFamilyItem::DISABLED:
1856                     dis = true;
1857                     break;
1858                 case CFamilyItem::PARTIAL:
1859                     en = dis = true;
1860                     break;
1861                 }
1862             }
1863         }
1864         if (en && dis) {
1865             break;
1866         }
1867     }
1868 
1869     m_enableAct->setEnabled(dis);
1870     m_disableAct->setEnabled(en);
1871     if (m_printAct) {
1872         m_printAct->setEnabled(en | dis);
1873     }
1874     if (m_viewAct) {
1875         m_viewAct->setEnabled(en | dis);
1876     }
1877     m_menu->popup(ev->globalPos());
1878 }
1879 
1880 bool CFontListView::viewportEvent(QEvent *event)
1881 {
1882     executeDelayedItemsLayout();
1883     return QTreeView::viewportEvent(event);
1884 }
1885 
1886 }