Updated the README file with some contributor tips.
[basket4.git] / src / tag.cpp
blobf8ed0059b78aa1c6cbfaff8e602b675b9252f3a1
1 /***************************************************************************
2 * Copyright (C) 2005 by S�astien Laot *
3 * slaout@linux62.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include <kapplication.h>
22 #include <kstyle.h>
23 #include <kiconloader.h>
24 #include <qpainter.h>
25 #include <qfont.h>
26 #include <qdom.h>
27 #include <qdir.h>
28 //Added by qt3to4:
29 #include <Q3TextStream>
30 #include <Q3ValueList>
31 #include <QPixmap>
32 #include <kglobalsettings.h>
33 #include <klocale.h>
35 #include "tag.h"
36 #include "xmlwork.h"
37 #include "global.h"
38 #include "debugwindow.h"
39 #include "bnpview.h"
40 #include "tools.h"
41 #include "basket.h"
43 #include <iostream>
45 /** class State: */
47 State::State(const QString &id, Tag *tag)
48 : m_id(id), m_name(), m_emblem(), m_bold(false), m_italic(false), m_underline(false), m_strikeOut(false),
49 m_textColor(), m_fontName(), m_fontSize(-1), m_backgroundColor(), m_textEquivalent(), m_onAllTextLines(false), m_parentTag(tag)
53 State::~State()
57 State* State::nextState(bool cycle /*= true*/)
59 if (!parentTag())
60 return 0;
62 List states = parentTag()->states();
63 // The tag contains only one state:
64 if (states.count() == 1)
65 return 0;
66 // Find the next state:
67 for (List::iterator it = states.begin(); it != states.end(); ++it)
68 // Found the current state in the list:
69 if (*it == this) {
70 // Find the next state:
71 State *next = *(++it);
72 if (it == states.end())
73 return (cycle ? states.first() : 0);
74 return next;
76 // Should not happens:
77 return 0;
80 QString State::fullName()
82 if (!parentTag() || parentTag()->states().count() == 1)
83 return (name().isEmpty() && parentTag() ? parentTag()->name() : name());
84 return QString(i18n("%1: %2")).arg(parentTag()->name(), name());
87 QFont State::font(QFont base)
89 if (bold())
90 base.setBold(true);
91 if (italic())
92 base.setItalic(true);
93 if (underline())
94 base.setUnderline(true);
95 if (strikeOut())
96 base.setStrikeOut(true);
97 if (!fontName().isEmpty())
98 base.setFamily(fontName());
99 if (fontSize() > 0)
100 base.setPointSize(fontSize());
101 return base;
104 QString State::toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont)
106 QString css;
107 if (bold())
108 css += " font-weight: bold;";
109 if (italic())
110 css += " font-style: italic;";
111 if (underline() && strikeOut())
112 css += " text-decoration: underline line-through;";
113 else if (underline())
114 css += " text-decoration: underline;";
115 else if (strikeOut())
116 css += " text-decoration: line-through;";
117 if (textColor().isValid())
118 css += " color: " + textColor().name() + ";";
119 if (!fontName().isEmpty()) {
120 QString fontFamily = Tools::cssFontDefinition(fontName(), /*onlyFontFamily=*/true);
121 css += " font-family: " + fontFamily + ";";
123 if (fontSize() > 0)
124 css += " font-size: " + QString::number(fontSize()) + "px;";
125 if (backgroundColor().isValid()) {
126 // Get the colors of the gradient and the border:
127 QColor topBgColor;
128 QColor bottomBgColor;
129 Note::getGradientColors(backgroundColor(), &topBgColor, &bottomBgColor);
130 // Produce the CSS code:
131 QString gradientFileName = Basket::saveGradientBackground(backgroundColor(), font(baseFont), gradientFolderPath);
132 css += " background: " + bottomBgColor.name() + " url('" + gradientFolderName + gradientFileName + "') repeat-x;";
133 css += " border-top: solid " + topBgColor.name() + " 1px;";
134 css += " border-bottom: solid " + Tools::mixColor(topBgColor, bottomBgColor).name() + " 1px;";
137 if (css.isEmpty())
138 return "";
139 else
140 return " .tag_" + id() + " {" + css + " }\n";
143 void State::merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor)
145 *result = State(); // Reset to default values.
146 *emblemsCount = 0;
147 *haveInvisibleTags = false;
149 for (List::const_iterator it = states.begin(); it != states.end(); ++it) {
150 State *state = *it;
151 bool isVisible = false;
152 // For each propertie, if that properties have a value (is not default) is the current state of the list,
153 // and if it haven't been set to the result state by a previous state, then it's visible and we assign the propertie to the result state.
154 if (!state->emblem().isEmpty()) {
155 ++*emblemsCount;
156 isVisible = true;
158 if (state->bold() && !result->bold()) {
159 result->setBold(true);
160 isVisible = true;
162 if (state->italic() && !result->italic()) {
163 result->setItalic(true);
164 isVisible = true;
166 if (state->underline() && !result->underline()) {
167 result->setUnderline(true);
168 isVisible = true;
170 if (state->strikeOut() && !result->strikeOut()) {
171 result->setStrikeOut(true);
172 isVisible = true;
174 if (state->textColor().isValid() && !result->textColor().isValid()) {
175 result->setTextColor(state->textColor());
176 isVisible = true;
178 if (!state->fontName().isEmpty() && result->fontName().isEmpty()) {
179 result->setFontName(state->fontName());
180 isVisible = true;
182 if (state->fontSize() > 0 && result->fontSize() <= 0) {
183 result->setFontSize(state->fontSize());
184 isVisible = true;
186 if (state->backgroundColor().isValid() && !result->backgroundColor().isValid() && state->backgroundColor() != backgroundColor) { // vv
187 result->setBackgroundColor(state->backgroundColor()); // This is particular: if the note background color is the same as the basket one, don't use that.
188 isVisible = true;
190 // 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:
191 if (!isVisible)
192 *haveInvisibleTags = true;
196 void State::copyTo(State *other)
198 other->m_id = m_id;
199 other->m_name = m_name;
200 other->m_emblem = m_emblem;
201 other->m_bold = m_bold;
202 other->m_italic = m_italic;
203 other->m_underline = m_underline;
204 other->m_strikeOut = m_strikeOut;
205 other->m_textColor = m_textColor;
206 other->m_fontName = m_fontName;
207 other->m_fontSize = m_fontSize;
208 other->m_backgroundColor = m_backgroundColor;
209 other->m_textEquivalent = m_textEquivalent;
210 other->m_onAllTextLines = m_onAllTextLines; // TODO
211 //TODO: other->m_parentTag;
214 /** class Tag: */
216 Tag::List Tag::all = Tag::List();
218 long Tag::nextStateUid = 1;
220 long Tag::getNextStateUid()
222 return nextStateUid++; // Return the next Uid and THEN increment the Uid
225 Tag::Tag()
227 static int tagNumber = 0;
228 ++tagNumber;
229 QString sAction = "tag_shortcut_number_" + QString::number(tagNumber);
230 m_action = new KAction("FAKE TEXT", "FAKE ICON", KShortcut(), Global::bnpView, SLOT(activatedTagShortcut()), Global::bnpView->actionCollection(), sAction);
231 m_action->setShortcutConfigurable(false); // We do it in the tag properties dialog
233 m_inheritedBySiblings = false;
236 Tag::~Tag()
238 delete m_action;
241 void Tag::setName(const QString &name)
243 m_name = name;
244 m_action->setText("TAG SHORTCUT: " + name); // TODO: i18n (for debug purpose only by now).
247 State* Tag::stateForId(const QString &id)
249 for (List::iterator it = all.begin(); it != all.end(); ++it)
250 for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2)
251 if ((*it2)->id() == id)
252 return *it2;
253 return 0;
256 Tag* Tag::tagForKAction(KAction *action)
258 for (List::iterator it = all.begin(); it != all.end(); ++it)
259 if ((*it)->m_action == action)
260 return *it;
261 return 0;
264 QMap<QString, QString> Tag::loadTags(const QString &path/* = QString()*//*, bool merge = false*/)
266 QMap<QString, QString> mergedStates;
268 bool merge = !path.isEmpty();
269 QString fullPath = (merge ? path : Global::savesFolder() + "tags.xml");
270 QString doctype = "basketTags";
272 QDir dir;
273 if (!dir.exists(fullPath)) {
274 if (merge)
275 return mergedStates;
276 DEBUG_WIN << "Tags file does not exist: Creating it...";
277 createDefaultTagsSet(fullPath);
280 QDomDocument *document = XMLWork::openFile(doctype, fullPath);
281 if (!document) {
282 DEBUG_WIN << "<font color=red>FAILED to read the tags file</font>";
283 return mergedStates;
286 QDomElement docElem = document->documentElement();
287 if (!merge)
288 nextStateUid = docElem.attribute("nextStateUid", QString::number(nextStateUid)).toLong();
290 QDomNode node = docElem.firstChild();
291 while (!node.isNull()) {
292 QDomElement element = node.toElement();
293 if ( (!element.isNull()) && element.tagName() == "tag" ) {
294 Tag *tag = new Tag();
295 // Load properties:
296 QString name = XMLWork::getElementText(element, "name");
297 QString shortcut = XMLWork::getElementText(element, "shortcut");
298 QString inherited = XMLWork::getElementText(element, "inherited", "false");
299 tag->setName(name);
300 tag->setShortcut(KShortcut(shortcut));
301 tag->setInheritedBySiblings(XMLWork::trueOrFalse(inherited));
302 // Load states:
303 QDomNode subNode = element.firstChild();
304 while (!subNode.isNull()) {
305 QDomElement subElement = subNode.toElement();
306 if ( (!subElement.isNull()) && subElement.tagName() == "state" ) {
307 State *state = new State(subElement.attribute("id"), tag);
308 state->setName( XMLWork::getElementText(subElement, "name") );
309 state->setEmblem( XMLWork::getElementText(subElement, "emblem") );
310 QDomElement textElement = XMLWork::getElement(subElement, "text");
311 state->setBold( XMLWork::trueOrFalse(textElement.attribute("bold", "false")) );
312 state->setItalic( XMLWork::trueOrFalse(textElement.attribute("italic", "false")) );
313 state->setUnderline( XMLWork::trueOrFalse(textElement.attribute("underline", "false")) );
314 state->setStrikeOut( XMLWork::trueOrFalse(textElement.attribute("strikeOut", "false")) );
315 QString textColor = textElement.attribute("color", "");
316 state->setTextColor(textColor.isEmpty() ? QColor() : QColor(textColor));
317 QDomElement fontElement = XMLWork::getElement(subElement, "font");
318 state->setFontName(fontElement.attribute("name", ""));
319 QString fontSize = fontElement.attribute("size", "");
320 state->setFontSize(fontSize.isEmpty() ? -1 : fontSize.toInt());
321 QString backgroundColor = XMLWork::getElementText(subElement, "backgroundColor", "");
322 state->setBackgroundColor(backgroundColor.isEmpty() ? QColor() : QColor(backgroundColor));
323 QDomElement textEquivalentElement = XMLWork::getElement(subElement, "textEquivalent");
324 state->setTextEquivalent( textEquivalentElement.attribute("string", "") );
325 state->setOnAllTextLines( XMLWork::trueOrFalse(textEquivalentElement.attribute("onAllTextLines", "false")) );
326 tag->appendState(state);
328 subNode = subNode.nextSibling();
330 // If the Tag is Valid:
331 if (tag->countStates() > 0) {
332 // Rename Things if Needed:
333 State *firstState = tag->states().first();
334 if (tag->countStates() == 1 && firstState->name().isEmpty())
335 firstState->setName(tag->name());
336 if (tag->name().isEmpty())
337 tag->setName(firstState->name());
338 // Add or Merge the Tag:
339 if (!merge) {
340 all.append(tag);
341 } else {
342 Tag *similarTag = tagSimilarTo(tag);
343 // Tag does not exists, add it:
344 if (similarTag == 0) {
345 // We are merging the new states, so we should choose new and unique (on that computer) ids for those states:
346 for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it) {
347 State *state = *it;
348 QString uid = state->id();
349 QString newUid = "tag_state_" + QString::number(getNextStateUid());
350 state->setId(newUid);
351 mergedStates[uid] = newUid;
353 // TODO: if shortcut is already assigned to a previous note, do not import it, keep the user settings!
354 all.append(tag);
355 // Tag already exists, rename to theire ids:
356 } else {
357 State::List::iterator it2 = similarTag->states().begin();
358 for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it, ++it2) {
359 State *state = *it;
360 State *similarState = *it2;
361 QString uid = state->id();
362 QString newUid = similarState->id();
363 if (uid != newUid)
364 mergedStates[uid] = newUid;
366 delete tag; // Already exists, not to be merged. Delete the shortcut and all.
371 node = node.nextSibling();
374 return mergedStates;
377 Tag* Tag::tagSimilarTo(Tag *tagToTest)
379 // Tags are considered similar if they have the same name, the same number of states, in the same order, and the same look.
380 // Keyboard shortcut, text equivalent and onEveryLines are user settings, and thus not considered during the comparision.
381 // Default tags (To Do, Important, Idea...) do not take into account the name of the tag and states during the comparision.
382 // Default tags are equal only if they have the same number of states, in the same order, and the same look.
383 // This is because default tag names are translated differently in every countries, but they are essentialy the same!
384 // User tags begins with "tag_state_" followed by a number. Default tags are the other ones.
386 // Browse all tags:
387 for (List::iterator it = all.begin(); it != all.end(); ++it) {
388 Tag *tag = *it;
389 bool same = true;
390 bool sameName;
391 bool defaultTag = true;
392 // We test only name and look. Shorcut and whenever it is inherited by sibling new notes are user settings only!
393 sameName = tag->name() == tagToTest->name();
394 if (tag->countStates() != tagToTest->countStates())
395 continue; // Tag is different!
396 // We found a tag with same name, check if every states/look are same too:
397 State::List::iterator itTest = tagToTest->states().begin();
398 for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2, ++itTest) {
399 State *state = *it2;
400 State *stateToTest = *itTest;
401 if (state->id().startsWith("tag_state_") || stateToTest->id().startsWith("tag_state_")) { defaultTag = false; }
402 if (state->name() != stateToTest->name()) { sameName = false; }
403 if (state->emblem() != stateToTest->emblem()) { same = false; break; }
404 if (state->bold() != stateToTest->bold()) { same = false; break; }
405 if (state->italic() != stateToTest->italic()) { same = false; break; }
406 if (state->underline() != stateToTest->underline()) { same = false; break; }
407 if (state->strikeOut() != stateToTest->strikeOut()) { same = false; break; }
408 if (state->textColor() != stateToTest->textColor()) { same = false; break; }
409 if (state->fontName() != stateToTest->fontName()) { same = false; break; }
410 if (state->fontSize() != stateToTest->fontSize()) { same = false; break; }
411 if (state->backgroundColor() != stateToTest->backgroundColor()) { same = false; break; }
412 // Text equivalent (as well as onAllTextLines) is also a user setting!
414 // We found an existing tag that is "exactly" the same:
415 if (same && (sameName || defaultTag))
416 return tag;
419 // Not found:
420 return 0;
423 void Tag::saveTags()
425 DEBUG_WIN << "Saving tags...";
426 saveTagsTo(all, Global::savesFolder() + "tags.xml");
429 void Tag::saveTagsTo(Q3ValueList<Tag*> &list, const QString &fullPath)
431 // Create Document:
432 QDomDocument document(/*doctype=*/"basketTags");
433 QDomElement root = document.createElement("basketTags");
434 root.setAttribute("nextStateUid", nextStateUid);
435 document.appendChild(root);
437 // Save all tags:
438 for (List::iterator it = list.begin(); it != list.end(); ++it) {
439 Tag *tag = *it;
440 // Create tag node:
441 QDomElement tagNode = document.createElement("tag");
442 root.appendChild(tagNode);
443 // Save tag properties:
444 XMLWork::addElement( document, tagNode, "name", tag->name() );
445 XMLWork::addElement( document, tagNode, "shortcut", tag->shortcut().toStringInternal() );
446 XMLWork::addElement( document, tagNode, "inherited", XMLWork::trueOrFalse(tag->inheritedBySiblings()) );
447 // Save all states:
448 for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) {
449 State *state = *it2;
450 // Create state node:
451 QDomElement stateNode = document.createElement("state");
452 tagNode.appendChild(stateNode);
453 // Save state properties:
454 stateNode.setAttribute("id", state->id());
455 XMLWork::addElement( document, stateNode, "name", state->name() );
456 XMLWork::addElement( document, stateNode, "emblem", state->emblem() );
457 QDomElement textNode = document.createElement("text");
458 stateNode.appendChild(textNode);
459 QString textColor = (state->textColor().isValid() ? state->textColor().name() : "");
460 textNode.setAttribute( "bold", XMLWork::trueOrFalse(state->bold()) );
461 textNode.setAttribute( "italic", XMLWork::trueOrFalse(state->italic()) );
462 textNode.setAttribute( "underline", XMLWork::trueOrFalse(state->underline()) );
463 textNode.setAttribute( "strikeOut", XMLWork::trueOrFalse(state->strikeOut()) );
464 textNode.setAttribute( "color", textColor );
465 QDomElement fontNode = document.createElement("font");
466 stateNode.appendChild(fontNode);
467 fontNode.setAttribute( "name", state->fontName() );
468 fontNode.setAttribute( "size", state->fontSize() );
469 QString backgroundColor = (state->backgroundColor().isValid() ? state->backgroundColor().name() : "");
470 XMLWork::addElement( document, stateNode, "backgroundColor", backgroundColor );
471 QDomElement textEquivalentNode = document.createElement("textEquivalent");
472 stateNode.appendChild(textEquivalentNode);
473 textEquivalentNode.setAttribute( "string", state->textEquivalent() );
474 textEquivalentNode.setAttribute( "onAllTextLines", XMLWork::trueOrFalse(state->onAllTextLines()) );
478 // Write to Disk:
479 if (!Basket::safelySaveToFile(fullPath, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + document.toString()))
480 DEBUG_WIN << "<font color=red>FAILED to save tags</font>!";
483 void Tag::copyTo(Tag *other)
485 other->m_name = m_name;
486 other->m_action->setShortcut(m_action->shortcut());
487 other->m_inheritedBySiblings = m_inheritedBySiblings;
490 void Tag::createDefaultTagsSet(const QString &fullPath)
492 QString xml = QString(
493 "<!DOCTYPE basketTags>\n"
494 "<basketTags>\n"
495 " <tag>\n"
496 " <name>%1</name>\n" // "To Do"
497 " <shortcut>Ctrl+1</shortcut>\n"
498 " <inherited>true</inherited>\n"
499 " <state id=\"todo_unchecked\">\n"
500 " <name>%2</name>\n" // "Unchecked"
501 " <emblem>tag_checkbox</emblem>\n"
502 " <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"false\" color=\"\" />\n"
503 " <font name=\"\" size=\"\" />\n"
504 " <backgroundColor></backgroundColor>\n"
505 " <textEquivalent string=\"[ ]\" onAllTextLines=\"false\" />\n"
506 " </state>\n"
507 " <state id=\"todo_done\">\n"
508 " <name>%3</name>\n" // "Done"
509 " <emblem>tag_checkbox_checked</emblem>\n"
510 " <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"true\" color=\"\" />\n"
511 " <font name=\"\" size=\"\" />\n"
512 " <backgroundColor></backgroundColor>\n"
513 " <textEquivalent string=\"[x]\" onAllTextLines=\"false\" />\n"
514 " </state>\n"
515 " </tag>\n"
516 "\n"
517 " <tag>\n"
518 " <name>%4</name>\n" // "Progress"
519 " <shortcut>Ctrl+2</shortcut>\n"
520 " <inherited>true</inherited>\n"
521 " <state id=\"progress_000\">\n"
522 " <name>%5</name>\n" // "0 %"
523 " <emblem>tag_progress_000</emblem>\n"
524 " <textEquivalent string=\"[ ]\" />\n"
525 " </state>\n"
526 " <state id=\"progress_025\">\n"
527 " <name>%6</name>\n" // "25 %"
528 " <emblem>tag_progress_025</emblem>\n"
529 " <textEquivalent string=\"[= ]\" />\n"
530 " </state>\n"
531 " <state id=\"progress_050\">\n"
532 " <name>%7</name>\n" // "50 %"
533 " <emblem>tag_progress_050</emblem>\n"
534 " <textEquivalent string=\"[== ]\" />\n"
535 " </state>\n"
536 " <state id=\"progress_075\">\n"
537 " <name>%8</name>\n" // "75 %"
538 " <emblem>tag_progress_075</emblem>\n"
539 " <textEquivalent string=\"[=== ]\" />\n"
540 " </state>\n"
541 " <state id=\"progress_100\">\n"
542 " <name>%9</name>\n" // "100 %"
543 " <emblem>tag_progress_100</emblem>\n"
544 " <textEquivalent string=\"[====]\" />\n"
545 " </state>\n"
546 " </tag>\n"
547 "\n")
548 .arg( i18n("To Do"), i18n("Unchecked"), i18n("Done") ) // %1 %2 %3
549 .arg( i18n("Progress"), i18n("0 %"), i18n("25 %") ) // %4 %5 %6
550 .arg( i18n("50 %"), i18n("75 %"), i18n("100 %") ) // %7 %8 %9
551 + QString(
552 " <tag>\n"
553 " <name>%1</name>\n" // "Priority"
554 " <shortcut>Ctrl+3</shortcut>\n"
555 " <inherited>true</inherited>\n"
556 " <state id=\"priority_low\">\n"
557 " <name>%2</name>\n" // "Low"
558 " <emblem>tag_priority_low</emblem>\n"
559 " <textEquivalent string=\"{1}\" />\n"
560 " </state>\n"
561 " <state id=\"priority_medium\">\n"
562 " <name>%3</name>\n" // "Medium
563 " <emblem>tag_priority_medium</emblem>\n"
564 " <textEquivalent string=\"{2}\" />\n"
565 " </state>\n"
566 " <state id=\"priority_high\">\n"
567 " <name>%4</name>\n" // "High"
568 " <emblem>tag_priority_high</emblem>\n"
569 " <textEquivalent string=\"{3}\" />\n"
570 " </state>\n"
571 " </tag>\n"
572 "\n"
573 " <tag>\n"
574 " <name>%5</name>\n" // "Preference"
575 " <shortcut>Ctrl+4</shortcut>\n"
576 " <inherited>true</inherited>\n"
577 " <state id=\"preference_bad\">\n"
578 " <name>%6</name>\n" // "Bad"
579 " <emblem>tag_preference_bad</emblem>\n"
580 " <textEquivalent string=\"(* )\" />\n"
581 " </state>\n"
582 " <state id=\"preference_good\">\n"
583 " <name>%7</name>\n" // "Good"
584 " <emblem>tag_preference_good</emblem>\n"
585 " <textEquivalent string=\"(** )\" />\n"
586 " </state>\n"
587 " <state id=\"preference_excelent\">\n"
588 " <name>%8</name>\n" // "Excellent"
589 " <emblem>tag_preference_excelent</emblem>\n" // "excelent": typo error, but we should keep compatibility with old versions.
590 " <textEquivalent string=\"(***)\" />\n"
591 " </state>\n"
592 " </tag>\n"
593 "\n"
594 " <tag>\n"
595 " <name>%9</name>\n" // "Highlight"
596 " <shortcut>Ctrl+5</shortcut>\n"
597 " <state id=\"highlight\">\n"
598 " <backgroundColor>#ffffcc</backgroundColor>\n"
599 " <textEquivalent string=\"=>\" />\n"
600 " </state>\n"
601 " </tag>\n"
602 "\n")
603 .arg( i18n("Priority"), i18n("Low"), i18n("Medium") ) // %1 %2 %3
604 .arg( i18n("High"), i18n("Preference"), i18n("Bad") ) // %4 %5 %6
605 .arg( i18n("Good"), i18n("Excellent"), i18n("Highlight") ) // %7 %8 %9
606 + QString(
607 " <tag>\n"
608 " <name>%1</name>\n" // "Important"
609 " <shortcut>Ctrl+6</shortcut>\n"
610 " <state id=\"important\">\n"
611 " <emblem>tag_important</emblem>\n"
612 " <backgroundColor>#ffcccc</backgroundColor>\n"
613 " <textEquivalent string=\"!!\" />\n"
614 " </state>\n"
615 " </tag>\n"
616 "\n"
617 " <tag>\n"
618 " <name>%2</name>\n" // "Very Important"
619 " <shortcut>Ctrl+7</shortcut>\n"
620 " <state id=\"very_important\">\n"
621 " <emblem>tag_important</emblem>\n"
622 " <text color=\"#ffffff\" />\n"
623 " <backgroundColor>#ff0000</backgroundColor>\n"
624 " <textEquivalent string=\"/!\\\" />\n"
625 " </state>\n"
626 " </tag>\n"
627 "\n"
628 " <tag>\n"
629 " <name>%3</name>\n" // "Information"
630 " <shortcut>Ctrl+8</shortcut>\n"
631 " <state id=\"information\">\n"
632 " <emblem>messagebox_info</emblem>\n"
633 " <textEquivalent string=\"(i)\" />\n"
634 " </state>\n"
635 " </tag>\n"
636 "\n"
637 " <tag>\n"
638 " <name>%4</name>\n" // "Idea"
639 " <shortcut>Ctrl+9</shortcut>\n"
640 " <state id=\"idea\">\n"
641 " <emblem>ktip</emblem>\n"
642 " <textEquivalent string=\"%5\" />\n" // I.
643 " </state>\n"
644 " </tag>""\n"
645 "\n"
646 " <tag>\n"
647 " <name>%6</name>\n" // "Title"
648 " <shortcut>Ctrl+0</shortcut>\n"
649 " <state id=\"title\">\n"
650 " <text bold=\"true\" />\n"
651 " <textEquivalent string=\"##\" />\n"
652 " </state>\n"
653 " </tag>\n"
654 "\n"
655 " <tag>\n"
656 " <name>%7</name>\n" // "Code"
657 " <state id=\"code\">\n"
658 " <font name=\"monospace\" />\n"
659 " <textEquivalent string=\"|\" onAllTextLines=\"true\" />\n"
660 " </state>\n"
661 " </tag>\n"
662 "\n"
663 " <tag>\n"
664 " <state id=\"work\">\n"
665 " <name>%8</name>\n" // "Work"
666 " <text color=\"#ff8000\" />\n"
667 " <textEquivalent string=\"%9\" />\n" // W.
668 " </state>\n"
669 " </tag>""\n"
670 "\n")
671 .arg( i18n("Important"), i18n("Very Important"), i18n("Information") ) // %1 %2 %3
672 .arg( i18n("Idea"), i18n("The initial of 'Idea'", "I."), i18n("Title") ) // %4 %5 %6
673 .arg( i18n("Code"), i18n("Work"), i18n("The initial of 'Work'", "W.") ) // %7 %8 %9
674 + QString(
675 " <tag>\n"
676 " <state id=\"personal\">\n"
677 " <name>%1</name>\n" // "Personal"
678 " <text color=\"#008000\" />\n"
679 " <textEquivalent string=\"%2\" />\n" // P.
680 " </state>\n"
681 " </tag>\n"
682 "\n"
683 " <tag>\n"
684 " <state id=\"funny\">\n"
685 " <name>%3</name>\n" // "Funny"
686 " <emblem>tag_fun</emblem>\n"
687 " </state>\n"
688 " </tag>\n"
689 "</basketTags>\n"
691 .arg( i18n("Personal"), i18n("The initial of 'Personal'", "P."), i18n("Funny") ); // %1 %2 %3
693 // Write to Disk:
694 QFile file(fullPath);
695 if (file.open(QIODevice::WriteOnly)) {
696 Q3TextStream stream(&file);
697 stream.setEncoding(Q3TextStream::UnicodeUTF8);
698 stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
699 stream << xml;
700 file.close();
701 } else
702 DEBUG_WIN << "<font color=red>FAILED to create the tags file</font>!";
705 #include <kapplication.h>
706 #include <qrect.h>
707 #include <qstyle.h>
708 #include <qcheckbox.h>
709 #include <qbitmap.h>
710 #include <kglobalsettings.h>
711 #include <qimage.h>
712 #include <qradiobutton.h>
713 #include <kiconeffect.h>
715 /** class IndentedMenuItem: */
717 IndentedMenuItem::IndentedMenuItem(const QString &text, const QString &icon, const QString &shortcut)
718 : m_text(text), m_icon(icon), m_shortcut(shortcut)
722 IndentedMenuItem::~IndentedMenuItem()
726 void IndentedMenuItem::paint(QPainter *painter, const QColorGroup &cg, bool active, bool enabled, int x, int y, int w, int h)
728 QPen pen = painter->pen();
729 QFont font = painter->font();
731 int iconSize = KIconLoader::SizeSmall;
732 int iconMargin = StateMenuItem::iconMargin();
734 /* When an item is disabled, it often have a 3D sunken look.
735 * This is done by calling this paint routine two times, with different pen color and offset.
736 * A disabled item is first painted in the rect (x+1, y+1, w, h) and with pen of cg.light() color,
737 * It is then drawn a second time in the rect (x, y, w, h).
738 * But we don't want to draw the icon two times! So, we try to detect if we are in the "etched-text draw" state and then don't draw the icon.
739 * This doesn't work for every styles but it's already better than nothing (styles when it doesn't work are seldomly used, if used).
741 bool drawingEtchedText = !enabled && !active && painter->pen().color() != cg.mid()/*== cg.foreground()*/;
742 if (drawingEtchedText) {
743 QString styleName = kapp->style().name();
744 if (styleName == "plastik" || styleName == "lipstik")
745 painter->setPen(cg.light());
746 drawingEtchedText = !enabled && !active && painter->pen().color() != cg.foreground();
747 } else
748 drawingEtchedText = !enabled && !active && painter->pen().color() == cg.light();
749 if (!m_icon.isEmpty() && !drawingEtchedText) {
750 QPixmap icon = kapp->iconLoader()->loadIcon(m_icon, KIcon::Small, iconSize,
751 (enabled ? (active ? KIcon::ActiveState : KIconLoader::DefaultState) : KIcon::DisabledState),
752 /*path_store=*/0L, /*canReturnNull=*/true);
753 painter->drawPixmap(x, y + (h-iconSize)/2, icon);
755 /* Pen and font are already set to the good ones, so we can directly draw the text.
756 * BUT, for the half of styles provided with KDE, the pen is not set for the Active state (when hovered by mouse of selected by keyboard).
757 * So, I set the pen myself.
758 * But it's certainly a bug in those styles because some other styles eg. just draw a 3D sunken rect when an item is selected
759 * and keep the background to white, drawing a white text over it is... very bad. But I can't see what can be done.
761 if (active && enabled)
762 painter->setPen(KGlobalSettings::highlightedTextColor());
763 painter->drawText(x + iconSize + iconMargin, y, w - iconSize - iconMargin, h, Qt::AlignLeft | Qt::AlignVCenter | DontClip | ShowPrefix, m_text/*painter->pen().color().name()*/);
765 if (!m_shortcut.isEmpty()) {
766 painter->setPen(pen);
767 if (active && enabled)
768 painter->setPen(KGlobalSettings::highlightedTextColor());
769 painter->setFont(font);
770 painter->setClipping(false);
771 painter->drawText(x + 5 + w, y, 3000, h, Qt::AlignLeft | Qt::AlignVCenter | DontClip | ShowPrefix, m_shortcut);
775 QSize IndentedMenuItem::sizeHint()
777 int iconSize = KIconLoader::SizeSmall;
778 int iconMargin = StateMenuItem::iconMargin();
779 QSize textSize = QFontMetrics(KGlobalSettings::menuFont()).size( Qt::AlignLeft | Qt::AlignVCenter | ShowPrefix | DontClip, m_text );
780 return QSize(iconSize + iconMargin + textSize.width(), textSize.height());
783 /** class StateMenuItem: */
785 StateMenuItem::StateMenuItem(State *state, const QString &shortcut, bool withTagName)
786 : m_state(state), m_shortcut(shortcut)
788 m_name = (withTagName && m_state->parentTag() ? m_state->parentTag()->name() : m_state->name());
791 StateMenuItem::~StateMenuItem()
795 void StateMenuItem::paint(QPainter *painter, const QColorGroup &cg, bool active, bool enabled, int x, int y, int w, int h)
797 QPen pen = painter->pen();
798 QFont font = painter->font();
800 int iconSize = 16; // We use 16 instead of KIconLoader::SizeSmall (the size of icons in menus) because tags will always be 16*16 icons
802 if (!active && m_state->backgroundColor().isValid())
803 painter->fillRect(x/*-1*/, y/*-1*/, w/*+2*/, h/*+2*/, m_state->backgroundColor());
804 /* When an item is disabled, it often have a 3D sunken look.
805 * This is done by calling this paint routine two times, with different pen color and offset.
806 * A disabled item is first painted in the rect (x+1, y+1, w, h) and with pen of cg.light() color,
807 * It is then drawn a second time in the rect (x, y, w, h).
808 * But we don't want to draw the icon two times! So, we try to detect if we are in the "etched-text draw" state and then don't draw the icon.
809 * This doesn't work for every styles but it's already better than nothing (styles when it doesn't work are seldomly used, if used).
811 bool drawingEtchedText = !enabled && !active && painter->pen().color() != cg.mid()/*== cg.foreground()*/;
812 if (drawingEtchedText) {
813 QString styleName = kapp->style().name();
814 if (styleName == "plastik" || styleName == "lipstik")
815 painter->setPen(cg.light());
816 drawingEtchedText = !enabled && !active && painter->pen().color() != cg.foreground();
817 } else
818 drawingEtchedText = !enabled && !active && painter->pen().color() == cg.light();
819 if (!m_state->emblem().isEmpty() && !drawingEtchedText) {
820 QPixmap icon = kapp->iconLoader()->loadIcon(m_state->emblem(), KIcon::Small, iconSize,
821 (enabled ? (active ? KIcon::ActiveState : KIconLoader::DefaultState) : KIcon::DisabledState),
822 /*path_store=*/0L, /*canReturnNull=*/true);
823 painter->drawPixmap(x, y + (h-iconSize)/2, icon);
825 if (enabled && !active && m_state->textColor().isValid())
826 painter->setPen(m_state->textColor());
827 /* Pen and font are already set to the good ones, so we can directly draw the text.
828 * BUT, for the half of styles provided with KDE, the pen is not set for the Active state (when hovered by mouse of selected by keyboard).
829 * So, I set the pen myself.
830 * But it's certainly a bug in those styles because some other styles eg. just draw a 3D sunken rect when an item is selected
831 * and keep the background to white, drawing a white text over it is... very bad. But I can't see what can be done.
833 if (active && enabled)
834 painter->setPen(KGlobalSettings::highlightedTextColor());
835 painter->setFont( m_state->font(painter->font()) );
836 painter->drawText(x + iconSize + iconMargin(), y, w - iconSize - iconMargin(), h, Qt::AlignLeft | Qt::AlignVCenter | DontClip | ShowPrefix, m_name);
838 if (!m_shortcut.isEmpty()) {
839 painter->setPen(pen);
840 if (active && enabled)
841 painter->setPen(KGlobalSettings::highlightedTextColor());
842 painter->setFont(font);
843 painter->setClipping(false);
844 painter->drawText(x + 5 + w, y, 3000, h, Qt::AlignLeft | Qt::AlignVCenter | DontClip | ShowPrefix, m_shortcut);
848 QSize StateMenuItem::sizeHint()
850 int iconSize = 16; // We use 16 instead of KIconLoader::SizeSmall (the size of icons in menus) because tags will always be 16*16 icons
851 QFont theFont = m_state->font(KGlobalSettings::menuFont());
852 QSize textSize = QFontMetrics(theFont).size( Qt::AlignLeft | Qt::AlignVCenter | ShowPrefix | DontClip, m_name );
853 return QSize(iconSize + iconMargin() + textSize.width(), textSize.height());
856 QIcon StateMenuItem::checkBoxIconSet(bool checked, QColorGroup cg)
858 int width = kapp->style().pixelMetric(QStyle::PM_IndicatorWidth, 0);
859 int height = kapp->style().pixelMetric(QStyle::PM_IndicatorHeight, 0);
860 QRect rect(0, 0, width, height);
862 QColor menuBackgroundColor = (dynamic_cast<KStyle*>(&(kapp->style())) == NULL ? cg.background() : cg.background().light(103));
864 // Enabled, Not hovering
865 QPixmap pixmap(width, height);
866 pixmap.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws
867 QPainter painter(&pixmap);
868 int style = QStyle::State_Enabled | QStyle::Style_Active | (checked ? QStyle::State_On : QStyle::State_Off);
869 QColor background = cg.color(QColorGroup::Background);
870 kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style);
871 painter.end();
873 // Enabled, Hovering
874 QPixmap pixmapHover(width, height);
875 pixmapHover.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws
876 painter.begin(&pixmapHover);
877 style |= QStyle::Style_MouseOver;
878 cg.setColor(QColorGroup::Background, KGlobalSettings::highlightColor());
879 kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style);
880 painter.end();
882 // Disabled
883 QPixmap pixmapDisabled(width, height);
884 pixmapDisabled.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws
885 painter.begin(&pixmapDisabled);
886 style = /*QStyle::State_Enabled | */QStyle::Style_Active | (checked ? QStyle::State_On : QStyle::State_Off);
887 cg.setColor(QColorGroup::Background, background);
888 kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style);
889 painter.end();
891 QIcon iconSet(pixmap);
892 iconSet.setPixmap(pixmapHover, QIcon::Automatic, QIcon::Active);
893 iconSet.setPixmap(pixmapDisabled, QIcon::Automatic, QIcon::Disabled);
894 return iconSet;
897 QIcon StateMenuItem::radioButtonIconSet(bool checked, QColorGroup cg)
899 int width = kapp->style().pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, 0);
900 int height = kapp->style().pixelMetric(QStyle::PM_ExclusiveIndicatorHeight, 0);
901 QRect rect(0, 0, width, height);
903 int style = QStyle::Style_Default | QStyle::State_Enabled | (checked ? QStyle::State_On : QStyle::State_Off);
905 QPixmap pixmap(width, height);
906 pixmap.fill(Qt::red);
907 QPainter painter(&pixmap);
908 /* We can't use that line of code (like for checkboxes):
909 * //kapp->style().drawPrimitive(QStyle::PE_ExclusiveIndicator, &painter, rect, cg, style);
910 * because Plastik (and derived styles) don't care of the QStyle::State_On flag and will ALWAYS draw an unchecked radiobutton.
911 * So, we use another method:
913 QRadioButton rb(0);
914 rb.setChecked(checked);
915 kapp->style().drawControl(QStyle::CE_RadioButton, &painter, &rb, rect, cg, style);
916 painter.end();
917 /* Some styles like Plastik (and derived ones) have QStyle::PE_ExclusiveIndicator drawing a radiobutton disc, as wanted,
918 * and leave pixels ouside it untouched, BUT QStyle::PE_ExclusiveIndicatorMask is a fully black square.
919 * So, we can't apply the mask to make the radiobutton circle transparent outside.
920 * We're using an hack by filling the pixmap in Qt::red, drawing the radiobutton and then creating an heuristic mask.
921 * The heuristic mask is created using the 4 edge pixels (that are red) and by making transparent every pixels that are of this color:
923 pixmap.setMask(pixmap.createHeuristicMask());
925 QPixmap pixmapHover(width, height);
926 pixmapHover.fill(Qt::red);
927 painter.begin(&pixmapHover);
928 //kapp->style().drawPrimitive(QStyle::PE_ExclusiveIndicator, &painter, rect, cg, style);
929 style |= QStyle::Style_MouseOver;
930 cg.setColor(QColorGroup::Background, KGlobalSettings::highlightColor());
931 kapp->style().drawControl(QStyle::CE_RadioButton, &painter, &rb, rect, cg, style);
932 painter.end();
933 pixmapHover.setMask(pixmapHover.createHeuristicMask());
935 QIcon iconSet(pixmap);
936 iconSet.setPixmap(pixmapHover, QIcon::Automatic, QIcon::Active);
937 return iconSet;