• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDECore

kuitsemantics.cpp

Go to the documentation of this file.
00001 /*  This file is part of the KDE libraries
00002     Copyright (C) 2007 Chusslove Illich <caslav.ilic@gmx.net>
00003 
00004     This library is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU Library General Public
00006     License as published by the Free Software Foundation; either
00007     version 2 of the License, or (at your option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017     Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include <kuitsemantics_p.h>
00021 
00022 #include <config.h>
00023 
00024 #include <QHash>
00025 #include <QSet>
00026 #include <QRegExp>
00027 #include <QStack>
00028 #include <QXmlStreamReader>
00029 #include <QStringList>
00030 #include <QPair>
00031 #include <QDir>
00032 
00033 #include <kdebug.h>
00034 #include <kglobal.h>
00035 #include <kcatalog_p.h>
00036 #include <kuitformats_p.h>
00037 #include <ktranslit_p.h>
00038 #include <kconfiggroup.h>
00039 
00040 // Truncates string, for output of long messages.
00041 // (But don't truncate too much otherwise it's impossible to determine
00042 // which message is faulty if many messages start with the same beginning).
00043 static QString shorten (const QString &str)
00044 {
00045     const int maxlen = 80;
00046     if (str.length() <= maxlen)
00047         return str;
00048     else
00049         return str.left(maxlen).append("...");
00050 }
00051 
00052 // -----------------------------------------------------------------------------
00053 // All the tag, attribute, and context marker element enums.
00054 namespace Kuit {
00055 
00056     namespace Tag { // tag names
00057         typedef enum {
00058             None,
00059             TopLong, TopShort,
00060             Title, Subtitle, Para, List, Item, Note, Warning, Link,
00061             Filename, Application, Command, Resource, Icode, Bcode, Shortcut,
00062             Interface, Emphasis, Placeholder, Email, Numid, Envar, Message, Nl,
00063             NumIntg, NumReal // internal helpers for numbers, not part of DTD
00064         } Var;
00065     }
00066 
00067     namespace Att { // tag attribute names
00068         typedef enum {
00069             None,
00070             Ctx, Url, Address, Section, Label, Strong
00071         } Var;
00072     }
00073 
00074     namespace Rol { // semantic roles
00075         typedef enum {
00076             None,
00077             Action, Title, Option, Label, Item, Info
00078         } Var;
00079     }
00080 
00081     namespace Cue { // interface subcues
00082         typedef enum {
00083             None,
00084             Button, Inmenu, Intoolbar,
00085             Window, Menu, Tab, Group, Column,
00086             Slider, Spinbox, Listbox, Textbox, Chooser,
00087             Check, Radio,
00088             Inlistbox, Intable, Inrange, Intext,
00089             Tooltip, Whatsthis, Status, Progress, Tipoftheday, Credit, Shell
00090         } Var;
00091     }
00092 
00093     namespace Fmt { // visual formats
00094         typedef enum {
00095             None, Plain, Rich, Term
00096         } Var;
00097     }
00098 
00099     typedef Tag::Var TagVar;
00100     typedef Att::Var AttVar;
00101     typedef Rol::Var RolVar;
00102     typedef Cue::Var CueVar;
00103     typedef Fmt::Var FmtVar;
00104 }
00105 
00106 // -----------------------------------------------------------------------------
00107 // All the global data.
00108 
00109 class KuitSemanticsStaticData
00110 {
00111     public:
00112 
00113     QHash<QString, Kuit::TagVar> knownTags;
00114     QHash<QString, Kuit::AttVar> knownAtts;
00115     QHash<QString, Kuit::FmtVar> knownFmts;
00116     QHash<QString, Kuit::RolVar> knownRols;
00117     QHash<QString, Kuit::CueVar> knownCues;
00118 
00119     QHash<Kuit::TagVar, QSet<Kuit::TagVar> > tagSubs;
00120     QHash<Kuit::TagVar, QSet<Kuit::AttVar> > tagAtts;
00121     QHash<Kuit::RolVar, QSet<Kuit::CueVar> > rolCues;
00122 
00123     QHash<Kuit::RolVar, QHash<Kuit::CueVar, Kuit::FmtVar> > defFmts;
00124 
00125     QHash<Kuit::TagVar, QString> tagNames;
00126 
00127     QSet<QString> qtHtmlTagNames;
00128 
00129     QHash<Kuit::TagVar, int> leadingNewlines;
00130 
00131     QHash<QString, QString> xmlEntities;
00132     QHash<QString, QString> xmlEntitiesInverse;
00133 
00134     KuitSemanticsStaticData ();
00135 };
00136 
00137 KuitSemanticsStaticData::KuitSemanticsStaticData ()
00138 {
00139     // Setup known tag names, attributes, and subtags.
00140     // A "lax" version of the DTD.
00141     #undef SETUP_TAG
00142     #define SETUP_TAG(tag, name, atts, subs) do { \
00143         knownTags[name] = Kuit::Tag::tag; \
00144         tagNames[Kuit::Tag::tag] = name; \
00145         { \
00146             using namespace Kuit::Att; \
00147             tagAtts[Kuit::Tag::tag] << atts; \
00148         } \
00149         { \
00150             using namespace Kuit::Tag; \
00151             tagSubs[Kuit::Tag::tag] << subs << NumIntg << NumReal; \
00152         } \
00153     } while (0)
00154 
00155     #undef INLINES
00156     #define INLINES \
00157         Filename << Link << Application << Command << Resource << Icode << \
00158         Shortcut << Interface << Emphasis << Placeholder << Email << \
00159         Numid << Envar << Nl
00160 
00161     SETUP_TAG(TopLong, "kuit", Ctx, Title << Subtitle << Para);
00162     SETUP_TAG(TopShort, "kuil", Ctx, INLINES << Note << Warning << Message);
00163 
00164     SETUP_TAG(Title, "title", None, INLINES);
00165     SETUP_TAG(Subtitle, "subtitle", None, INLINES);
00166     SETUP_TAG(Para, "para", None,
00167               INLINES << Note << Warning << Message << List);
00168     SETUP_TAG(List, "list", None, Item);
00169     SETUP_TAG(Item, "item", None, INLINES << Note << Warning << Message);
00170 
00171     SETUP_TAG(Note, "note", Label, INLINES);
00172     SETUP_TAG(Warning, "warning", Label, INLINES);
00173     SETUP_TAG(Filename, "filename", None, Envar << Placeholder);
00174     SETUP_TAG(Link, "link", Url, None);
00175     SETUP_TAG(Application, "application", None, None);
00176     SETUP_TAG(Command, "command", Section, None);
00177     SETUP_TAG(Resource, "resource", None, None);
00178     SETUP_TAG(Icode, "icode", None, Envar << Placeholder);
00179     SETUP_TAG(Bcode, "bcode", None, None);
00180     SETUP_TAG(Shortcut, "shortcut", None, None);
00181     SETUP_TAG(Interface, "interface", None, None);
00182     SETUP_TAG(Emphasis, "emphasis", Strong, None);
00183     SETUP_TAG(Placeholder, "placeholder", None, None);
00184     SETUP_TAG(Email, "email", Address, None);
00185     SETUP_TAG(Envar, "envar", None, None);
00186     SETUP_TAG(Message, "message", None, None);
00187     SETUP_TAG(Numid, "numid", None, None);
00188     SETUP_TAG(Nl, "nl", None, None);
00189 
00190     SETUP_TAG(NumIntg, KUIT_NUMINTG, None, None); // internal, not part of DTD
00191     SETUP_TAG(NumReal, KUIT_NUMREAL, None, None); // internal, not part of DTD
00192 
00193     // Setup known attribute names.
00194     #undef SETUP_ATT
00195     #define SETUP_ATT(att, name) do { \
00196         knownAtts[name] = Kuit::Att::att; \
00197     } while (0)
00198     SETUP_ATT(Ctx, "ctx");
00199     SETUP_ATT(Url, "url");
00200     SETUP_ATT(Address, "address");
00201     SETUP_ATT(Section, "section");
00202     SETUP_ATT(Label, "label");
00203     SETUP_ATT(Strong, "strong");
00204 
00205     // Setup known format names.
00206     #undef SETUP_FMT
00207     #define SETUP_FMT(fmt, name) do { \
00208         knownFmts[name] = Kuit::Fmt::fmt; \
00209     } while (0)
00210     SETUP_FMT(Plain, "plain");
00211     SETUP_FMT(Rich, "rich");
00212     SETUP_FMT(Term, "term");
00213 
00214     // Setup known role names, their default format and subcues.
00215     #undef SETUP_ROL
00216     #define SETUP_ROL(rol, name, fmt, cues) do { \
00217         knownRols[name] = Kuit::Rol::rol; \
00218         defFmts[Kuit::Rol::rol][Kuit::Cue::None] = Kuit::Fmt::fmt; \
00219         { \
00220             using namespace Kuit::Cue; \
00221             rolCues[Kuit::Rol::rol] << cues; \
00222         } \
00223     } while (0)
00224     SETUP_ROL(Action, "action", Plain,
00225               Button << Inmenu << Intoolbar);
00226     SETUP_ROL(Title, "title", Plain,
00227               Window << Menu << Tab << Group << Column);
00228     SETUP_ROL(Label, "label", Plain,
00229               Slider << Spinbox << Listbox << Textbox << Chooser);
00230     SETUP_ROL(Option, "option", Plain,
00231               Check << Radio);
00232     SETUP_ROL(Item, "item", Plain,
00233               Inmenu << Inlistbox << Intable << Inrange << Intext);
00234     SETUP_ROL(Info, "info", Rich,
00235                  Tooltip << Whatsthis << Kuit::Cue::Status << Progress
00236               << Tipoftheday << Credit << Shell);
00237 
00238     // Setup override formats by subcue.
00239     #undef SETUP_ROLCUEFMT
00240     #define SETUP_ROLCUEFMT(rol, cue, fmt) do { \
00241         defFmts[Kuit::Rol::rol][Kuit::Cue::cue] = Kuit::Fmt::fmt; \
00242     } while (0)
00243     SETUP_ROLCUEFMT(Info, Status, Plain);
00244     SETUP_ROLCUEFMT(Info, Progress, Plain);
00245     SETUP_ROLCUEFMT(Info, Credit, Plain);
00246     SETUP_ROLCUEFMT(Info, Shell, Term);
00247 
00248     // Setup known subcue names.
00249     #undef SETUP_CUE
00250     #define SETUP_CUE(cue, name) do { \
00251         knownCues[name] = Kuit::Cue::cue; \
00252     } while (0)
00253     SETUP_CUE(Button, "button");
00254     SETUP_CUE(Inmenu, "inmenu");
00255     SETUP_CUE(Intoolbar, "intoolbar");
00256     SETUP_CUE(Window, "window");
00257     SETUP_CUE(Menu, "menu");
00258     SETUP_CUE(Tab, "tab");
00259     SETUP_CUE(Group, "group");
00260     SETUP_CUE(Column, "column");
00261     SETUP_CUE(Slider, "slider");
00262     SETUP_CUE(Spinbox, "spinbox");
00263     SETUP_CUE(Listbox, "listbox");
00264     SETUP_CUE(Textbox, "textbox");
00265     SETUP_CUE(Chooser, "chooser");
00266     SETUP_CUE(Check, "check");
00267     SETUP_CUE(Radio, "radio");
00268     SETUP_CUE(Inlistbox, "inlistbox");
00269     SETUP_CUE(Intable, "intable");
00270     SETUP_CUE(Inrange, "inrange");
00271     SETUP_CUE(Intext, "intext");
00272     SETUP_CUE(Tooltip, "tooltip");
00273     SETUP_CUE(Whatsthis, "whatsthis");
00274     SETUP_CUE(Status, "status");
00275     SETUP_CUE(Progress, "progress");
00276     SETUP_CUE(Tipoftheday, "tipoftheday");
00277     SETUP_CUE(Credit, "credit");
00278     SETUP_CUE(Shell, "shell");
00279 
00280     // Collect all Qt's rich text engine HTML tags, for some checks later.
00281     qtHtmlTagNames << "a" << "address" << "b" << "big" << "blockquote"
00282                    << "body" << "br" << "center" << "cita" << "code"
00283                    << "dd" << "dfn" << "div" << "dl" << "dt" << "em"
00284                    << "font" << "h1" << "h2" << "h3" << "h4" << "h5"
00285                    << "h6" << "head" << "hr" << "html" << "i" << "img"
00286                    << "kbd" << "meta" << "li" << "nobr" << "ol" << "p"
00287                    << "pre" << "qt" << "s" << "samp" << "small" << "span"
00288                    << "strong" << "sup" << "sub" << "table" << "tbody"
00289                    << "td" << "tfoot" << "th" << "thead" << "title"
00290                    << "tr" << "tt" << "u" << "ul" << "var";
00291 
00292     // Tags that format with number of leading newlines.
00293     #undef SETUP_TAG_NL
00294     #define SETUP_TAG_NL(tag, nlead) do { \
00295         leadingNewlines[Kuit::Tag::tag] = nlead; \
00296     } while (0)
00297     SETUP_TAG_NL(Title, 2);
00298     SETUP_TAG_NL(Subtitle, 2);
00299     SETUP_TAG_NL(Para, 2);
00300     SETUP_TAG_NL(List, 1);
00301     SETUP_TAG_NL(Bcode, 1);
00302     SETUP_TAG_NL(Item, 1);
00303 
00304     // Known XML entities, direct/inverse mapping.
00305     xmlEntities["lt"] = '<';
00306     xmlEntities["gt"] = '>';
00307     xmlEntities["amp"] = '&';
00308     xmlEntities["apos"] = '\'';
00309     xmlEntities["quot"] = '"';
00310     xmlEntitiesInverse[QString('<')] = "lt";
00311     xmlEntitiesInverse[QString('>')] = "gt";
00312     xmlEntitiesInverse[QString('&')] = "amp";
00313     xmlEntitiesInverse[QString('\'')] = "apos";
00314     xmlEntitiesInverse[QString('"')] = "quot";
00315 }
00316 
00317 K_GLOBAL_STATIC(KuitSemanticsStaticData, semanticsStaticData)
00318 
00319 
00320 // -----------------------------------------------------------------------------
00321 // The KuitSemanticsPrivate methods, they do the work.
00322 
00323 class KuitSemanticsPrivate
00324 {
00325     public:
00326 
00327     KuitSemanticsPrivate (const QString &lang_);
00328 
00329     QString format (const QString &text, const QString &ctxt) const;
00330 
00331     // Get metatranslation (formatting patterns, etc.)
00332     QString metaTr (const char *ctxt, const char *id) const;
00333 
00334     // Set visual formatting patterns for text in semantic tags.
00335     void setFormattingPatterns ();
00336 
00337     // Set data used in transformation of text within semantic tags.
00338     void setTextTransformData ();
00339 
00340     // Compute integer hash key from the set of attributes.
00341     static int attSetKey (const QSet<Kuit::AttVar> &aset = QSet<Kuit::AttVar>());
00342 
00343     // Determine visual format by parsing the context marker.
00344     static Kuit::FmtVar formatFromContextMarker (const QString &ctxmark,
00345                                                  const QString &text);
00346     // Determine visual format by parsing tags.
00347     static Kuit::FmtVar formatFromTags (const QString &text);
00348 
00349     // Apply appropriate top tag is to the text.
00350     static QString equipTopTag (const QString &text, Kuit::TagVar &toptag);
00351 
00352     // Formats the semantic into visual text.
00353     QString semanticToVisualText (const QString &text,
00354                                   Kuit::FmtVar fmtExp,
00355                                   Kuit::FmtVar fmtImp) const;
00356 
00357     // Final touches to the formatted text.
00358     QString finalizeVisualText (const QString &final,
00359                                 Kuit::FmtVar fmt,
00360                                 bool hadQtTag = false,
00361                                 bool hadAnyHtmlTag = false) const;
00362 
00363     // In case of markup errors, try to make result not look too bad.
00364     QString salvageMarkup (const QString &text, Kuit::FmtVar fmt) const;
00365 
00366     // Data for XML parsing state.
00367     class OpenEl
00368     {
00369         public:
00370 
00371         typedef enum { Proper, Ignored, Dropout } Handling;
00372 
00373         Kuit::TagVar tag;
00374         QString name;
00375         QHash<Kuit::AttVar, QString> avals;
00376         int akey;
00377         QString astr;
00378         Handling handling;
00379         QString formattedText;
00380     };
00381 
00382     // Gather data about current element for the parse state.
00383     KuitSemanticsPrivate::OpenEl parseOpenEl (const QXmlStreamReader &xml,
00384                                               Kuit::TagVar etag,
00385                                               const QString &text) const;
00386 
00387     // Select visual pattern for given tag+attributes+format combination.
00388     QString visualPattern (Kuit::TagVar tag, int akey, Kuit::FmtVar fmt) const;
00389 
00390     // Format text of the element.
00391     QString formatSubText (const QString &ptext, const OpenEl &oel,
00392                            Kuit::FmtVar fmt, int numctx) const;
00393 
00394     // Count number of newlines at start and at end of text.
00395     static void countWrappingNewlines (const QString &ptext,
00396                                        int &numle, int &numtr);
00397 
00398     // Modifies text for some tags.
00399     QString modifyTagText (Kuit::TagVar tag, const QString &text,
00400                            int numctx, Kuit::FmtVar fmt) const;
00401 
00402     private:
00403 
00404     QString m_lang;
00405 
00406     QHash<Kuit::TagVar,
00407           QHash<int, // attribute set key
00408                 QHash<Kuit::FmtVar, QString> > > m_patterns;
00409 
00410     QHash<Kuit::FmtVar, QString> m_comboKeyDelim;
00411     QHash<Kuit::FmtVar, QString> m_guiPathDelim;
00412 
00413     QHash<QString, QString> m_keyNames;
00414 
00415     // For fetching metatranslations.
00416     KCatalog *m_metaCat;
00417     KTranslit *m_metaTranslit;
00418     QString m_metaScript;
00419 };
00420 
00421 KuitSemanticsPrivate::KuitSemanticsPrivate (const QString &lang)
00422 : m_metaCat(NULL), m_metaTranslit(NULL)
00423 {
00424     m_lang = lang;
00425 
00426     // NOTE: This function draws translation from raw message catalogs
00427     // because full i18n system is not available at this point (this
00428     // function is called within the initialization of the i18n system),
00429     // Also, pattern/transformation strings are "metastrings", not
00430     // fully proper i18n strings on their own.
00431 
00432     // If this language may be made by transliteration from another,
00433     // look for the catalog in transliteration fallbacks too.
00434     QStringList possibleLangs = KTranslit::fallbackList(lang);
00435     possibleLangs.prepend(lang);
00436     QString realLang = lang;
00437     foreach (const QString& clang, possibleLangs) {
00438         if (!KCatalog::catalogLocaleDir("kdelibs4", clang).isEmpty()) {
00439             realLang = clang;
00440             break;
00441         }
00442     }
00443     m_metaCat = new KCatalog("kdelibs4", realLang);
00444 
00445     // Create transliterator and script to transliterate into.
00446     m_metaTranslit = KTranslit::create(realLang); // may be NULL
00447     int pos = lang.indexOf('@');
00448     if (pos >= 0) {
00449         m_metaScript = lang.mid(pos + 1);
00450     }
00451 
00452     // Fetching of metatranslations prepared, assemble all metadata.
00453 
00454     // Get formatting patterns for all tag/att/fmt combinations.
00455     setFormattingPatterns();
00456 
00457     // Get data for tag text transformations.
00458     setTextTransformData();
00459 
00460     // Catalog and transliterator not needed any more.
00461     delete m_metaCat;
00462     m_metaCat = NULL;
00463     delete m_metaTranslit;
00464     m_metaTranslit = NULL;
00465 }
00466 
00467 QString KuitSemanticsPrivate::metaTr (const char *ctxt, const char *id) const
00468 {
00469     if (m_metaCat == NULL) {
00470         return QString(id);
00471     }
00472     QString meta = m_metaCat->translate(ctxt, id);
00473     if (m_metaTranslit != NULL) {
00474         meta = m_metaTranslit->transliterate(meta, m_metaScript);
00475     }
00476     return meta;
00477 }
00478 
00479 void KuitSemanticsPrivate::setFormattingPatterns ()
00480 {
00481     using namespace Kuit;
00482 
00483     // Macro to expedite setting the patterns.
00484     #undef SET_PATTERN
00485     #define SET_PATTERN(tag, atts, fmt, ctxt_ptrn) do { \
00486         QSet<AttVar> aset; \
00487         aset << atts; \
00488         int akey = attSetKey(aset); \
00489         QString pattern = metaTr(ctxt_ptrn); \
00490         m_patterns[tag][akey][fmt] = pattern; \
00491         /* Make Term pattern same as Plain, unless explicitly given. */ \
00492         if (fmt == Fmt::Plain && !m_patterns[tag][akey].contains(Fmt::Term)) { \
00493             m_patterns[tag][akey][Fmt::Term] = pattern; \
00494         } \
00495     } while (0)
00496 
00497     // Normal I18N_NOOP2 removes context, but below we need both.
00498     #undef I18N_NOOP2
00499     #define I18N_NOOP2(ctxt, msg) ctxt, msg
00500 
00501     // Some of the formatting patterns are intentionally not exposed for
00502     // localization.
00503     #undef XXXX_NOOP2
00504     #define XXXX_NOOP2(ctxt, msg) ctxt, msg
00505 
00506     // NOTE: The following "i18n:" comments are oddly placed in order that
00507     // xgettext extracts them properly.
00508 
00509     // -------> Title
00510     SET_PATTERN(Tag::Title, Att::None, Fmt::Plain,
00511                 I18N_NOOP2("@title/plain",
00512     // i18n: The following messages, with msgctxt "@tag/modifier",
00513     // are KUIT patterns for formatting the text found inside semantic tags.
00514     // For review of the KUIT semantic markup, see the article on Techbase:
00515     // http://techbase.kde.org/Development/Tutorials/Localization/i18n_Semantics
00516     // The "/modifier" tells if the pattern is used for plain text, or rich text
00517     // which can use HTML tags.
00518     // You may be in general satisfied with the patterns as they are in the
00519     // original. Some things you may think about changing:
00520     // - the proper quotes, those used in msgid are English-standard
00521     // - the <i> and <b> tags, does your language script work well with them?
00522                            "== %1 =="));
00523     SET_PATTERN(Tag::Title, Att::None, Fmt::Rich,
00524                 I18N_NOOP2("@title/rich",
00525     // i18n: KUIT pattern, see the comment to the first of these entries above.
00526                            "<h2>%1</h2>"));
00527 
00528     // -------> Subtitle
00529     SET_PATTERN(Tag::Subtitle, Att::None, Fmt::Plain,
00530                 I18N_NOOP2("@subtitle/plain",
00531     // i18n: KUIT pattern, see the comment to the first of these entries above.
00532                            "~ %1 ~"));
00533     SET_PATTERN(Tag::Subtitle, Att::None, Fmt::Rich,
00534                 I18N_NOOP2("@subtitle/rich",
00535     // i18n: KUIT pattern, see the comment to the first of these entries above.
00536                            "<h3>%1</h3>"));
00537 
00538     // -------> Para
00539     SET_PATTERN(Tag::Para, Att::None, Fmt::Plain,
00540                 XXXX_NOOP2("@para/plain",
00541     // i18n: KUIT pattern, see the comment to the first of these entries above.
00542                            "%1"));
00543     SET_PATTERN(Tag::Para, Att::None, Fmt::Rich,
00544                 XXXX_NOOP2("@para/rich",
00545     // i18n: KUIT pattern, see the comment to the first of these entries above.
00546                            "<p>%1</p>"));
00547 
00548     // -------> List
00549     SET_PATTERN(Tag::List, Att::None, Fmt::Plain,
00550                 XXXX_NOOP2("@list/plain",
00551     // i18n: KUIT pattern, see the comment to the first of these entries above.
00552                            "%1"));
00553     SET_PATTERN(Tag::List, Att::None, Fmt::Rich,
00554                 XXXX_NOOP2("@list/rich",
00555     // i18n: KUIT pattern, see the comment to the first of these entries above.
00556                            "<ul>%1</ul>"));
00557 
00558     // -------> Item
00559     SET_PATTERN(Tag::Item, Att::None, Fmt::Plain,
00560                 I18N_NOOP2("@item/plain",
00561     // i18n: KUIT pattern, see the comment to the first of these entries above.
00562                            "  * %1"));
00563     SET_PATTERN(Tag::Item, Att::None, Fmt::Rich,
00564                 I18N_NOOP2("@item/rich",
00565     // i18n: KUIT pattern, see the comment to the first of these entries above.
00566                            "<li>%1</li>"));
00567 
00568     // -------> Note
00569     SET_PATTERN(Tag::Note, Att::None, Fmt::Plain,
00570                 I18N_NOOP2("@note/plain",
00571     // i18n: KUIT pattern, see the comment to the first of these entries above.
00572                            "Note: %1"));
00573     SET_PATTERN(Tag::Note, Att::None, Fmt::Rich,
00574                 I18N_NOOP2("@note/rich",
00575     // i18n: KUIT pattern, see the comment to the first of these entries above.
00576                            "<i>Note</i>: %1"));
00577     SET_PATTERN(Tag::Note, Att::Label, Fmt::Plain,
00578                 I18N_NOOP2("@note-with-label/plain\n"
00579                            "%1 is the note label, %2 is the text",
00580     // i18n: KUIT pattern, see the comment to the first of these entries above.
00581                            "%1: %2"));
00582     SET_PATTERN(Tag::Note, Att::Label, Fmt::Rich,
00583                 I18N_NOOP2("@note-with-label/rich\n"
00584                            "%1 is the note label, %2 is the text",
00585     // i18n: KUIT pattern, see the comment to the first of these entries above.
00586                            "<i>%1</i>: %2"));
00587 
00588     // -------> Warning
00589     SET_PATTERN(Tag::Warning, Att::None, Fmt::Plain,
00590                 I18N_NOOP2("@warning/plain",
00591     // i18n: KUIT pattern, see the comment to the first of these entries above.
00592                            "WARNING: %1"));
00593     SET_PATTERN(Tag::Warning, Att::None, Fmt::Rich,
00594                 I18N_NOOP2("@warning/rich",
00595     // i18n: KUIT pattern, see the comment to the first of these entries above.
00596                            "<b>Warning</b>: %1"));
00597     SET_PATTERN(Tag::Warning, Att::Label, Fmt::Plain,
00598                 I18N_NOOP2("@warning-with-label/plain\n"
00599                            "%1 is the warning label, %2 is the text",
00600     // i18n: KUIT pattern, see the comment to the first of these entries above.
00601                            "%1: %2"));
00602     SET_PATTERN(Tag::Warning, Att::Label, Fmt::Rich,
00603                 I18N_NOOP2("@warning-with-label/rich\n"
00604                            "%1 is the warning label, %2 is the text",
00605     // i18n: KUIT pattern, see the comment to the first of these entries above.
00606                            "<b>%1</b>: %2"));
00607 
00608     // -------> Link
00609     SET_PATTERN(Tag::Link, Att::None, Fmt::Plain,
00610                 XXXX_NOOP2("@link/plain",
00611     // i18n: KUIT pattern, see the comment to the first of these entries above.
00612                            "%1"));
00613     SET_PATTERN(Tag::Link, Att::None, Fmt::Rich,
00614                 XXXX_NOOP2("@link/rich",
00615     // i18n: KUIT pattern, see the comment to the first of these entries above.
00616                            "<a href=\"%1\">%1</a>"));
00617     SET_PATTERN(Tag::Link, Att::Url, Fmt::Plain,
00618                 I18N_NOOP2("@link-with-description/plain\n"
00619                            "%1 is the URL, %2 is the descriptive text",
00620     // i18n: KUIT pattern, see the comment to the first of these entries above.
00621                            "%2 (%1)"));
00622     SET_PATTERN(Tag::Link, Att::Url, Fmt::Rich,
00623                 I18N_NOOP2("@link-with-description/rich\n"
00624                            "%1 is the URL, %2 is the descriptive text",
00625     // i18n: KUIT pattern, see the comment to the first of these entries above.
00626                            "<a href=\"%1\">%2</a>"));
00627 
00628     // -------> Filename
00629     SET_PATTERN(Tag::Filename, Att::None, Fmt::Plain,
00630                 I18N_NOOP2("@filename/plain",
00631     // i18n: KUIT pattern, see the comment to the first of these entries above.
00632                            "‘%1’"));
00633     SET_PATTERN(Tag::Filename, Att::None, Fmt::Rich,
00634                 I18N_NOOP2("@filename/rich",
00635     // i18n: KUIT pattern, see the comment to the first of these entries above.
00636                            "<tt>%1</tt>"));
00637 
00638     // -------> Application
00639     SET_PATTERN(Tag::Application, Att::None, Fmt::Plain,
00640                 I18N_NOOP2("@application/plain",
00641     // i18n: KUIT pattern, see the comment to the first of these entries above.
00642                            "%1"));
00643     SET_PATTERN(Tag::Application, Att::None, Fmt::Rich,
00644                 I18N_NOOP2("@application/rich",
00645     // i18n: KUIT pattern, see the comment to the first of these entries above.
00646                            "%1"));
00647 
00648     // -------> Command
00649     SET_PATTERN(Tag::Command, Att::None, Fmt::Plain,
00650                 I18N_NOOP2("@command/plain",
00651     // i18n: KUIT pattern, see the comment to the first of these entries above.
00652                            "%1"));
00653     SET_PATTERN(Tag::Command, Att::None, Fmt::Rich,
00654                 I18N_NOOP2("@command/rich",
00655     // i18n: KUIT pattern, see the comment to the first of these entries above.
00656                            "<tt>%1</tt>"));
00657     SET_PATTERN(Tag::Command, Att::Section, Fmt::Plain,
00658                 I18N_NOOP2("@command-with-section/plain\n"
00659                            "%1 is the command name, %2 is its man section",
00660     // i18n: KUIT pattern, see the comment to the first of these entries above.
00661                            "%1(%2)"));
00662     SET_PATTERN(Tag::Command, Att::Section, Fmt::Rich,
00663                 I18N_NOOP2("@command-with-section/rich\n"
00664                            "%1 is the command name, %2 is its man section",
00665     // i18n: KUIT pattern, see the comment to the first of these entries above.
00666                            "<tt>%1(%2)</tt>"));
00667 
00668     // -------> Resource
00669     SET_PATTERN(Tag::Resource, Att::None, Fmt::Plain,
00670                 I18N_NOOP2("@resource/plain",
00671     // i18n: KUIT pattern, see the comment to the first of these entries above.
00672                            "“%1”"));
00673     SET_PATTERN(Tag::Resource, Att::None, Fmt::Rich,
00674                 I18N_NOOP2("@resource/rich",
00675     // i18n: KUIT pattern, see the comment to the first of these entries above.
00676                            "“%1”"));
00677 
00678     // -------> Icode
00679     SET_PATTERN(Tag::Icode, Att::None, Fmt::Plain,
00680                 I18N_NOOP2("@icode/plain",
00681     // i18n: KUIT pattern, see the comment to the first of these entries above.
00682                            "“%1”"));
00683     SET_PATTERN(Tag::Icode, Att::None, Fmt::Rich,
00684                 I18N_NOOP2("@icode/rich",
00685     // i18n: KUIT pattern, see the comment to the first of these entries above.
00686                            "<tt>%1</tt>"));
00687 
00688     // -------> Bcode
00689     SET_PATTERN(Tag::Bcode, Att::None, Fmt::Plain,
00690                 XXXX_NOOP2("@bcode/plain",
00691     // i18n: KUIT pattern, see the comment to the first of these entries above.
00692                            "\n%1\n"));
00693     SET_PATTERN(Tag::Bcode, Att::None, Fmt::Rich,
00694                 XXXX_NOOP2("@bcode/rich",
00695     // i18n: KUIT pattern, see the comment to the first of these entries above.
00696                            "<pre>%1</pre>"));
00697 
00698     // -------> Shortcut
00699     SET_PATTERN(Tag::Shortcut, Att::None, Fmt::Plain,
00700                 I18N_NOOP2("@shortcut/plain",
00701     // i18n: KUIT pattern, see the comment to the first of these entries above.
00702                            "%1"));
00703     SET_PATTERN(Tag::Shortcut, Att::None, Fmt::Rich,
00704                 I18N_NOOP2("@shortcut/rich",
00705     // i18n: KUIT pattern, see the comment to the first of these entries above.
00706                            "<b>%1</b>"));
00707 
00708     // -------> Interface
00709     SET_PATTERN(Tag::Interface, Att::None, Fmt::Plain,
00710                 I18N_NOOP2("@interface/plain",
00711     // i18n: KUIT pattern, see the comment to the first of these entries above.
00712                            "|%1|"));
00713     SET_PATTERN(Tag::Interface, Att::None, Fmt::Rich,
00714                 I18N_NOOP2("@interface/rich",
00715     // i18n: KUIT pattern, see the comment to the first of these entries above.
00716                            "<i>%1</i>"));
00717 
00718     // -------> Emphasis
00719     SET_PATTERN(Tag::Emphasis, Att::None, Fmt::Plain,
00720                 I18N_NOOP2("@emphasis/plain",
00721     // i18n: KUIT pattern, see the comment to the first of these entries above.
00722                            "*%1*"));
00723     SET_PATTERN(Tag::Emphasis, Att::None, Fmt::Rich,
00724                 I18N_NOOP2("@emphasis/rich",
00725     // i18n: KUIT pattern, see the comment to the first of these entries above.
00726                            "<i>%1</i>"));
00727     SET_PATTERN(Tag::Emphasis, Att::Strong, Fmt::Plain,
00728                 I18N_NOOP2("@emphasis-strong/plain",
00729     // i18n: KUIT pattern, see the comment to the first of these entries above.
00730                            "**%1**"));
00731     SET_PATTERN(Tag::Emphasis, Att::Strong, Fmt::Rich,
00732                 I18N_NOOP2("@emphasis-strong/rich",
00733     // i18n: KUIT pattern, see the comment to the first of these entries above.
00734                            "<b>%1</b>"));
00735 
00736     // -------> Placeholder
00737     SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Plain,
00738                 I18N_NOOP2("@placeholder/plain",
00739     // i18n: KUIT pattern, see the comment to the first of these entries above.
00740                            "&lt;%1&gt;"));
00741     SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Rich,
00742                 I18N_NOOP2("@placeholder/rich",
00743     // i18n: KUIT pattern, see the comment to the first of these entries above.
00744                            "&lt;<i>%1</i>&gt;"));
00745 
00746     // -------> Email
00747     SET_PATTERN(Tag::Email, Att::None, Fmt::Plain,
00748                 I18N_NOOP2("@email/plain",
00749     // i18n: KUIT pattern, see the comment to the first of these entries above.
00750                            "&lt;%1&gt;"));
00751     SET_PATTERN(Tag::Email, Att::None, Fmt::Rich,
00752                 I18N_NOOP2("@email/rich",
00753     // i18n: KUIT pattern, see the comment to the first of these entries above.
00754                            "&lt;<a href=\"mailto:%1\">%1</a>&gt;"));
00755     SET_PATTERN(Tag::Email, Att::Address, Fmt::Plain,
00756                 I18N_NOOP2("@email-with-name/plain\n"
00757                            "%1 is name, %2 is address",
00758     // i18n: KUIT pattern, see the comment to the first of these entries above.
00759                            "%1 &lt;%2&gt;"));
00760     SET_PATTERN(Tag::Email, Att::Address, Fmt::Rich,
00761                 I18N_NOOP2("@email-with-name/rich\n"
00762                            "%1 is name, %2 is address",
00763     // i18n: KUIT pattern, see the comment to the first of these entries above.
00764                            "<a href=\"mailto:%2\">%1</a>"));
00765 
00766     // -------> Envar
00767     SET_PATTERN(Tag::Envar, Att::None, Fmt::Plain,
00768                 I18N_NOOP2("@envar/plain",
00769     // i18n: KUIT pattern, see the comment to the first of these entries above.
00770                            "$%1"));
00771     SET_PATTERN(Tag::Envar, Att::None, Fmt::Rich,
00772                 I18N_NOOP2("@envar/rich",
00773     // i18n: KUIT pattern, see the comment to the first of these entries above.
00774                            "<tt>$%1</tt>"));
00775 
00776     // -------> Message
00777     SET_PATTERN(Tag::Message, Att::None, Fmt::Plain,
00778                 I18N_NOOP2("@message/plain",
00779     // i18n: KUIT pattern, see the comment to the first of these entries above.
00780                            "/%1/"));
00781     SET_PATTERN(Tag::Message, Att::None, Fmt::Rich,
00782                 I18N_NOOP2("@message/rich",
00783     // i18n: KUIT pattern, see the comment to the first of these entries above.
00784                            "<i>%1</i>"));
00785 
00786     // -------> Nl
00787     SET_PATTERN(Tag::Nl, Att::None, Fmt::Plain,
00788                 XXXX_NOOP2("@nl/plain",
00789     // i18n: KUIT pattern, see the comment to the first of these entries above.
00790                            "%1\n"));
00791     SET_PATTERN(Tag::Nl, Att::None, Fmt::Rich,
00792                 XXXX_NOOP2("@nl/rich",
00793     // i18n: KUIT pattern, see the comment to the first of these entries above.
00794                            "%1<br/>"));
00795 }
00796 
00797 void KuitSemanticsPrivate::setTextTransformData ()
00798 {
00799     // Mask metaTr with I18N_NOOP2 to have stuff extracted.
00800     #undef I18N_NOOP2
00801     #define I18N_NOOP2(ctxt, msg) metaTr(ctxt, msg)
00802 
00803     // i18n: Decide which string is used to delimit keys in a keyboard
00804     // shortcut (e.g. + in Ctrl+Alt+Tab) in plain text.
00805     m_comboKeyDelim[Kuit::Fmt::Plain] = I18N_NOOP2("shortcut-key-delimiter/plain", "+");
00806     m_comboKeyDelim[Kuit::Fmt::Term] = m_comboKeyDelim[Kuit::Fmt::Plain];
00807     // i18n: Decide which string is used to delimit keys in a keyboard
00808     // shortcut (e.g. + in Ctrl+Alt+Tab) in rich text.
00809     m_comboKeyDelim[Kuit::Fmt::Rich] = I18N_NOOP2("shortcut-key-delimiter/rich", "+");
00810 
00811     // i18n: Decide which string is used to delimit elements in a GUI path
00812     // (e.g. -> in "Go to Settings->Advanced->Core tab.") in plain text.
00813     m_guiPathDelim[Kuit::Fmt::Plain] = I18N_NOOP2("gui-path-delimiter/plain", "→");
00814     m_guiPathDelim[Kuit::Fmt::Term] = m_guiPathDelim[Kuit::Fmt::Plain];
00815     // i18n: Decide which string is used to delimit elements in a GUI path
00816     // (e.g. -> in "Go to Settings->Advanced->Core tab.") in rich text.
00817     m_guiPathDelim[Kuit::Fmt::Rich] = I18N_NOOP2("gui-path-delimiter/rich", "→");
00818     // NOTE: The '→' glyph seems to be available in all widespread fonts.
00819 
00820     // Collect keyboard key names.
00821     #undef SET_KEYNAME
00822     #define SET_KEYNAME(rawname) do { \
00823         /* Normalize key, trim and all lower-case. */ \
00824         QString normname = QString(rawname).trimmed().toLower(); \
00825         m_keyNames[normname] = metaTr("keyboard-key-name", rawname); \
00826     } while (0)
00827 
00828     // Now we need I18N_NOOP2 that does remove context.
00829     #undef I18N_NOOP2
00830     #define I18N_NOOP2(ctxt, msg) msg
00831 
00832     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Alt"));
00833     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "AltGr"));
00834     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Backspace"));
00835     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "CapsLock"));
00836     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Control"));
00837     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Ctrl"));
00838     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Del"));
00839     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Delete"));
00840     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Down"));
00841     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "End"));
00842     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Enter"));
00843     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Esc"));
00844     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Escape"));
00845     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Home"));
00846     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Hyper"));
00847     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Ins"));
00848     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Insert"));
00849     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Left"));
00850     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Menu"));
00851     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Meta"));
00852     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "NumLock"));
00853     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PageDown"));
00854     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PageUp"));
00855     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PgDown"));
00856     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PgUp"));
00857     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PauseBreak"));
00858     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PrintScreen"));
00859     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PrtScr"));
00860     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Return"));
00861     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Right"));
00862     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "ScrollLock"));
00863     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Shift"));
00864     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Space"));
00865     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Super"));
00866     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "SysReq"));
00867     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Tab"));
00868     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Up"));
00869     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Win"));
00870     // TODO: Add rest of the key names?
00871 
00872     // i18n: Pattern for the function keys.
00873     SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "F%1"));
00874 }
00875 
00876 QString KuitSemanticsPrivate::format (const QString &text,
00877                                       const QString &ctxt) const
00878 {
00879     // Parse context marker to determine format.
00880     Kuit::FmtVar fmtExplicit = formatFromContextMarker(ctxt, text);
00881 
00882     // Quick check: are there any tags at all?
00883     if (text.indexOf('<') < 0) {
00884         return finalizeVisualText(text, fmtExplicit);
00885     }
00886 
00887     // If format not explicitly given, heuristically determine
00888     // implicit format based on presence or lack of HTML tags.
00889     Kuit::FmtVar fmtImplicit = fmtExplicit;
00890     if (fmtExplicit == Kuit::Fmt::None) {
00891         fmtImplicit = formatFromTags(text);
00892     }
00893 
00894     // Decide on the top tag, either TopLong or TopShort,
00895     // and wrap the text with it.
00896     Kuit::TagVar toptag;
00897     QString wtext = equipTopTag(text, toptag);
00898 
00899     // Format the text.
00900     QString ftext = semanticToVisualText(wtext, fmtExplicit, fmtImplicit);
00901     if (ftext.isEmpty()) { // error while processing markup
00902         return salvageMarkup(text, fmtImplicit);
00903     }
00904 
00905     return ftext;
00906 }
00907 
00908 int KuitSemanticsPrivate::attSetKey (const QSet<Kuit::AttVar> &aset)
00909 {
00910     QList<Kuit::AttVar> alist = aset.toList();
00911     qSort(alist);
00912     int key = 0;
00913     int tenp = 1;
00914     foreach (const Kuit::AttVar &att, alist) {
00915         key += att * tenp;
00916         tenp *= 10;
00917     }
00918     return key;
00919 }
00920 
00921 Kuit::FmtVar KuitSemanticsPrivate::formatFromContextMarker (
00922     const QString &ctxmark_, const QString &text)
00923 {
00924     #ifdef NDEBUG
00925     Q_UNUSED(text);
00926     #endif
00927 
00928     KuitSemanticsStaticData *s = semanticsStaticData;
00929 
00930     // Semantic context marker is in the form @rolname:cuename/fmtname,
00931     // and must start just after any leading whitespace in the context string.
00932     QString rolname;
00933     QString fmtname;
00934     QString cuename;
00935     QString ctxmark = ctxmark_.trimmed();
00936     if (ctxmark.startsWith('@')) { // found context marker
00937         static QRegExp wsRx("\\s");
00938         ctxmark = ctxmark.mid(1, wsRx.indexIn(ctxmark) - 1);
00939 
00940         // Possible visual format.
00941         int pfmt = ctxmark.indexOf('/');
00942         if (pfmt >= 0) {
00943             fmtname = ctxmark.mid(pfmt + 1);
00944             ctxmark = ctxmark.left(pfmt);
00945         }
00946 
00947         // Possible interface subcue.
00948         int pcue = ctxmark.indexOf(':');
00949         if (pcue >= 0) {
00950             cuename = ctxmark.mid(pcue + 1);
00951             ctxmark = ctxmark.left(pcue);
00952         }
00953 
00954         // Semantic role.
00955         rolname = ctxmark;
00956     }
00957     // Names remain empty if marker was not found, which is ok.
00958 
00959     // Normalize names.
00960     rolname = rolname.trimmed().toLower();
00961     cuename = cuename.trimmed().toLower();
00962     fmtname = fmtname.trimmed().toLower();
00963 
00964     // Set role from name.
00965     Kuit::RolVar rol;
00966     if (s->knownRols.contains(rolname)) { // known role
00967         rol = s->knownRols[rolname];
00968     }
00969     else { // unknown role
00970         rol = Kuit::Rol::None;
00971         if (!rolname.isEmpty()) {
00972             kDebug(173) << QString("Unknown semantic role '@%1' in "
00973                                    "context marker for message {%2}.")
00974                                   .arg(rolname, shorten(text));
00975         }
00976     }
00977 
00978     // Set subcue from name.
00979     Kuit::CueVar cue;
00980     if (s->knownCues.contains(cuename)) { // known subcue
00981         cue = s->knownCues[cuename];
00982     }
00983     else { // unknown or not given subcue
00984         cue = Kuit::Cue::None;
00985         if (!cuename.isEmpty()) {
00986             kDebug(173) << QString("Unknown interface subcue ':%1' in "
00987                                    "context marker for message {%2}.")
00988                                   .arg(cuename, shorten(text));
00989         }
00990     }
00991 
00992     // Set format from name, or by derivation from contex/subcue.
00993     Kuit::FmtVar fmt;
00994     if (s->knownFmts.contains(fmtname)) { // known format
00995         fmt = s->knownFmts[fmtname];
00996     }
00997     else { // unknown or not given format
00998 
00999         // Check first if there is a format defined for role/subcue
01000         // combination, than for role only, then default to none.
01001         if (s->defFmts.contains(rol)) {
01002             if (s->defFmts[rol].contains(cue)) {
01003                 fmt = s->defFmts[rol][cue];
01004             }
01005             else {
01006                 fmt = s->defFmts[rol][Kuit::Cue::None];
01007             }
01008         }
01009         else {
01010             fmt = Kuit::Fmt::None;
01011         }
01012 
01013         if (!fmtname.isEmpty()) {
01014             kDebug(173) << QString("Unknown visual format '/%1' in "
01015                                    "context marker for message {%2}.")
01016                                   .arg(fmtname, shorten(text));
01017         }
01018     }
01019 
01020     return fmt;
01021 }
01022 
01023 Kuit::FmtVar KuitSemanticsPrivate::formatFromTags (const QString &text)
01024 {
01025     KuitSemanticsStaticData *s = semanticsStaticData;
01026     static QRegExp staticTagRx("<\\s*(\\w+)[^>]*>");
01027 
01028     QRegExp tagRx = staticTagRx; // for thread-safety
01029     int p = tagRx.indexIn(text);
01030     while (p >= 0) {
01031         QString tagname = tagRx.capturedTexts().at(1).toLower();
01032         if (s->qtHtmlTagNames.contains(tagname)) {
01033             return Kuit::Fmt::Rich;
01034         }
01035         p = tagRx.indexIn(text, p + tagRx.matchedLength());
01036     }
01037     return Kuit::Fmt::Plain;
01038 }
01039 
01040 QString KuitSemanticsPrivate::equipTopTag (const QString &text_,
01041                                            Kuit::TagVar &toptag)
01042 {
01043     KuitSemanticsStaticData *s = semanticsStaticData;
01044 
01045     // Unless the text opens either with TopLong or TopShort tags,
01046     // make a guess: if it opens with one of Title, Subtitle, Para,
01047     // consider it TopLong, otherwise TopShort.
01048     static QRegExp opensWithTagRx("^\\s*<\\s*(\\w+)[^>]*>");
01049     bool explicitTopTag = false;
01050 
01051     QString text = text_;
01052     int p = opensWithTagRx.indexIn(text);
01053 
01054     // <qt> or <html> tag are to be ignored for deciding the top tag.
01055     if (p >= 0) {
01056         QString fullmatch = opensWithTagRx.capturedTexts().at(0);
01057         QString tagname = opensWithTagRx.capturedTexts().at(1).toLower();
01058         if (tagname == "qt" || tagname == "html") {
01059             // Kill the tag and see if there is another one following,
01060             // for primary check below.
01061             text = text.mid(fullmatch.length());
01062             p = opensWithTagRx.indexIn(text);
01063         }
01064     }
01065 
01066     // Check the first non-<qt>/<html> tag.
01067     if (p >= 0) { // opens with a tag
01068         QString tagname = opensWithTagRx.capturedTexts().at(1).toLower();
01069         if (s->knownTags.contains(tagname)) { // a known tag
01070             Kuit::TagVar tag = s->knownTags[tagname];
01071             if (   tag == Kuit::Tag::TopLong
01072                 || tag == Kuit::Tag::TopShort) { // explicitly given top tag
01073                 toptag = tag;
01074                 explicitTopTag = true;
01075             }
01076             else if (   tag == Kuit::Tag::Para
01077                      || tag == Kuit::Tag::Title
01078                      || tag == Kuit::Tag::Subtitle) { // one of long text tags
01079                 toptag = Kuit::Tag::TopLong;
01080             }
01081             else { // not one of long text tags
01082                 toptag = Kuit::Tag::TopShort;
01083             }
01084         }
01085         else { // not a KUIT tag
01086             toptag = Kuit::Tag::TopShort;
01087         }
01088     }
01089     else { // doesn't open with a tag
01090         toptag = Kuit::Tag::TopShort;
01091     }
01092 
01093     // Wrap text with top tag if not explicitly given.
01094     if (!explicitTopTag) {
01095         return   '<' + s->tagNames[toptag] + '>'
01096                + text_ // original text, not the one possibly stripped above
01097                + "</" + s->tagNames[toptag] + '>';
01098     }
01099     else {
01100         return text;
01101     }
01102 }
01103 
01104 #define ENTITY_SUBRX "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+"
01105 
01106 QString KuitSemanticsPrivate::semanticToVisualText (const QString &text_,
01107                                                     Kuit::FmtVar fmtExp_,
01108                                                     Kuit::FmtVar fmtImp_) const
01109 {
01110     KuitSemanticsStaticData *s = semanticsStaticData;
01111 
01112     // Replace &-shortcut marker with "&amp;", not to confuse the parser;
01113     // but do not touch & which forms an XML entity as it is.
01114     QString original = text_;
01115     QString text;
01116     int p = original.indexOf('&');
01117     while (p >= 0) {
01118         text.append(original.mid(0, p + 1));
01119         original.remove(0, p + 1);
01120         static QRegExp restRx("^("ENTITY_SUBRX");");
01121         if (original.indexOf(restRx) != 0) { // not an entity
01122             text.append("amp;");
01123         }
01124         p = original.indexOf('&');
01125     }
01126     text.append(original);
01127 
01128     Kuit::FmtVar fmtExp = fmtExp_;
01129     Kuit::FmtVar fmtImp = fmtImp_;
01130     int numCtx = 0;
01131     bool hadQtTag = false;
01132     bool hadAnyHtmlTag = false;
01133     QStack<OpenEl> openEls;
01134     QXmlStreamReader xml(text);
01135     QStringRef lastElementName;
01136 
01137     while (!xml.atEnd()) {
01138         xml.readNext();
01139 
01140         if (xml.isStartElement()) {
01141             lastElementName = xml.name();
01142 
01143             // Find first proper enclosing element tag.
01144             Kuit::TagVar etag = Kuit::Tag::None;
01145             for (int i = openEls.size() - 1; i >= 0; --i) {
01146                 if (openEls[i].handling == OpenEl::Proper) {
01147                     etag = openEls[i].tag;
01148                     break;
01149                 }
01150             }
01151 
01152             // Collect data about this element.
01153             OpenEl oel = parseOpenEl(xml, etag, text);
01154             if (oel.name == "qt" || oel.name == "html") {
01155                 hadQtTag = true;
01156             }
01157             if (s->qtHtmlTagNames.contains(oel.name)) {
01158                 hadAnyHtmlTag = true;
01159             }
01160 
01161             // If this is top tag, check if it overrides the context marker
01162             // by its ctx attribute.
01163             if (openEls.isEmpty() && oel.avals.contains(Kuit::Att::Ctx)) {
01164                 // Resolve format override.
01165                 fmtExp = formatFromContextMarker(oel.avals[Kuit::Att::Ctx], text);
01166                 fmtImp = fmtExp;
01167             }
01168 
01169             // Record the new element on the parse stack.
01170             openEls.push(oel);
01171 
01172             // Update numeric context.
01173             if (oel.tag == Kuit::Tag::Numid) {
01174                 ++numCtx;
01175             }
01176         }
01177         else if (xml.isEndElement()) {
01178             // Get closed element data.
01179             OpenEl oel = openEls.pop();
01180 
01181             // If this was closing of the top element, we're done.
01182             if (openEls.isEmpty()) {
01183                 // Return with final touches applied.
01184                 return finalizeVisualText(oel.formattedText, fmtExp,
01185                                           hadQtTag, hadAnyHtmlTag);
01186             }
01187 
01188             // Append formatted text segment.
01189             QString pt = openEls.top().formattedText; // preceding text
01190             openEls.top().formattedText += formatSubText(pt, oel, fmtImp, numCtx);
01191 
01192             // Update numeric context.
01193             if (oel.tag == Kuit::Tag::Numid) {
01194                 --numCtx;
01195             }
01196         }
01197         else if (xml.isCharacters()) {
01198             // Stream reader will automatically resolve default XML entities,
01199             // which is not desired in this case, as the final text may
01200             // be rich. Convert them back into entities.
01201             QString text = xml.text().toString();
01202             QString ntext;
01203             foreach (const QChar &c, text) {
01204                 if (s->xmlEntitiesInverse.contains(c)) {
01205                     QString entname = s->xmlEntitiesInverse[c];
01206                     ntext += '&' + entname + ';';
01207                 } else {
01208                     ntext += c;
01209                 }
01210             }
01211             openEls.top().formattedText += ntext;
01212         }
01213     }
01214 
01215     if (xml.hasError()) {
01216         kDebug(173) << QString("Markup error in message {%1}: %2. Last tag parsed: %3")
01217                               .arg(shorten(text), xml.errorString(), lastElementName.toString());
01218         return QString();
01219     }
01220 
01221     // Cannot reach here.
01222     return text;
01223 }
01224 
01225 KuitSemanticsPrivate::OpenEl
01226 KuitSemanticsPrivate::parseOpenEl (const QXmlStreamReader &xml,
01227                                    Kuit::TagVar etag,
01228                                    const QString &text) const
01229 {
01230     #ifdef NDEBUG
01231     Q_UNUSED(text);
01232     #endif
01233 
01234     KuitSemanticsStaticData *s = semanticsStaticData;
01235 
01236     OpenEl oel;
01237     oel.name = xml.name().toString().toLower();
01238 
01239     // Collect attribute names and values, and format attribute string.
01240     QStringList attnams, attvals;
01241     foreach (const QXmlStreamAttribute &xatt, xml.attributes()) {
01242         attnams += xatt.name().toString().toLower();
01243         attvals += xatt.value().toString();
01244         QChar qc = attvals.last().indexOf('\'') < 0 ? '\'' : '"';
01245         oel.astr += ' ' + attnams.last() + '=' + qc + attvals.last() + qc;
01246     }
01247 
01248     if (s->knownTags.contains(oel.name)) { // known KUIT element
01249         oel.tag = s->knownTags[oel.name];
01250 
01251         // If this element can be contained within enclosing element,
01252         // mark it proper, otherwise mark it for removal.
01253         if (etag == Kuit::Tag::None || s->tagSubs[etag].contains(oel.tag)) {
01254             oel.handling = OpenEl::Proper;
01255         }
01256         else {
01257             oel.handling = OpenEl::Dropout;
01258             kDebug(173) << QString("Tag '%1' cannot be subtag of '%2' "
01259                                    "in message {%3}.")
01260                                   .arg(s->tagNames[oel.tag], s->tagNames[etag],
01261                                        shorten(text));
01262         }
01263 
01264         // Resolve attributes and compute attribute set key.
01265         QSet<Kuit::AttVar> attset;
01266         for (int i = 0; i < attnams.size(); ++i) {
01267             if (s->knownAtts.contains(attnams[i])) {
01268                 Kuit::AttVar att = s->knownAtts[attnams[i]];
01269                 if (s->tagAtts[oel.tag].contains(att)) {
01270                     attset << att;
01271                     oel.avals[att] = attvals[i];
01272                 }
01273                 else {
01274                     kDebug(173) << QString("Attribute '%1' cannot be used in "
01275                                            "tag '%2' in message {%3}.")
01276                                           .arg(attnams[i], oel.name,
01277                                                shorten(text));
01278                 }
01279             }
01280             else {
01281                 kDebug(173) << QString("Unknown semantic tag attribute '%1' "
01282                                        "in message {%2}.")
01283                                       .arg(attnams[i], shorten(text));
01284             }
01285         }
01286         oel.akey = attSetKey(attset);
01287     }
01288     else if (oel.name == "qt" || oel.name == "html") {
01289         // Drop qt/html tags (gets added in the end).
01290         oel.handling = OpenEl::Dropout;
01291     }
01292     else { // other element, leave it in verbatim
01293         oel.handling = OpenEl::Ignored;
01294         if (!s->qtHtmlTagNames.contains(oel.name)) {
01295             kDebug(173) << QString("Tag '%1' is neither semantic nor HTML in "
01296                                    "message {%3}.")
01297                                   .arg(oel.name, shorten(text));
01298         }
01299     }
01300 
01301     return oel;
01302 }
01303 
01304 QString KuitSemanticsPrivate::visualPattern (Kuit::TagVar tag, int akey,
01305                                              Kuit::FmtVar fmt) const
01306 {
01307     // Default pattern: simple substitution.
01308     QString pattern("%1");
01309 
01310     // See if there is a pattern specifically for this element.
01311     if (   m_patterns.contains(tag)
01312         && m_patterns[tag].contains(akey)
01313         && m_patterns[tag][akey].contains(fmt))
01314     {
01315         pattern = m_patterns[tag][akey][fmt];
01316     }
01317 
01318     return pattern;
01319 }
01320 
01321 QString KuitSemanticsPrivate::formatSubText (const QString &ptext,
01322                                              const OpenEl &oel,
01323                                              Kuit::FmtVar fmt,
01324                                              int numctx) const
01325 {
01326     KuitSemanticsStaticData *s = semanticsStaticData;
01327 
01328     if (oel.handling == OpenEl::Proper) {
01329         // Select formatting pattern.
01330         QString pattern = visualPattern(oel.tag, oel.akey, fmt);
01331 
01332         // Some tags modify their text.
01333         QString mtext = modifyTagText(oel.tag, oel.formattedText, numctx, fmt);
01334 
01335         using namespace Kuit;
01336 
01337         // Format text according to pattern.
01338         QString ftext;
01339          if (oel.tag == Tag::Link && oel.avals.contains(Att::Url)) {
01340             ftext = pattern.arg(oel.avals[Att::Url], mtext);
01341         }
01342         else if (oel.tag == Tag::Command && oel.avals.contains(Att::Section)) {
01343             ftext = pattern.arg(mtext, oel.avals[Att::Section]);
01344         }
01345         else if (oel.tag == Tag::Email && oel.avals.contains(Att::Address)) {
01346             ftext = pattern.arg(mtext, oel.avals[Att::Address]);
01347         }
01348         else if (oel.tag == Tag::Note && oel.avals.contains(Att::Label)) {
01349             ftext = pattern.arg(oel.avals[Att::Label], mtext);
01350         }
01351         else if (oel.tag == Tag::Warning && oel.avals.contains(Att::Label)) {
01352             ftext = pattern.arg(oel.avals[Att::Label], mtext);
01353         }
01354         else {
01355             ftext = pattern.arg(mtext);
01356         }
01357 
01358         // Handle leading newlines, if this is not start of the text
01359         // (ptext is the preceding text).
01360         if (!ptext.isEmpty() && s->leadingNewlines.contains(oel.tag)) {
01361             // Count number of present newlines.
01362             int pnumle, pnumtr, fnumle, fnumtr;
01363             countWrappingNewlines(ptext, pnumle, pnumtr);
01364             countWrappingNewlines(ftext, fnumle, fnumtr);
01365             // Number of leading newlines already present.
01366             int numle = pnumtr + fnumle;
01367             // The required extra newlines.
01368             QString strle;
01369             if (numle < s->leadingNewlines[oel.tag]) {
01370                 strle = QString(s->leadingNewlines[oel.tag] - numle, '\n');
01371             }
01372             ftext = strle + ftext;
01373         }
01374 
01375         return ftext;
01376     }
01377     else if (oel.handling == OpenEl::Ignored) {
01378         if (oel.name == "br" || oel.name == "hr") {
01379             // Close these tags in-place (just for looks).
01380             return '<' + oel.name + "/>";
01381         }
01382         else {
01383             return   '<' + oel.name + oel.astr + '>'
01384                    + oel.formattedText
01385                    + "</" + oel.name + '>';
01386         }
01387     }
01388     else { // oel.handling == OpenEl::Dropout
01389         return oel.formattedText;
01390     }
01391 }
01392 
01393 void KuitSemanticsPrivate::countWrappingNewlines (const QString &text,
01394                                                   int &numle, int &numtr)
01395 {
01396     int len = text.length();
01397     // Number of newlines at start of text.
01398     numle = 0;
01399     while (numle < len && text[numle] == '\n') {
01400         ++numle;
01401     }
01402     // Number of newlines at end of text.
01403     numtr = 0;
01404     while (numtr < len && text[len - numtr - 1] == '\n') {
01405         ++numtr;
01406     }
01407 }
01408 
01409 QString KuitSemanticsPrivate::modifyTagText (Kuit::TagVar tag,
01410                                              const QString &text,
01411                                              int numctx,
01412                                              Kuit::FmtVar fmt) const
01413 {
01414     // numctx < 1 means that the number is not in numeric-id context.
01415     if (   (tag == Kuit::Tag::NumIntg || tag == Kuit::Tag::NumReal) \
01416         && numctx < 1)
01417     {
01418         return KGlobal::locale()->formatNumber(text, false);
01419     }
01420     else if (tag == Kuit::Tag::Filename) {
01421         return QDir::toNativeSeparators(text);
01422     }
01423     else if (tag == Kuit::Tag::Shortcut) {
01424         return KuitFormats::toKeyCombo(text, m_comboKeyDelim[fmt], m_keyNames);
01425     }
01426     else if (tag == Kuit::Tag::Interface) {
01427         return KuitFormats::toInterfacePath(text, m_guiPathDelim[fmt]);
01428     }
01429 
01430     // Fell through, no modification.
01431     return text;
01432 }
01433 
01434 QString KuitSemanticsPrivate::finalizeVisualText (const QString &final,
01435                                                   Kuit::FmtVar fmt,
01436                                                   bool hadQtTag,
01437                                                   bool hadAnyHtmlTag) const
01438 {
01439     KuitSemanticsStaticData *s = semanticsStaticData;
01440 
01441     QString text = final;
01442 
01443     // Resolve XML entities if format explicitly not rich
01444     // and no HTML tag encountered.
01445     if (fmt != Kuit::Fmt::Rich && !hadAnyHtmlTag)
01446     {
01447         static QRegExp staticEntRx("&("ENTITY_SUBRX");");
01448         // We have to have a local copy here, otherwise this function
01449         // will not be thread safe because QRegExp is not thread safe.
01450         QRegExp entRx = staticEntRx;
01451         int p = entRx.indexIn(text);
01452         QString plain;
01453         while (p >= 0) {
01454             QString ent = entRx.capturedTexts().at(1);
01455             plain.append(text.mid(0, p));
01456             text.remove(0, p + ent.length() + 2);
01457             if (ent.startsWith('#')) { // numeric character entity
01458                 QChar c;
01459                 bool ok;
01460                 if (ent[1] == 'x') {
01461                     c = QChar(ent.mid(2).toInt(&ok, 16));
01462                 } else {
01463                     c = QChar(ent.mid(1).toInt(&ok, 10));
01464                 }
01465                 if (ok) {
01466                     plain.append(c);
01467                 } else { // unknown Unicode point, leave as is
01468                     plain.append('&' + ent + ';');
01469                 }
01470             }
01471             else if (s->xmlEntities.contains(ent)) { // known entity
01472                 plain.append(s->xmlEntities[ent]);
01473             } else { // unknown entity, just leave as is
01474                 plain.append('&' + ent + ';');
01475             }
01476             p = entRx.indexIn(text);
01477         }
01478         plain.append(text);
01479         text = plain;
01480     }
01481 
01482     // Add top rich tag if format explicitly rich or such tag encountered.
01483     if (fmt == Kuit::Fmt::Rich || hadQtTag) {
01484         text = "<html>" + text + "</html>";
01485     }
01486 
01487     return text;
01488 }
01489 
01490 QString KuitSemanticsPrivate::salvageMarkup (const QString &text_,
01491                                              Kuit::FmtVar fmt) const
01492 {
01493     KuitSemanticsStaticData *s = semanticsStaticData;
01494     QString text = text_;
01495     QString ntext;
01496     int pos;
01497 
01498     // Resolve KUIT tags simple-mindedly.
01499 
01500     // - tags with content
01501     static QRegExp staticWrapRx("(<\\s*(\\w+)\\b([^>]*)>)(.*)(<\\s*/\\s*\\2\\s*>)");
01502     QRegExp wrapRx = staticWrapRx; // for thread-safety
01503     wrapRx.setMinimal(true);
01504     pos = 0;
01505     ntext.clear();
01506     while (true) {
01507         int previousPos = pos;
01508         pos = wrapRx.indexIn(text, previousPos);
01509         if (pos < 0) {
01510             ntext += text.mid(previousPos);
01511             break;
01512         }
01513         ntext += text.mid(previousPos, pos - previousPos);
01514         const QStringList capts = wrapRx.capturedTexts();
01515         QString tagname = capts[2].toLower();
01516         QString content = salvageMarkup(capts[4], fmt);
01517         if (s->knownTags.contains(tagname)) {
01518             // Select formatting pattern.
01519             // TODO: Do not ignore attributes (in capts[3]).
01520             QString pattern = visualPattern(s->knownTags[tagname], 0, fmt);
01521             ntext += pattern.arg(content);
01522         } else {
01523             ntext += capts[1] + content + capts[5];
01524         }
01525         pos += wrapRx.matchedLength();
01526     }
01527     text = ntext;
01528 
01529     // - content-less tags
01530     static QRegExp staticNowrRx("<\\s*(\\w+)\\b([^>]*)/\\s*>");
01531     QRegExp nowrRx = staticNowrRx; // for thread-safety
01532     nowrRx.setMinimal(true);
01533     pos = 0;
01534     ntext.clear();
01535     while (true) {
01536         int previousPos = pos;
01537         pos = nowrRx.indexIn(text, previousPos);
01538         if (pos < 0) {
01539             ntext += text.mid(previousPos);
01540             break;
01541         }
01542         ntext += text.mid(previousPos, pos - previousPos);
01543         const QStringList capts = nowrRx.capturedTexts();
01544         QString tagname = capts[1].toLower();
01545         if (s->knownTags.contains(tagname)) {
01546             QString pattern = visualPattern(s->knownTags[tagname], 0, fmt);
01547             ntext += pattern.arg(QString());
01548         } else {
01549             ntext += capts[0];
01550         }
01551         pos += nowrRx.matchedLength();
01552     }
01553     text = ntext;
01554 
01555     return text;
01556 }
01557 
01558 // -----------------------------------------------------------------------------
01559 // The KuitSemantics methods, only delegate to KuitSemanticsPrivate.
01560 
01561 KuitSemantics::KuitSemantics (const QString &lang)
01562 : d(new KuitSemanticsPrivate(lang))
01563 {
01564 }
01565 
01566 KuitSemantics::~KuitSemantics ()
01567 {
01568     delete d;
01569 }
01570 
01571 QString KuitSemantics::format (const QString &text, const QString &ctxt) const
01572 {
01573     return d->format(text, ctxt);
01574 }
01575 
01576 bool KuitSemantics::mightBeRichText (const QString &text)
01577 {
01578     KuitSemanticsStaticData *s = semanticsStaticData;
01579 
01580     // Check by appearance of a valid XML entity at first ampersand.
01581     int p1 = text.indexOf('&');
01582     if (p1 >= 0) {
01583         p1 += 1;
01584         int p2 = text.indexOf(';', p1);
01585         return (p2 > p1 && s->xmlEntities.contains(text.mid(p1, p2 - p1)));
01586     }
01587 
01588     // Check by appearance of a valid Qt rich-text tag at first less-than.
01589     int tlen = text.length();
01590     p1 = text.indexOf('<');
01591     if (p1 >= 0) {
01592         p1 += 1;
01593         // Also allow first tag to be closing tag,
01594         // e.g. in case the text is pieced up with list.join("</foo><foo>")
01595         bool closing = false;
01596         while (p1 < tlen && (text[p1].isSpace() || text[p1] == '/')) {
01597             if (text[p1] == '/') {
01598                 if (!closing) {
01599                     closing = true;
01600                 } else {
01601                     return false;
01602                 }
01603             }
01604             ++p1;
01605         }
01606         for (int p2 = p1; p2 < tlen; ++p2) {
01607             QChar c = text[p2];
01608             if (c == '>' || (!closing && c == '/') || c.isSpace()) {
01609                 return s->qtHtmlTagNames.contains(text.mid(p1, p2 - p1));
01610             } else if (!c.isLetter()) {
01611                 return false;
01612             }
01613         }
01614         return false;
01615     }
01616 
01617     return false;
01618 }
01619 
01620 QString KuitSemantics::escape (const QString &text)
01621 {
01622     int tlen = text.length();
01623     QString ntext;
01624     ntext.reserve(tlen);
01625     for (int i = 0; i < tlen; ++i) {
01626         QChar c = text[i];
01627         if (c == '&') {
01628             ntext += "&amp;";
01629         } else if (c == '<') {
01630             ntext += "&lt;";
01631         } else if (c == '>') {
01632             ntext += "&gt;";
01633         } else {
01634             ntext += c;
01635         }
01636     }
01637 
01638     return ntext;
01639 }
01640 

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal