Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / kcontrol / kfontinst / lib / DisabledFonts.cpp
blob26d7f8e0160f2e74ca7cfd05cea1a884181b087c
1 /*
2 * KFontInst - KDE Font Installer
4 * Copyright 2003-2007 Craig Drummond <craig@kde.org>
6 * ----
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
24 #include "DisabledFonts.h"
25 #include "Fc.h"
26 #include "Misc.h"
27 #include "KfiConstants.h"
28 #include <QtCore/QDir>
29 #include <QtXml/QDomDocument>
30 #include <QtXml/QDomElement>
31 #include <QtXml/QDomNode>
32 #include <QtCore/QFile>
33 #include <QtCore/QTextStream>
34 #include <KDE/KLockFile>
35 #include <KDE/KSaveFile>
36 #include <KDE/KLocale>
37 #include <KDE/KStandardDirs>
38 #include <fontconfig/fontconfig.h>
39 #include <stdio.h>
41 namespace KFI
44 #define FILE_NAME "disabledfonts"
45 #define DISABLED_DOC "disabledfonts"
46 #define FONT_TAG "font"
47 #define FILE_TAG "file"
48 #define PATH_ATTR "path"
49 #define FOUNDRY_ATTR "foundry"
50 #define FAMILY_ATTR "family"
51 #define WEIGHT_ATTR "weight"
52 #define WIDTH_ATTR "width"
53 #define SLANT_ATTR "slant"
54 #define FACE_ATTR "face"
55 #define LANGS_ATTR "langs"
56 #define LANG_SEP ","
58 static const int constStaleLockTime(5);
59 static const QString constLockExt(".lock");
61 static QString changeName(const QString &f, bool enable)
63 QString file(Misc::getFile(f)),
64 dest;
66 if(enable && Misc::isHidden(file))
67 dest=Misc::getDir(f)+file.mid(1);
68 else if (!enable && !Misc::isHidden(file))
69 dest=Misc::getDir(f)+QChar('.')+file;
70 else
71 dest=f;
72 return dest;
75 static bool changeFileStatus(const QString &f, bool enable)
77 QString dest(changeName(f, enable));
79 if(dest==f) // File is already enabled/disabled
80 return true;
82 if(Misc::fExists(dest) && !Misc::fExists(f)) // File is already enabled/disabled
83 return true;
85 if(0==::rename(QFile::encodeName(f).data(), QFile::encodeName(dest).data()))
87 QStringList files;
89 Misc::getAssociatedFiles(f, files);
91 if(files.count())
93 QStringList::Iterator fIt,
94 fEnd=files.end();
96 for(fIt=files.begin(); fIt!=fEnd; ++fIt)
97 ::rename(QFile::encodeName(*fIt).data(),
98 QFile::encodeName(changeName(*fIt, enable)).data());
100 return true;
102 return false;
105 static bool changeStatus(const CDisabledFonts::TFileList &files, bool enable)
107 QStringList mods;
108 CDisabledFonts::TFileList::ConstIterator it(files.begin()),
109 end(files.end());
111 for(; it!=end; ++it)
112 if(changeFileStatus((*it).path, enable))
113 mods.append((*it).path);
114 else
115 break;
117 if(mods.count()!=files.count())
120 // Failed to enable/disable a file - so need to revert any
121 // previous changes...
122 QStringList::ConstIterator sit(mods.begin()),
123 send(mods.end());
124 for(; sit!=send; ++sit)
125 changeFileStatus(*sit, !enable);
126 return false;
128 return true;
131 CDisabledFonts::LangWritingSystemMap CDisabledFonts::theirLanguageForWritingSystem[]=
133 { QFontDatabase::Latin, (const FcChar8 *)"en" },
134 { QFontDatabase::Greek, (const FcChar8 *)"el" },
135 { QFontDatabase::Cyrillic, (const FcChar8 *)"ru" },
136 { QFontDatabase::Armenian, (const FcChar8 *)"hy" },
137 { QFontDatabase::Hebrew, (const FcChar8 *)"he" },
138 { QFontDatabase::Arabic, (const FcChar8 *)"ar" },
139 { QFontDatabase::Syriac, (const FcChar8 *)"syr" },
140 { QFontDatabase::Thaana, (const FcChar8 *)"div" },
141 { QFontDatabase::Devanagari, (const FcChar8 *)"hi" },
142 { QFontDatabase::Bengali, (const FcChar8 *)"bn" },
143 { QFontDatabase::Gurmukhi, (const FcChar8 *)"pa" },
144 { QFontDatabase::Gujarati, (const FcChar8 *)"gu" },
145 { QFontDatabase::Oriya, (const FcChar8 *)"or" },
146 { QFontDatabase::Tamil, (const FcChar8 *)"ta" },
147 { QFontDatabase::Telugu, (const FcChar8 *)"te" },
148 { QFontDatabase::Kannada, (const FcChar8 *)"kn" },
149 { QFontDatabase::Malayalam, (const FcChar8 *)"ml" },
150 { QFontDatabase::Sinhala, (const FcChar8 *)"si" },
151 { QFontDatabase::Thai, (const FcChar8 *)"th" },
152 { QFontDatabase::Lao, (const FcChar8 *)"lo" },
153 { QFontDatabase::Tibetan, (const FcChar8 *)"bo" },
154 { QFontDatabase::Myanmar, (const FcChar8 *)"my" },
155 { QFontDatabase::Georgian, (const FcChar8 *)"ka" },
156 { QFontDatabase::Khmer, (const FcChar8 *)"km" },
157 { QFontDatabase::SimplifiedChinese, (const FcChar8 *)"zh-cn" },
158 { QFontDatabase::TraditionalChinese, (const FcChar8 *)"zh-tw" },
159 { QFontDatabase::Japanese, (const FcChar8 *)"ja" },
160 { QFontDatabase::Korean, (const FcChar8 *)"ko" },
161 { QFontDatabase::Vietnamese, (const FcChar8 *)"vi" },
162 { QFontDatabase::Other, NULL },
164 // The following is only used to save writing system data for disabled fonts...
165 { QFontDatabase::Telugu, (const FcChar8 *)"Qt-Telugu" },
166 { QFontDatabase::Kannada, (const FcChar8 *)"Qt-Kannada" },
167 { QFontDatabase::Malayalam, (const FcChar8 *)"Qt-Malayalam" },
168 { QFontDatabase::Sinhala, (const FcChar8 *)"Qt-Sinhala" },
169 { QFontDatabase::Myanmar, (const FcChar8 *)"Qt-Myanmar" },
170 { QFontDatabase::Ogham, (const FcChar8 *)"Qt-Ogham" },
171 { QFontDatabase::Runic, (const FcChar8 *)"Qt-Runic" },
173 { QFontDatabase::Any, NULL }
176 // Cache qstring->ws value
178 static QMap<QString, qulonglong> constWritingSystemMap;
180 void CDisabledFonts::createWritingSystemMap()
182 // check if we have created the cache yet...
183 if(constWritingSystemMap.isEmpty())
184 for(int i=0; QFontDatabase::Any!=theirLanguageForWritingSystem[i].ws; ++i)
185 if(theirLanguageForWritingSystem[i].lang)
186 constWritingSystemMap[(const char *)theirLanguageForWritingSystem[i].lang]=
187 ((qulonglong)1)<<theirLanguageForWritingSystem[i].ws;
190 CDisabledFonts::CDisabledFonts(bool sys)
191 : itsTimeStamp(0),
192 itsModified(false),
193 itsMods(0)
195 QString path;
197 createWritingSystemMap();
199 if(Misc::root() || sys)
200 path=KFI_ROOT_CFG_DIR;
201 else
203 path=KGlobal::dirs()->localxdgconfdir();
205 if(!Misc::dExists(path))
206 Misc::createDir(path);
209 itsFileName=path+'/'+FILE_NAME".xml";
211 itsModifiable=Misc::fWritable(itsFileName) ||
212 (!Misc::fExists(itsFileName) && Misc::dWritable(Misc::getDir(itsFileName)));
214 load();
215 if(itsModified)
216 save();
219 void CDisabledFonts::reload()
221 save();
222 itsTimeStamp=0;
223 load();
224 save(); // This will only do a second save, if the 'load' set the modfified flag...
227 bool CDisabledFonts::refresh()
229 time_t ts=Misc::getTimeStamp(itsFileName);
231 if(!ts || ts!=itsTimeStamp)
233 save();
234 load();
235 return true;
237 return false;
241 // Do not always lock during a load, as we may be trying to read global file (but not as root),
242 // or this load might be being called within the save() - so cant lock as is already!
243 void CDisabledFonts::load(bool lock)
245 KLockFile lf(itsFileName+constLockExt);
247 lf.setStaleTime(constStaleLockTime);
248 lock=lock && itsModifiable;
250 if(!lock || KLockFile::LockOK==lf.lock(KLockFile::ForceFlag))
252 time_t ts=Misc::getTimeStamp(itsFileName);
254 if(!ts || ts!=itsTimeStamp)
256 itsTimeStamp=ts;
258 QFile f(itsFileName);
260 if(f.open(QIODevice::ReadOnly))
262 itsFonts.clear();
263 itsModified=false;
265 QDomDocument doc;
267 if(doc.setContent(&f))
268 for(QDomNode n=doc.documentElement().firstChild(); !n.isNull(); n=n.nextSibling())
270 QDomElement e=n.toElement();
272 if(FONT_TAG==e.tagName())
274 TFont font;
276 if(font.load(e, itsModified))
277 itsFonts.add(font);
278 else
279 itsModified=true;
283 f.close();
289 bool CDisabledFonts::save()
291 bool rv(true);
293 if(itsModified)
295 KLockFile lf(itsFileName+constLockExt);
297 lf.setStaleTime(constStaleLockTime);
298 if(KLockFile::LockOK==lf.lock(KLockFile::ForceFlag))
300 time_t ts=Misc::getTimeStamp(itsFileName);
302 if(Misc::fExists(itsFileName) && ts!=itsTimeStamp)
304 // Timestamps differ, so possibly file was modified by another process...
305 merge(CDisabledFonts(*this));
308 KSaveFile file(itsFileName);
310 if(file.open())
312 QTextStream str(&file);
314 str << "<"DISABLED_DOC">" << endl;
316 TFontList::Iterator it(itsFonts.begin()),
317 end(itsFonts.end());
319 for(; it!=end; ++it)
320 str << (*it);
321 str << "</"DISABLED_DOC">" << endl;
322 itsModified=false;
323 file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|
324 QFile::ReadGroup|QFile::ReadOther);
325 rv=file.finalize();
326 if(rv)
328 itsTimeStamp=Misc::getTimeStamp(itsFileName);
329 itsMods=0;
335 return rv;
338 static QString expandHome(const QString &path)
340 QString mpath = path;
341 return !mpath.isEmpty() && '~'==mpath[0]
342 ? 1==mpath.length() ? QDir::homePath() : mpath.replace(0, 1, QDir::homePath())
343 : mpath;
346 bool CDisabledFonts::TFile::load(QDomElement &elem)
348 if(elem.hasAttribute(PATH_ATTR))
350 bool ok(false);
352 path=expandHome(elem.attribute(PATH_ATTR));
353 foundry=elem.attribute(FOUNDRY_ATTR);
355 if(elem.hasAttribute(FACE_ATTR))
356 face=elem.attribute(FACE_ATTR).toInt(&ok);
358 if(!ok || face<0)
359 face=0;
360 return Misc::fExists(path);
363 return false;
366 QString CDisabledFonts::TFileList::toString(bool skipFirst) const
368 QString s;
369 QTextStream str(&s, QIODevice::WriteOnly);
370 ConstIterator it(begin()),
371 e(end());
373 if(skipFirst && it!=e)
374 ++it;
376 for(; it!=e; ++it)
377 str << (*it).path << endl
378 << (*it).foundry << endl
379 << (*it).face << endl;
381 return s;
384 void CDisabledFonts::TFileList::fromString(QString &s)
386 clear();
387 QTextStream str(&s, QIODevice::ReadOnly);
389 while(!str.atEnd())
391 QString path(str.readLine()),
392 foundry(str.readLine()),
393 face(str.readLine());
395 if(face.isEmpty())
396 break;
397 else
398 append(TFile(path, face.toInt(), foundry));
402 bool CDisabledFonts::TFont::load(QDomElement &elem, bool &modified)
404 if(elem.hasAttribute(FAMILY_ATTR))
406 bool ok(false);
407 int weight(KFI_NULL_SETTING), width(KFI_NULL_SETTING), slant(KFI_NULL_SETTING),
408 tmp(KFI_NULL_SETTING);
410 family=elem.attribute(FAMILY_ATTR);
412 if(elem.hasAttribute(WEIGHT_ATTR))
414 tmp=elem.attribute(WEIGHT_ATTR).toInt(&ok);
415 if(ok)
416 weight=tmp;
418 if(elem.hasAttribute(WIDTH_ATTR))
420 tmp=elem.attribute(WIDTH_ATTR).toInt(&ok);
421 if(ok)
422 width=tmp;
425 if(elem.hasAttribute(SLANT_ATTR))
427 tmp=elem.attribute(SLANT_ATTR).toInt(&ok);
428 if(ok)
429 slant=tmp;
432 styleInfo=FC::createStyleVal(weight, width, slant);
434 if(elem.hasAttribute(LANGS_ATTR))
436 QStringList langs(elem.attribute(LANGS_ATTR).split(LANG_SEP, QString::SkipEmptyParts));
438 QStringList::ConstIterator it(langs.begin()),
439 end(langs.end());
441 for(; it!=end; ++it)
442 writingSystems|=constWritingSystemMap[*it];
445 if(elem.hasAttribute(PATH_ATTR))
447 TFile file;
449 if(file.load(elem))
450 files.add(file);
451 else
452 modified=true;
454 else
455 for(QDomNode n=elem.firstChild(); !n.isNull(); n=n.nextSibling())
457 QDomElement ent=n.toElement();
459 if(FILE_TAG==ent.tagName())
461 TFile file;
463 if(file.load(ent))
464 files.add(file);
465 else
466 modified=true;
470 return files.count()>0;
473 return false;
476 const QString & CDisabledFonts::TFont::getName() const
478 if(name.isEmpty())
479 name=FC::createName(family, styleInfo);
480 return name;
483 CDisabledFonts::TFontList::Iterator CDisabledFonts::TFontList::locate(const TFont &t)
485 return find(t);
488 CDisabledFonts::TFontList::Iterator CDisabledFonts::TFontList::locate(const Misc::TFont &t)
490 return locate((TFont &)t);
493 void CDisabledFonts::TFontList::add(const TFont &t) const
495 (const_cast<TFontList *>(this))->insert(t);
498 bool CDisabledFonts::disable(const TFont &font)
500 static const int constMaxMods=100;
502 TFontList::Iterator it=itsFonts.locate(font);
504 if(it==itsFonts.end())
506 TFont newFont(font.family, font.styleInfo, font.writingSystems);
508 if(changeStatus(font.files, false))
510 TFileList::ConstIterator it(font.files.begin()),
511 end(font.files.end());
513 for(; it!=end; ++it)
514 newFont.files.add(TFile(changeName((*it).path, false), (*it).face, (*it).foundry));
516 itsFonts.add(newFont);
517 itsModified=true;
519 if(++itsMods>constMaxMods)
520 save();
521 return true;
523 else
525 TFileList::ConstIterator it(font.files.begin()),
526 end(font.files.end());
528 for(; it!=end; ++it)
530 QString modName(changeName((*it).path, false));
531 if(Misc::fExists(modName))
532 newFont.files.add(TFile(modName, (*it).face, (*it).foundry));
533 else
534 break;
537 if(newFont.files.count()==font.files.count())
539 itsFonts.add(newFont);
540 itsModified=true;
541 if(++itsMods>constMaxMods)
542 save();
543 return true;
547 else
548 return true; // Already disabled...
550 return false;
553 bool CDisabledFonts::enable(TFontList::Iterator font)
555 if(font!=itsFonts.end())
557 if(changeStatus((*font).files, true))
559 itsFonts.erase(font);
560 itsModified=true;
561 return true;
563 else
565 QStringList mod;
566 TFileList::ConstIterator fit((*font).files.begin()),
567 fend((*font).files.end());
569 for(; fit!=fend; ++fit)
571 QString modName(changeName((*fit).path, true));
572 if(Misc::fExists(modName))
573 mod.append((*fit).path);
574 else
575 break;
578 if(mod.count()==(*font).files.count())
580 itsFonts.erase(font);
581 itsModified=true;
582 return true;
586 else
587 return true; // Already enabled...
589 return false;
592 CDisabledFonts::TFontList::Iterator CDisabledFonts::find(const QString &name, int face)
594 TFontList::Iterator it(itsFonts.begin()),
595 end(itsFonts.end());
596 QString fontName(name);
598 if('.'==fontName[0])
599 fontName=fontName.mid(1);
601 for(; it!=end; ++it)
602 if((*it).getName()==fontName)
603 break;
605 if(it==end && '.'==name[0])
606 for(it=itsFonts.begin(); it!=end ; ++it)
608 TFileList::ConstIterator fit((*it).files.begin()),
609 fend((*it).files.end());
611 for(; fit!=fend; ++fit)
612 if(Misc::getFile((*fit).path)==name && (*fit).face==face)
613 return it;
615 return it;
619 // This constrcutor is only used internally, and is called in ::save() when it has been
620 // detected that the file has been modified by another process...
621 CDisabledFonts::CDisabledFonts(const CDisabledFonts &o)
622 : itsFileName(o.itsFileName),
623 itsTimeStamp(o.itsTimeStamp),
624 itsModified(false),
625 itsModifiable(false)
627 load(false);
630 void CDisabledFonts::merge(const CDisabledFonts &other)
632 TFontList::ConstIterator it(other.itsFonts.begin()),
633 end(other.itsFonts.end());
635 for(; it!=end; ++it)
637 TFontList::Iterator existing(itsFonts.locate(*it));
639 if(existing!=itsFonts.end())
641 TFileList::ConstIterator fit((*it).files.begin()),
642 fend((*it).files.end());
644 for(; fit!=fend; ++fit)
645 if(!(*existing).files.contains(*fit))
647 (*existing).files.add(*fit);
648 itsModified=true;
651 else
653 itsFonts.add(*it);
654 itsModified=true;
661 QTextStream & operator<<(QTextStream &s, const KFI::CDisabledFonts::TFile &f)
663 s << PATH_ATTR"=\"" << KFI::Misc::encodeText(KFI::Misc::contractHome(f.path), s) << "\" "
664 << FOUNDRY_ATTR"=\"" << KFI::Misc::encodeText(f.foundry, s) << "\" ";
666 if(f.face>0)
667 s << FACE_ATTR"=\"" << f.face << "\" ";
669 return s;
672 QTextStream & operator<<(QTextStream &s, const KFI::CDisabledFonts::TFont &f)
674 int weight, width, slant;
676 KFI::FC::decomposeStyleVal(f.styleInfo, weight, width, slant);
678 s << " <"FONT_TAG" "FAMILY_ATTR"=\"" << KFI::Misc::encodeText(f.family, s) << "\" ";
680 if(KFI_NULL_SETTING!=weight)
681 s << WEIGHT_ATTR"=\"" << weight << "\" ";
682 if(KFI_NULL_SETTING!=width)
683 s << WIDTH_ATTR"=\"" << width << "\" ";
684 if(KFI_NULL_SETTING!=slant)
685 s << SLANT_ATTR"=\"" << slant << "\" ";
687 QStringList ws;
688 QMap<QString, qulonglong>::ConstIterator wit(KFI::constWritingSystemMap.begin()),
689 wend(KFI::constWritingSystemMap.end());
691 for(; wit!=wend; ++wit)
692 if(f.writingSystems&wit.value())
693 ws+=wit.key();
695 if(ws.count())
696 s << LANGS_ATTR"=\"" << ws.join(LANG_SEP) << "\" ";
698 if(1==f.files.count())
699 s << *(f.files.begin()) << "/>" << endl;
700 else
702 KFI::CDisabledFonts::TFileList::ConstIterator it(f.files.begin()),
703 end(f.files.end());
705 s << '>' << endl;
706 for(; it!=end; ++it)
707 s << " <"FILE_TAG" " << *it << "/>" << endl;
708 s << " </"FONT_TAG">" << endl;
711 return s;