File indexing completed on 2022-09-27 16:29:27

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût <slaout@linux62.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "tag.h"
0008 
0009 #include <KActionCollection>
0010 #include <KLocalizedString>
0011 
0012 #include <QIcon>
0013 #include <QLocale>
0014 #include <QtCore/QDir>
0015 #include <QtCore/QList>
0016 #include <QtCore/QTextStream>
0017 #include <QtGui/QFont>
0018 #include <QtXml/QDomDocument>
0019 
0020 #include "basketscene.h"
0021 #include "bnpview.h"
0022 #include "common.h"
0023 #include "debugwindow.h"
0024 #include "gitwrapper.h"
0025 #include "global.h"
0026 #include "tools.h"
0027 #include "xmlwork.h"
0028 
0029 /** class State: */
0030 
0031 State::State(const QString &id, Tag *tag)
0032     : m_id(id)
0033     , m_name()
0034     , m_emblem()
0035     , m_bold(false)
0036     , m_italic(false)
0037     , m_underline(false)
0038     , m_strikeOut(false)
0039     , m_textColor()
0040     , m_fontName()
0041     , m_fontSize(-1)
0042     , m_backgroundColor()
0043     , m_textEquivalent()
0044     , m_onAllTextLines(false)
0045     , m_allowCrossReferences(true)
0046     , m_parentTag(tag)
0047 {
0048 }
0049 
0050 State::~State()
0051 {
0052 }
0053 
0054 State *State::nextState(bool cycle /*= true*/)
0055 {
0056     if (!parentTag())
0057         return nullptr;
0058 
0059     List states = parentTag()->states();
0060     // The tag contains only one state:
0061     if (states.count() == 1)
0062         return nullptr;
0063     // Find the next state:
0064     for (List::iterator it = states.begin(); it != states.end(); ++it)
0065         // Found the current state in the list:
0066         if (*it == this) {
0067             // Find the next state:
0068             State *next = *(++it);
0069             if (it == states.end())
0070                 return (cycle ? states.first() : 0);
0071             return next;
0072         }
0073     // Should not happens:
0074     Q_ASSERT(false);
0075     return nullptr;
0076 }
0077 
0078 QString State::fullName()
0079 {
0080     if (!parentTag() || parentTag()->states().count() == 1)
0081         return (name().isEmpty() && parentTag() ? parentTag()->name() : name());
0082     return QString(i18n("%1: %2", parentTag()->name(), name()));
0083 }
0084 
0085 QFont State::font(QFont base)
0086 {
0087     if (bold())
0088         base.setBold(true);
0089     if (italic())
0090         base.setItalic(true);
0091     if (underline())
0092         base.setUnderline(true);
0093     if (strikeOut())
0094         base.setStrikeOut(true);
0095     if (!fontName().isEmpty())
0096         base.setFamily(fontName());
0097     if (fontSize() > 0)
0098         base.setPointSize(fontSize());
0099     return base;
0100 }
0101 
0102 QString State::toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont)
0103 {
0104     QString css;
0105     if (bold())
0106         css += " font-weight: bold;";
0107     if (italic())
0108         css += " font-style: italic;";
0109     if (underline() && strikeOut())
0110         css += " text-decoration: underline line-through;";
0111     else if (underline())
0112         css += " text-decoration: underline;";
0113     else if (strikeOut())
0114         css += " text-decoration: line-through;";
0115     if (textColor().isValid())
0116         css += " color: " + textColor().name() + ';';
0117     if (!fontName().isEmpty()) {
0118         QString fontFamily = Tools::cssFontDefinition(fontName(), /*onlyFontFamily=*/true);
0119         css += " font-family: " + fontFamily + ';';
0120     }
0121     if (fontSize() > 0)
0122         css += " font-size: " + QString::number(fontSize()) + "px;";
0123     if (backgroundColor().isValid()) {
0124         css += " background-color: " + backgroundColor().name() + ";";
0125     }
0126 
0127     if (css.isEmpty())
0128         return QString();
0129     else
0130         return "   .tag_" + id() + " {" + css + " }\n";
0131 }
0132 
0133 void State::merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor)
0134 {
0135     *result = State(); // Reset to default values.
0136     *emblemsCount = 0;
0137     *haveInvisibleTags = false;
0138 
0139     for (List::const_iterator it = states.begin(); it != states.end(); ++it) {
0140         State *state = *it;
0141         bool isVisible = false;
0142         // For each property, if that properties have a value (is not default) is the current state of the list,
0143         // and if it haven't been set to the result state by a previous state, then it's visible and we assign the property to the result state.
0144         if (!state->emblem().isEmpty()) {
0145             ++*emblemsCount;
0146             isVisible = true;
0147         }
0148         if (state->bold() && !result->bold()) {
0149             result->setBold(true);
0150             isVisible = true;
0151         }
0152         if (state->italic() && !result->italic()) {
0153             result->setItalic(true);
0154             isVisible = true;
0155         }
0156         if (state->underline() && !result->underline()) {
0157             result->setUnderline(true);
0158             isVisible = true;
0159         }
0160         if (state->strikeOut() && !result->strikeOut()) {
0161             result->setStrikeOut(true);
0162             isVisible = true;
0163         }
0164         if (state->textColor().isValid() && !result->textColor().isValid()) {
0165             result->setTextColor(state->textColor());
0166             isVisible = true;
0167         }
0168         if (!state->fontName().isEmpty() && result->fontName().isEmpty()) {
0169             result->setFontName(state->fontName());
0170             isVisible = true;
0171         }
0172         if (state->fontSize() > 0 && result->fontSize() <= 0) {
0173             result->setFontSize(state->fontSize());
0174             isVisible = true;
0175         }
0176         if (state->backgroundColor().isValid() && !result->backgroundColor().isValid() && state->backgroundColor() != backgroundColor) { // vv
0177             result->setBackgroundColor(state->backgroundColor());                                                                        // This is particular: if the note background color is the same as the basket one, don't use that.
0178             isVisible = true;
0179         }
0180         // If it's not visible, well, at least one tag is not visible: the note will display "..." at the tags arrow place to show that:
0181         if (!isVisible)
0182             *haveInvisibleTags = true;
0183     }
0184 }
0185 
0186 void State::copyTo(State *other)
0187 {
0188     other->m_id = m_id;
0189     other->m_name = m_name;
0190     other->m_emblem = m_emblem;
0191     other->m_bold = m_bold;
0192     other->m_italic = m_italic;
0193     other->m_underline = m_underline;
0194     other->m_strikeOut = m_strikeOut;
0195     other->m_textColor = m_textColor;
0196     other->m_fontName = m_fontName;
0197     other->m_fontSize = m_fontSize;
0198     other->m_backgroundColor = m_backgroundColor;
0199     other->m_textEquivalent = m_textEquivalent;
0200     other->m_onAllTextLines = m_onAllTextLines; // TODO
0201     other->m_allowCrossReferences = m_allowCrossReferences;
0202     // TODO: other->m_parentTag;
0203 }
0204 
0205 /** class Tag: */
0206 
0207 Tag::List Tag::all = Tag::List();
0208 
0209 long Tag::nextStateUid = 1;
0210 
0211 QRegularExpression Tag::regexpDetectTags = QRegularExpression();
0212 
0213 QHash<QString, State*> Tag::dictStatesByEquiv = QHash<QString, State*>();
0214 
0215 long Tag::getNextStateUid()
0216 {
0217     return nextStateUid++; // Return the next Uid and THEN increment the Uid
0218 }
0219 
0220 Tag::Tag()
0221 {
0222     static int tagNumber = 0;
0223     ++tagNumber;
0224     QString sAction = "tag_shortcut_number_" + QString::number(tagNumber);
0225 
0226     KActionCollection *ac = Global::bnpView->actionCollection();
0227     m_action = ac->addAction(sAction, Global::bnpView, SLOT(activatedTagShortcut()));
0228     m_action->setText("FAKE TEXT");
0229     m_action->setIcon(QIcon::fromTheme("FAKE ICON"));
0230 
0231     ac->setShortcutsConfigurable(m_action, false); // We do it in the tag properties dialog
0232 
0233     m_inheritedBySiblings = false;
0234 }
0235 
0236 Tag::~Tag()
0237 {
0238     delete m_action;
0239 }
0240 
0241 void Tag::setName(const QString &name)
0242 {
0243     m_name = name;
0244     m_action->setText("TAG SHORTCUT: " + name); // TODO: i18n  (for debug purpose only by now).
0245 }
0246 
0247 State *Tag::stateById(const QString &id)
0248 {
0249     for (List::iterator it = all.begin(); it != all.end(); ++it)
0250         for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2)
0251             if ((*it2)->id() == id)
0252                 return *it2;
0253     return nullptr;
0254 }
0255 
0256 State *Tag::stateByTextEquiv(const QString &text)
0257 {
0258     if (!dictStatesByEquiv.contains(text))
0259         return nullptr;
0260 
0261     return dictStatesByEquiv.value(text);
0262 }
0263 
0264 Tag *Tag::tagForKAction(QAction *action)
0265 {
0266     for (List::iterator it = all.begin(); it != all.end(); ++it)
0267         if ((*it)->m_action == action)
0268             return *it;
0269     return nullptr;
0270 }
0271 
0272 QMap<QString, QString> Tag::loadTags(const QString &path /* = QString()*/ /*, bool merge = false*/)
0273 {
0274     QMap<QString, QString> mergedStates;
0275 
0276     bool merge = !path.isEmpty();
0277     QString fullPath = (merge ? path : Global::savesFolder() + "tags.xml");
0278     QString doctype = "basketTags";
0279 
0280     QDir dir;
0281     if (!dir.exists(fullPath)) {
0282         if (merge)
0283             return mergedStates;
0284         DEBUG_WIN << "Tags file does not exist: Creating it...";
0285         createDefaultTagsSet(fullPath);
0286     }
0287 
0288     QScopedPointer<QDomDocument> document(XMLWork::openFile(doctype, fullPath));
0289     if (!document) {
0290         DEBUG_WIN << "<font color=red>FAILED to read the tags file</font>";
0291         return mergedStates;
0292     }
0293 
0294     QDomElement docElem = document->documentElement();
0295     if (!merge)
0296         nextStateUid = docElem.attribute("nextStateUid", QString::number(nextStateUid)).toLong();
0297 
0298     QDomNode node = docElem.firstChild();
0299     while (!node.isNull()) {
0300         QDomElement element = node.toElement();
0301         if ((!element.isNull()) && element.tagName() == "tag") {
0302             Tag *tag = new Tag();
0303             // Load properties:
0304             QString name = XMLWork::getElementText(element, "name");
0305             QString shortcut = XMLWork::getElementText(element, "shortcut");
0306             QString inherited = XMLWork::getElementText(element, "inherited", "false");
0307             tag->setName(name);
0308             tag->setShortcut(QKeySequence(shortcut));
0309             tag->setInheritedBySiblings(XMLWork::trueOrFalse(inherited));
0310             // Load states:
0311             QDomNode subNode = element.firstChild();
0312             while (!subNode.isNull()) {
0313                 QDomElement subElement = subNode.toElement();
0314                 if ((!subElement.isNull()) && subElement.tagName() == "state") {
0315                     State *state = new State(subElement.attribute("id"), tag);
0316                     state->setName(XMLWork::getElementText(subElement, "name"));
0317                     state->setEmblem(XMLWork::getElementText(subElement, "emblem"));
0318                     QDomElement textElement = XMLWork::getElement(subElement, "text");
0319                     state->setBold(XMLWork::trueOrFalse(textElement.attribute("bold", "false")));
0320                     state->setItalic(XMLWork::trueOrFalse(textElement.attribute("italic", "false")));
0321                     state->setUnderline(XMLWork::trueOrFalse(textElement.attribute("underline", "false")));
0322                     state->setStrikeOut(XMLWork::trueOrFalse(textElement.attribute("strikeOut", "false")));
0323                     QString textColor = textElement.attribute("color", QString());
0324                     state->setTextColor(textColor.isEmpty() ? QColor() : QColor(textColor));
0325                     QDomElement fontElement = XMLWork::getElement(subElement, "font");
0326                     state->setFontName(fontElement.attribute("name", QString()));
0327                     QString fontSize = fontElement.attribute("size", QString());
0328                     state->setFontSize(fontSize.isEmpty() ? -1 : fontSize.toInt());
0329                     QString backgroundColor = XMLWork::getElementText(subElement, "backgroundColor", QString());
0330                     state->setBackgroundColor(backgroundColor.isEmpty() ? QColor() : QColor(backgroundColor));
0331                     QDomElement textEquivalentElement = XMLWork::getElement(subElement, "textEquivalent");
0332                     state->setTextEquivalent(textEquivalentElement.attribute("string", QString()));
0333                     state->setOnAllTextLines(XMLWork::trueOrFalse(textEquivalentElement.attribute("onAllTextLines", "false")));
0334                     QString allowXRef = XMLWork::getElementText(subElement, "allowCrossReferences", "true");
0335                     state->setAllowCrossReferences(XMLWork::trueOrFalse(allowXRef));
0336                     tag->appendState(state);
0337                 }
0338                 subNode = subNode.nextSibling();
0339             }
0340             // If the Tag is Valid:
0341             if (tag->countStates() > 0) {
0342                 // Rename Things if Needed:
0343                 State *firstState = tag->states().first();
0344                 if (tag->countStates() == 1 && firstState->name().isEmpty())
0345                     firstState->setName(tag->name());
0346                 if (tag->name().isEmpty())
0347                     tag->setName(firstState->name());
0348                 // Add or Merge the Tag:
0349                 if (!merge) {
0350                     all.append(tag);
0351                 } else {
0352                     Tag *similarTag = tagSimilarTo(tag);
0353                     // Tag does not exists, add it:
0354                     if (similarTag == nullptr) {
0355                         // We are merging the new states, so we should choose new and unique (on that computer) ids for those states:
0356                         for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it) {
0357                             State *state = *it;
0358                             QString uid = state->id();
0359                             QString newUid = "tag_state_" + QString::number(getNextStateUid());
0360                             state->setId(newUid);
0361                             mergedStates[uid] = newUid;
0362                         }
0363                         // TODO: if shortcut is already assigned to a previous note, do not import it, keep the user settings!
0364                         all.append(tag);
0365                         // Tag already exists, rename to their ids:
0366                     } else {
0367                         State::List::iterator it2 = similarTag->states().begin();
0368                         for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it, ++it2) {
0369                             State *state = *it;
0370                             State *similarState = *it2;
0371                             QString uid = state->id();
0372                             QString newUid = similarState->id();
0373                             if (uid != newUid)
0374                                 mergedStates[uid] = newUid;
0375                         }
0376                         delete tag; // Already exists, not to be merged. Delete the shortcut and all.
0377                     }
0378                 }
0379             }
0380         }
0381         node = node.nextSibling();
0382     }
0383     updateCaches();
0384 
0385     return mergedStates;
0386 }
0387 
0388 Tag *Tag::tagSimilarTo(Tag *tagToTest)
0389 {
0390     // Tags are considered similar if they have the same name, the same number of states, in the same order, and the same look.
0391     // Keyboard shortcut, text equivalent and onEveryLines are user settings, and thus not considered during the comparison.
0392     // Default tags (To Do, Important, Idea...) do not take into account the name of the tag and states during the comparison.
0393     // Default tags are equal only if they have the same number of states, in the same order, and the same look.
0394     // This is because default tag names are translated differently in every countries, but they are essentially the same!
0395     // User tags begins with "tag_state_" followed by a number. Default tags are the other ones.
0396 
0397     // Browse all tags:
0398     for (List::iterator it = all.begin(); it != all.end(); ++it) {
0399         Tag *tag = *it;
0400         bool same = true;
0401         bool sameName;
0402         bool defaultTag = true;
0403         // We test only name and look. Shortcut and whenever it is inherited by sibling new notes are user settings only!
0404         sameName = tag->name() == tagToTest->name();
0405         if (tag->countStates() != tagToTest->countStates())
0406             continue; // Tag is different!
0407         // We found a tag with same name, check if every states/look are same too:
0408         State::List::iterator itTest = tagToTest->states().begin();
0409         for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2, ++itTest) {
0410             State *state = *it2;
0411             State *stateToTest = *itTest;
0412             if (state->id().startsWith(QLatin1String("tag_state_")) || stateToTest->id().startsWith(QLatin1String("tag_state_"))) {
0413                 defaultTag = false;
0414             }
0415             if (state->name() != stateToTest->name()) {
0416                 sameName = false;
0417             }
0418             if (state->emblem() != stateToTest->emblem()) {
0419                 same = false;
0420                 break;
0421             }
0422             if (state->bold() != stateToTest->bold()) {
0423                 same = false;
0424                 break;
0425             }
0426             if (state->italic() != stateToTest->italic()) {
0427                 same = false;
0428                 break;
0429             }
0430             if (state->underline() != stateToTest->underline()) {
0431                 same = false;
0432                 break;
0433             }
0434             if (state->strikeOut() != stateToTest->strikeOut()) {
0435                 same = false;
0436                 break;
0437             }
0438             if (state->textColor() != stateToTest->textColor()) {
0439                 same = false;
0440                 break;
0441             }
0442             if (state->fontName() != stateToTest->fontName()) {
0443                 same = false;
0444                 break;
0445             }
0446             if (state->fontSize() != stateToTest->fontSize()) {
0447                 same = false;
0448                 break;
0449             }
0450             if (state->backgroundColor() != stateToTest->backgroundColor()) {
0451                 same = false;
0452                 break;
0453             }
0454             // Text equivalent (as well as onAllTextLines) is also a user setting!
0455         }
0456         // We found an existing tag that is "exactly" the same:
0457         if (same && (sameName || defaultTag))
0458             return tag;
0459     }
0460 
0461     // Not found:
0462     return nullptr;
0463 }
0464 
0465 void Tag::saveTags()
0466 {
0467     DEBUG_WIN << "Saving tags...";
0468     saveTagsTo(all, Global::savesFolder() + "tags.xml");
0469 
0470     GitWrapper::commitTagsXml();
0471 }
0472 
0473 void Tag::saveTagsTo(QList<Tag *> &list, const QString &fullPath)
0474 {
0475     // Create Document:
0476     QDomDocument document(/*doctype=*/"basketTags");
0477     QDomElement root = document.createElement("basketTags");
0478     root.setAttribute("nextStateUid", static_cast<long long int>(nextStateUid));
0479     document.appendChild(root);
0480 
0481     // Save all tags:
0482     for (List::iterator it = list.begin(); it != list.end(); ++it) {
0483         Tag *tag = *it;
0484         // Create tag node:
0485         QDomElement tagNode = document.createElement("tag");
0486         root.appendChild(tagNode);
0487         // Save tag properties:
0488         XMLWork::addElement(document, tagNode, "name", tag->name());
0489         XMLWork::addElement(document, tagNode, "shortcut", tag->shortcut().toString());
0490         XMLWork::addElement(document, tagNode, "inherited", XMLWork::trueOrFalse(tag->inheritedBySiblings()));
0491         // Save all states:
0492         for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) {
0493             State *state = *it2;
0494             // Create state node:
0495             QDomElement stateNode = document.createElement("state");
0496             tagNode.appendChild(stateNode);
0497             // Save state properties:
0498             stateNode.setAttribute("id", state->id());
0499             XMLWork::addElement(document, stateNode, "name", state->name());
0500             XMLWork::addElement(document, stateNode, "emblem", state->emblem());
0501             QDomElement textNode = document.createElement("text");
0502             stateNode.appendChild(textNode);
0503             QString textColor = (state->textColor().isValid() ? state->textColor().name() : QString());
0504             textNode.setAttribute("bold", XMLWork::trueOrFalse(state->bold()));
0505             textNode.setAttribute("italic", XMLWork::trueOrFalse(state->italic()));
0506             textNode.setAttribute("underline", XMLWork::trueOrFalse(state->underline()));
0507             textNode.setAttribute("strikeOut", XMLWork::trueOrFalse(state->strikeOut()));
0508             textNode.setAttribute("color", textColor);
0509             QDomElement fontNode = document.createElement("font");
0510             stateNode.appendChild(fontNode);
0511             fontNode.setAttribute("name", state->fontName());
0512             fontNode.setAttribute("size", state->fontSize());
0513             QString backgroundColor = (state->backgroundColor().isValid() ? state->backgroundColor().name() : QString());
0514             XMLWork::addElement(document, stateNode, "backgroundColor", backgroundColor);
0515             QDomElement textEquivalentNode = document.createElement("textEquivalent");
0516             stateNode.appendChild(textEquivalentNode);
0517             textEquivalentNode.setAttribute("string", state->textEquivalent());
0518             textEquivalentNode.setAttribute("onAllTextLines", XMLWork::trueOrFalse(state->onAllTextLines()));
0519             XMLWork::addElement(document, stateNode, "allowCrossReferences", XMLWork::trueOrFalse(state->allowCrossReferences()));
0520         }
0521     }
0522 
0523     // Write to Disk:
0524     if (!FileStorage::safelySaveToFile(fullPath, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + document.toString()))
0525         DEBUG_WIN << "<font color=red>FAILED to save tags</font>!";
0526 }
0527 
0528 void Tag::copyTo(Tag *other)
0529 {
0530     other->m_name = m_name;
0531     other->m_action->setShortcut(m_action->shortcut());
0532     other->m_inheritedBySiblings = m_inheritedBySiblings;
0533 }
0534 
0535 void Tag::createDefaultTagsSet(const QString &fullPath)
0536 {
0537     QString xml = QString(
0538                       "<!DOCTYPE basketTags>\n"
0539                       "<basketTags>\n"
0540                       "  <tag>\n"
0541                       "    <name>%1</name>\n" // "To Do"
0542                       "    <shortcut>Ctrl+1</shortcut>\n"
0543                       "    <inherited>true</inherited>\n"
0544                       "    <state id=\"todo_unchecked\">\n"
0545                       "      <name>%2</name>\n" // "Unchecked"
0546                       "      <emblem>tag_checkbox</emblem>\n"
0547                       "      <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"false\" color=\"\" />\n"
0548                       "      <font name=\"\" size=\"\" />\n"
0549                       "      <backgroundColor></backgroundColor>\n"
0550                       "      <textEquivalent string=\"[ ]\" onAllTextLines=\"false\" />\n"
0551                       "    </state>\n"
0552                       "    <state id=\"todo_done\">\n"
0553                       "      <name>%3</name>\n" // "Done"
0554                       "      <emblem>tag_checkbox_checked</emblem>\n"
0555                       "      <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"true\" color=\"\" />\n"
0556                       "      <font name=\"\" size=\"\" />\n"
0557                       "      <backgroundColor></backgroundColor>\n"
0558                       "      <textEquivalent string=\"[x]\" onAllTextLines=\"false\" />\n"
0559                       "    </state>\n"
0560                       "  </tag>\n"
0561                       "\n"
0562                       "  <tag>\n"
0563                       "    <name>%4</name>\n" // "Progress"
0564                       "    <shortcut>Ctrl+2</shortcut>\n"
0565                       "    <inherited>true</inherited>\n"
0566                       "    <state id=\"progress_000\">\n"
0567                       "      <name>%5</name>\n" // "0 %"
0568                       "      <emblem>tag_progress_000</emblem>\n"
0569                       "      <textEquivalent string=\"[    ]\" />\n"
0570                       "    </state>\n"
0571                       "    <state id=\"progress_025\">\n"
0572                       "      <name>%6</name>\n" // "25 %"
0573                       "      <emblem>tag_progress_025</emblem>\n"
0574                       "      <textEquivalent string=\"[=   ]\" />\n"
0575                       "    </state>\n"
0576                       "    <state id=\"progress_050\">\n"
0577                       "      <name>%7</name>\n" // "50 %"
0578                       "      <emblem>tag_progress_050</emblem>\n"
0579                       "      <textEquivalent string=\"[==  ]\" />\n"
0580                       "    </state>\n"
0581                       "    <state id=\"progress_075\">\n"
0582                       "      <name>%8</name>\n" // "75 %"
0583                       "      <emblem>tag_progress_075</emblem>\n"
0584                       "      <textEquivalent string=\"[=== ]\" />\n"
0585                       "    </state>\n"
0586                       "    <state id=\"progress_100\">\n"
0587                       "      <name>%9</name>\n" // "100 %"
0588                       "      <emblem>tag_progress_100</emblem>\n"
0589                       "      <textEquivalent string=\"[====]\" />\n"
0590                       "    </state>\n"
0591                       "  </tag>\n"
0592                       "\n")
0593                       .arg(i18n("To Do"), i18n("Unchecked"), i18n("Done")) // %1 %2 %3
0594                       .arg(i18n("Progress"), i18n("0 %"), i18n("25 %"))    // %4 %5 %6
0595                       .arg(i18n("50 %"), i18n("75 %"), i18n("100 %"))      // %7 %8 %9
0596         + QString(
0597               "  <tag>\n"
0598               "    <name>%1</name>\n" // "Priority"
0599               "    <shortcut>Ctrl+3</shortcut>\n"
0600               "    <inherited>true</inherited>\n"
0601               "    <state id=\"priority_low\">\n"
0602               "      <name>%2</name>\n" // "Low"
0603               "      <emblem>tag_priority_low</emblem>\n"
0604               "      <textEquivalent string=\"{1}\" />\n"
0605               "    </state>\n"
0606               "    <state id=\"priority_medium\">\n"
0607               "      <name>%3</name>\n" // "Medium
0608               "      <emblem>tag_priority_medium</emblem>\n"
0609               "      <textEquivalent string=\"{2}\" />\n"
0610               "    </state>\n"
0611               "    <state id=\"priority_high\">\n"
0612               "      <name>%4</name>\n" // "High"
0613               "      <emblem>tag_priority_high</emblem>\n"
0614               "      <textEquivalent string=\"{3}\" />\n"
0615               "    </state>\n"
0616               "  </tag>\n"
0617               "\n"
0618               "  <tag>\n"
0619               "    <name>%5</name>\n" // "Preference"
0620               "    <shortcut>Ctrl+4</shortcut>\n"
0621               "    <inherited>true</inherited>\n"
0622               "    <state id=\"preference_bad\">\n"
0623               "      <name>%6</name>\n" // "Bad"
0624               "      <emblem>tag_preference_bad</emblem>\n"
0625               "      <textEquivalent string=\"(*  )\" />\n"
0626               "    </state>\n"
0627               "    <state id=\"preference_good\">\n"
0628               "      <name>%7</name>\n" // "Good"
0629               "      <emblem>tag_preference_good</emblem>\n"
0630               "      <textEquivalent string=\"(** )\" />\n"
0631               "    </state>\n"
0632               "    <state id=\"preference_excellent\">\n"
0633               "      <name>%8</name>\n" // "Excellent"
0634               "      <emblem>tag_preference_excellent</emblem>\n"
0635               "      <textEquivalent string=\"(***)\" />\n"
0636               "    </state>\n"
0637               "  </tag>\n"
0638               "\n"
0639               "  <tag>\n"
0640               "    <name>%9</name>\n" // "Highlight"
0641               "    <shortcut>Ctrl+5</shortcut>\n"
0642               "    <state id=\"highlight\">\n"
0643               "      <backgroundColor>#ffffcc</backgroundColor>\n"
0644               "      <textEquivalent string=\"=>\" />\n"
0645               "    </state>\n"
0646               "  </tag>\n"
0647               "\n")
0648               .arg(i18n("Priority"), i18n("Low"), i18n("Medium"))      // %1 %2 %3
0649               .arg(i18n("High"), i18n("Preference"), i18n("Bad"))      // %4 %5 %6
0650               .arg(i18n("Good"), i18n("Excellent"), i18n("Highlight")) // %7 %8 %9
0651         + QString(
0652               "  <tag>\n"
0653               "    <name>%1</name>\n" // "Important"
0654               "    <shortcut>Ctrl+6</shortcut>\n"
0655               "    <state id=\"important\">\n"
0656               "      <emblem>tag_important</emblem>\n"
0657               "      <backgroundColor>#ffcccc</backgroundColor>\n"
0658               "      <textEquivalent string=\"!!\" />\n"
0659               "    </state>\n"
0660               "  </tag>\n"
0661               "\n"
0662               "  <tag>\n"
0663               "    <name>%2</name>\n" // "Very Important"
0664               "    <shortcut>Ctrl+7</shortcut>\n"
0665               "    <state id=\"very_important\">\n"
0666               "      <emblem>tag_important</emblem>\n"
0667               "      <text color=\"#ffffff\" />\n"
0668               "      <backgroundColor>#ff0000</backgroundColor>\n"
0669               "      <textEquivalent string=\"/!\\\" />\n"
0670               "    </state>\n"
0671               "  </tag>\n"
0672               "\n"
0673               "  <tag>\n"
0674               "    <name>%3</name>\n" // "Information"
0675               "    <shortcut>Ctrl+8</shortcut>\n"
0676               "    <state id=\"information\">\n"
0677               "      <emblem>dialog-information</emblem>\n"
0678               "      <textEquivalent string=\"(i)\" />\n"
0679               "    </state>\n"
0680               "  </tag>\n"
0681               "\n"
0682               "  <tag>\n"
0683               "    <name>%4</name>\n" // "Idea"
0684               "    <shortcut>Ctrl+9</shortcut>\n"
0685               "    <state id=\"idea\">\n"
0686               "      <emblem>ktip</emblem>\n"
0687               "      <textEquivalent string=\"%5\" />\n" // I.
0688               "    </state>\n"
0689               "  </tag>"
0690               "\n"
0691               "\n"
0692               "  <tag>\n"
0693               "    <name>%6</name>\n" // "Title"
0694               "    <shortcut>Ctrl+0</shortcut>\n"
0695               "    <state id=\"title\">\n"
0696               "      <text bold=\"true\" />\n"
0697               "      <textEquivalent string=\"##\" />\n"
0698               "    </state>\n"
0699               "  </tag>\n"
0700               "\n"
0701               "  <tag>\n"
0702               "    <name>%7</name>\n" // "Code"
0703               "    <state id=\"code\">\n"
0704               "      <font name=\"monospace\" />\n"
0705               "      <textEquivalent string=\"|\" onAllTextLines=\"true\" />\n"
0706               "      <allowCrossReferences>false</allowCrossReferences>\n"
0707               "    </state>\n"
0708               "  </tag>\n"
0709               "\n"
0710               "  <tag>\n"
0711               "    <state id=\"work\">\n"
0712               "      <name>%8</name>\n" // "Work"
0713               "      <text color=\"#ff8000\" />\n"
0714               "      <textEquivalent string=\"%9\" />\n" // W.
0715               "    </state>\n"
0716               "  </tag>"
0717               "\n"
0718               "\n")
0719               .arg(i18n("Important"), i18n("Very Important"), i18n("Information"))    // %1 %2 %3
0720               .arg(i18n("Idea"), i18nc("The initial of 'Idea'", "I."), i18n("Title")) // %4 %5 %6
0721               .arg(i18n("Code"), i18n("Work"), i18nc("The initial of 'Work'", "W."))  // %7 %8 %9
0722         + QString(
0723               "  <tag>\n"
0724               "    <state id=\"personal\">\n"
0725               "      <name>%1</name>\n" // "Personal"
0726               "      <text color=\"#008000\" />\n"
0727               "      <textEquivalent string=\"%2\" />\n" // P.
0728               "    </state>\n"
0729               "  </tag>\n"
0730               "\n"
0731               "  <tag>\n"
0732               "    <state id=\"funny\">\n"
0733               "      <name>%3</name>\n" // "Funny"
0734               "      <emblem>tag_fun</emblem>\n"
0735               "    </state>\n"
0736               "  </tag>\n"
0737               "</basketTags>\n"
0738               "")
0739               .arg(i18n("Personal"), i18nc("The initial of 'Personal'", "P."), i18n("Funny")); // %1 %2 %3
0740 
0741     // Write to Disk:
0742     QFile file(fullPath);
0743     if (file.open(QIODevice::WriteOnly)) {
0744         QTextStream stream(&file);
0745         stream.setCodec("UTF-8");
0746         stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
0747         stream << xml;
0748         file.close();
0749     } else
0750         DEBUG_WIN << "<font color=red>FAILED to create the tags file</font>!";
0751 }
0752 
0753 const QRegularExpression& Tag::regexpDetectTagsInPlainText()
0754 {
0755     return regexpDetectTags;
0756 }
0757 
0758 void Tag::updateCaches()
0759 {
0760     QString patternAllTags;
0761     for (Tag* tag: Tag::all)
0762     {
0763         for (State* state: tag->states())
0764         {
0765             QString textEquivalent = state->textEquivalent().trimmed();
0766             if (textEquivalent.isEmpty()) continue;
0767 
0768             dictStatesByEquiv[textEquivalent] = state;
0769             patternAllTags.append(QRegularExpression::escape(textEquivalent)).append("|");
0770         }
0771     }
0772     if (patternAllTags.isEmpty())
0773     {
0774         regexpDetectTags.setPattern(QString());
0775         return;
0776     }
0777 
0778     patternAllTags.chop(1);
0779     patternAllTags = QString(("\\G\\s*?(%1)")).arg(patternAllTags);
0780 
0781     regexpDetectTags = QRegularExpression(patternAllTags, QRegularExpression::UseUnicodePropertiesOption);
0782     regexpDetectTags.optimize();
0783 }
0784 
0785 // StateAction
0786 StateAction::StateAction(State *state, const QKeySequence &shortcut, QWidget *parent, bool withTagName)
0787     : KToggleAction(parent)
0788     , m_state(state)
0789 {
0790     setText(m_state->name());
0791 
0792     if (withTagName && m_state->parentTag())
0793         setText(m_state->parentTag()->name());
0794 
0795     setIcon(QIcon::fromTheme(m_state->emblem()));
0796 
0797     setShortcut(shortcut);
0798 }
0799 
0800 StateAction::~StateAction()
0801 {
0802     // pass
0803 }