File indexing completed on 2024-04-14 05:41:21
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 } 0804 0805 #include "moc_tag.cpp"