1 /***************************************************************************
2 * Copyright (C) 2005 by S�astien Laot *
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. *
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. *
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>
23 #include <kiconloader.h>
29 #include <Q3TextStream>
30 #include <Q3ValueList>
32 #include <kglobalsettings.h>
38 #include "debugwindow.h"
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
)
57 State
* State::nextState(bool cycle
/*= true*/)
62 List states
= parentTag()->states();
63 // The tag contains only one state:
64 if (states
.count() == 1)
66 // Find the next state:
67 for (List::iterator it
= states
.begin(); it
!= states
.end(); ++it
)
68 // Found the current state in the list:
70 // Find the next state:
71 State
*next
= *(++it
);
72 if (it
== states
.end())
73 return (cycle
? states
.first() : 0);
76 // Should not happens:
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
)
94 base
.setUnderline(true);
96 base
.setStrikeOut(true);
97 if (!fontName().isEmpty())
98 base
.setFamily(fontName());
100 base
.setPointSize(fontSize());
104 QString
State::toCSS(const QString
&gradientFolderPath
, const QString
&gradientFolderName
, const QFont
&baseFont
)
108 css
+= " font-weight: bold;";
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
+ ";";
124 css
+= " font-size: " + QString::number(fontSize()) + "px;";
125 if (backgroundColor().isValid()) {
126 // Get the colors of the gradient and the border:
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;";
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.
147 *haveInvisibleTags
= false;
149 for (List::const_iterator it
= states
.begin(); it
!= states
.end(); ++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()) {
158 if (state
->bold() && !result
->bold()) {
159 result
->setBold(true);
162 if (state
->italic() && !result
->italic()) {
163 result
->setItalic(true);
166 if (state
->underline() && !result
->underline()) {
167 result
->setUnderline(true);
170 if (state
->strikeOut() && !result
->strikeOut()) {
171 result
->setStrikeOut(true);
174 if (state
->textColor().isValid() && !result
->textColor().isValid()) {
175 result
->setTextColor(state
->textColor());
178 if (!state
->fontName().isEmpty() && result
->fontName().isEmpty()) {
179 result
->setFontName(state
->fontName());
182 if (state
->fontSize() > 0 && result
->fontSize() <= 0) {
183 result
->setFontSize(state
->fontSize());
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.
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:
192 *haveInvisibleTags
= true;
196 void State::copyTo(State
*other
)
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;
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
227 static int tagNumber
= 0;
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;
241 void Tag::setName(const QString
&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
)
256 Tag
* Tag::tagForKAction(KAction
*action
)
258 for (List::iterator it
= all
.begin(); it
!= all
.end(); ++it
)
259 if ((*it
)->m_action
== action
)
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";
273 if (!dir
.exists(fullPath
)) {
276 DEBUG_WIN
<< "Tags file does not exist: Creating it...";
277 createDefaultTagsSet(fullPath
);
280 QDomDocument
*document
= XMLWork::openFile(doctype
, fullPath
);
282 DEBUG_WIN
<< "<font color=red>FAILED to read the tags file</font>";
286 QDomElement docElem
= document
->documentElement();
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();
296 QString name
= XMLWork::getElementText(element
, "name");
297 QString shortcut
= XMLWork::getElementText(element
, "shortcut");
298 QString inherited
= XMLWork::getElementText(element
, "inherited", "false");
300 tag
->setShortcut(KShortcut(shortcut
));
301 tag
->setInheritedBySiblings(XMLWork::trueOrFalse(inherited
));
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:
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
) {
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!
355 // Tag already exists, rename to theire ids:
357 State::List::iterator it2
= similarTag
->states().begin();
358 for (State::List::iterator it
= tag
->states().begin(); it
!= tag
->states().end(); ++it
, ++it2
) {
360 State
*similarState
= *it2
;
361 QString uid
= state
->id();
362 QString newUid
= similarState
->id();
364 mergedStates
[uid
] = newUid
;
366 delete tag
; // Already exists, not to be merged. Delete the shortcut and all.
371 node
= node
.nextSibling();
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.
387 for (List::iterator it
= all
.begin(); it
!= all
.end(); ++it
) {
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
) {
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
))
425 DEBUG_WIN
<< "Saving tags...";
426 saveTagsTo(all
, Global::savesFolder() + "tags.xml");
429 void Tag::saveTagsTo(Q3ValueList
<Tag
*> &list
, const QString
&fullPath
)
432 QDomDocument
document(/*doctype=*/"basketTags");
433 QDomElement root
= document
.createElement("basketTags");
434 root
.setAttribute("nextStateUid", nextStateUid
);
435 document
.appendChild(root
);
438 for (List::iterator it
= list
.begin(); it
!= list
.end(); ++it
) {
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()) );
448 for (State::List::iterator it2
= (*it
)->states().begin(); it2
!= (*it
)->states().end(); ++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()) );
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"
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"
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"
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"
526 " <state id=\"progress_025\">\n"
527 " <name>%6</name>\n" // "25 %"
528 " <emblem>tag_progress_025</emblem>\n"
529 " <textEquivalent string=\"[= ]\" />\n"
531 " <state id=\"progress_050\">\n"
532 " <name>%7</name>\n" // "50 %"
533 " <emblem>tag_progress_050</emblem>\n"
534 " <textEquivalent string=\"[== ]\" />\n"
536 " <state id=\"progress_075\">\n"
537 " <name>%8</name>\n" // "75 %"
538 " <emblem>tag_progress_075</emblem>\n"
539 " <textEquivalent string=\"[=== ]\" />\n"
541 " <state id=\"progress_100\">\n"
542 " <name>%9</name>\n" // "100 %"
543 " <emblem>tag_progress_100</emblem>\n"
544 " <textEquivalent string=\"[====]\" />\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
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"
561 " <state id=\"priority_medium\">\n"
562 " <name>%3</name>\n" // "Medium
563 " <emblem>tag_priority_medium</emblem>\n"
564 " <textEquivalent string=\"{2}\" />\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"
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"
582 " <state id=\"preference_good\">\n"
583 " <name>%7</name>\n" // "Good"
584 " <emblem>tag_preference_good</emblem>\n"
585 " <textEquivalent string=\"(** )\" />\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"
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"
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
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"
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"
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"
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.
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"
656 " <name>%7</name>\n" // "Code"
657 " <state id=\"code\">\n"
658 " <font name=\"monospace\" />\n"
659 " <textEquivalent string=\"|\" onAllTextLines=\"true\" />\n"
664 " <state id=\"work\">\n"
665 " <name>%8</name>\n" // "Work"
666 " <text color=\"#ff8000\" />\n"
667 " <textEquivalent string=\"%9\" />\n" // W.
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
676 " <state id=\"personal\">\n"
677 " <name>%1</name>\n" // "Personal"
678 " <text color=\"#008000\" />\n"
679 " <textEquivalent string=\"%2\" />\n" // P.
684 " <state id=\"funny\">\n"
685 " <name>%3</name>\n" // "Funny"
686 " <emblem>tag_fun</emblem>\n"
691 .arg( i18n("Personal"), i18n("The initial of 'Personal'", "P."), i18n("Funny") ); // %1 %2 %3
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";
702 DEBUG_WIN
<< "<font color=red>FAILED to create the tags file</font>!";
705 #include <kapplication.h>
708 #include <qcheckbox.h>
710 #include <kglobalsettings.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();
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();
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
);
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
);
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
);
891 QIcon
iconSet(pixmap
);
892 iconSet
.setPixmap(pixmapHover
, QIcon::Automatic
, QIcon::Active
);
893 iconSet
.setPixmap(pixmapDisabled
, QIcon::Automatic
, QIcon::Disabled
);
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:
914 rb
.setChecked(checked
);
915 kapp
->style().drawControl(QStyle::CE_RadioButton
, &painter
, &rb
, rect
, cg
, style
);
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
);
933 pixmapHover
.setMask(pixmapHover
.createHeuristicMask());
935 QIcon
iconSet(pixmap
);
936 iconSet
.setPixmap(pixmapHover
, QIcon::Automatic
, QIcon::Active
);