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

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
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
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