2 * KFontInst - KDE Font Installer
4 * Copyright 2003-2007 Craig Drummond <craig@kde.org>
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"
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>
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"
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
)),
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
;
75 static bool changeFileStatus(const QString
&f
, bool enable
)
77 QString
dest(changeName(f
, enable
));
79 if(dest
==f
) // File is already enabled/disabled
82 if(Misc::fExists(dest
) && !Misc::fExists(f
)) // File is already enabled/disabled
85 if(0==::rename(QFile::encodeName(f
).data(), QFile::encodeName(dest
).data()))
89 Misc::getAssociatedFiles(f
, files
);
93 QStringList::Iterator fIt
,
96 for(fIt
=files
.begin(); fIt
!=fEnd
; ++fIt
)
97 ::rename(QFile::encodeName(*fIt
).data(),
98 QFile::encodeName(changeName(*fIt
, enable
)).data());
105 static bool changeStatus(const CDisabledFonts::TFileList
&files
, bool enable
)
108 CDisabledFonts::TFileList::ConstIterator
it(files
.begin()),
112 if(changeFileStatus((*it
).path
, enable
))
113 mods
.append((*it
).path
);
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()),
124 for(; sit
!=send
; ++sit
)
125 changeFileStatus(*sit
, !enable
);
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
)
197 createWritingSystemMap();
199 if(Misc::root() || sys
)
200 path
=KFI_ROOT_CFG_DIR
;
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
)));
219 void CDisabledFonts::reload()
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
)
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
)
258 QFile
f(itsFileName
);
260 if(f
.open(QIODevice::ReadOnly
))
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())
276 if(font
.load(e
, itsModified
))
289 bool CDisabledFonts::save()
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
);
312 QTextStream
str(&file
);
314 str
<< "<"DISABLED_DOC
">" << endl
;
316 TFontList::Iterator
it(itsFonts
.begin()),
321 str
<< "</"DISABLED_DOC
">" << endl
;
323 file
.setPermissions(QFile::ReadOwner
|QFile::WriteOwner
|
324 QFile::ReadGroup
|QFile::ReadOther
);
328 itsTimeStamp
=Misc::getTimeStamp(itsFileName
);
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())
346 bool CDisabledFonts::TFile::load(QDomElement
&elem
)
348 if(elem
.hasAttribute(PATH_ATTR
))
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
);
360 return Misc::fExists(path
);
366 QString
CDisabledFonts::TFileList::toString(bool skipFirst
) const
369 QTextStream
str(&s
, QIODevice::WriteOnly
);
370 ConstIterator
it(begin()),
373 if(skipFirst
&& it
!=e
)
377 str
<< (*it
).path
<< endl
378 << (*it
).foundry
<< endl
379 << (*it
).face
<< endl
;
384 void CDisabledFonts::TFileList::fromString(QString
&s
)
387 QTextStream
str(&s
, QIODevice::ReadOnly
);
391 QString
path(str
.readLine()),
392 foundry(str
.readLine()),
393 face(str
.readLine());
398 append(TFile(path
, face
.toInt(), foundry
));
402 bool CDisabledFonts::TFont::load(QDomElement
&elem
, bool &modified
)
404 if(elem
.hasAttribute(FAMILY_ATTR
))
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
);
418 if(elem
.hasAttribute(WIDTH_ATTR
))
420 tmp
=elem
.attribute(WIDTH_ATTR
).toInt(&ok
);
425 if(elem
.hasAttribute(SLANT_ATTR
))
427 tmp
=elem
.attribute(SLANT_ATTR
).toInt(&ok
);
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()),
442 writingSystems
|=constWritingSystemMap
[*it
];
445 if(elem
.hasAttribute(PATH_ATTR
))
455 for(QDomNode n
=elem
.firstChild(); !n
.isNull(); n
=n
.nextSibling())
457 QDomElement ent
=n
.toElement();
459 if(FILE_TAG
==ent
.tagName())
470 return files
.count()>0;
476 const QString
& CDisabledFonts::TFont::getName() const
479 name
=FC::createName(family
, styleInfo
);
483 CDisabledFonts::TFontList::Iterator
CDisabledFonts::TFontList::locate(const TFont
&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());
514 newFont
.files
.add(TFile(changeName((*it
).path
, false), (*it
).face
, (*it
).foundry
));
516 itsFonts
.add(newFont
);
519 if(++itsMods
>constMaxMods
)
525 TFileList::ConstIterator
it(font
.files
.begin()),
526 end(font
.files
.end());
530 QString
modName(changeName((*it
).path
, false));
531 if(Misc::fExists(modName
))
532 newFont
.files
.add(TFile(modName
, (*it
).face
, (*it
).foundry
));
537 if(newFont
.files
.count()==font
.files
.count())
539 itsFonts
.add(newFont
);
541 if(++itsMods
>constMaxMods
)
548 return true; // Already disabled...
553 bool CDisabledFonts::enable(TFontList::Iterator font
)
555 if(font
!=itsFonts
.end())
557 if(changeStatus((*font
).files
, true))
559 itsFonts
.erase(font
);
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
);
578 if(mod
.count()==(*font
).files
.count())
580 itsFonts
.erase(font
);
587 return true; // Already enabled...
592 CDisabledFonts::TFontList::Iterator
CDisabledFonts::find(const QString
&name
, int face
)
594 TFontList::Iterator
it(itsFonts
.begin()),
596 QString
fontName(name
);
599 fontName
=fontName
.mid(1);
602 if((*it
).getName()==fontName
)
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
)
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
),
630 void CDisabledFonts::merge(const CDisabledFonts
&other
)
632 TFontList::ConstIterator
it(other
.itsFonts
.begin()),
633 end(other
.itsFonts
.end());
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
);
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) << "\" ";
667 s << FACE_ATTR"=\"" << f.face << "\" ";
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 << "\" ";
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())
696 s << LANGS_ATTR"=\"" << ws.join(LANG_SEP) << "\" ";
698 if(1==f.files.count())
699 s << *(f.files.begin()) << "/>" << endl;
702 KFI::CDisabledFonts::TFileList::ConstIterator it(f.files.begin()),
707 s << " <"FILE_TAG" " << *it << "/>" << endl;
708 s << " </"FONT_TAG">" << endl;