diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-10 00:18:25 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-10 00:18:25 +0000 |
commit | f21e5792b5084f5d008bf46f6316030c6dfb31e5 (patch) | |
tree | d51583b36aa1672bac78d98a682cdc330df27e4d /src/tag.cpp | |
download | basket-f21e5792b5084f5d008bf46f6316030c6dfb31e5.tar.gz basket-f21e5792b5084f5d008bf46f6316030c6dfb31e5.zip |
Add author-abandoned basket application
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/basket@1072339 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/tag.cpp')
-rw-r--r-- | src/tag.cpp | 934 |
1 files changed, 934 insertions, 0 deletions
diff --git a/src/tag.cpp b/src/tag.cpp new file mode 100644 index 0000000..a4ed7e6 --- /dev/null +++ b/src/tag.cpp @@ -0,0 +1,934 @@ +/*************************************************************************** + * Copyright (C) 2005 by S�astien Laot * + * slaout@linux62.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include <kapplication.h> +#include <kstyle.h> +#include <kiconloader.h> +#include <qpainter.h> +#include <qfont.h> +#include <qdom.h> +#include <qdir.h> +#include <kglobalsettings.h> +#include <klocale.h> + +#include "tag.h" +#include "xmlwork.h" +#include "global.h" +#include "debugwindow.h" +#include "bnpview.h" +#include "tools.h" +#include "basket.h" + +#include <iostream> + +/** class State: */ + +State::State(const QString &id, Tag *tag) + : m_id(id), m_name(), m_emblem(), m_bold(false), m_italic(false), m_underline(false), m_strikeOut(false), + m_textColor(), m_fontName(), m_fontSize(-1), m_backgroundColor(), m_textEquivalent(), m_onAllTextLines(false), m_parentTag(tag) +{ +} + +State::~State() +{ +} + +State* State::nextState(bool cycle /*= true*/) +{ + if (!parentTag()) + return 0; + + List states = parentTag()->states(); + // The tag contains only one state: + if (states.count() == 1) + return 0; + // Find the next state: + for (List::iterator it = states.begin(); it != states.end(); ++it) + // Found the current state in the list: + if (*it == this) { + // Find the next state: + State *next = *(++it); + if (it == states.end()) + return (cycle ? states.first() : 0); + return next; + } + // Should not happens: + return 0; +} + +QString State::fullName() +{ + if (!parentTag() || parentTag()->states().count() == 1) + return (name().isEmpty() && parentTag() ? parentTag()->name() : name()); + return QString(i18n("%1: %2")).arg(parentTag()->name(), name()); +} + +QFont State::font(QFont base) +{ + if (bold()) + base.setBold(true); + if (italic()) + base.setItalic(true); + if (underline()) + base.setUnderline(true); + if (strikeOut()) + base.setStrikeOut(true); + if (!fontName().isEmpty()) + base.setFamily(fontName()); + if (fontSize() > 0) + base.setPointSize(fontSize()); + return base; +} + +QString State::toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont) +{ + QString css; + if (bold()) + css += " font-weight: bold;"; + if (italic()) + css += " font-style: italic;"; + if (underline() && strikeOut()) + css += " text-decoration: underline line-through;"; + else if (underline()) + css += " text-decoration: underline;"; + else if (strikeOut()) + css += " text-decoration: line-through;"; + if (textColor().isValid()) + css += " color: " + textColor().name() + ";"; + if (!fontName().isEmpty()) { + QString fontFamily = Tools::cssFontDefinition(fontName(), /*onlyFontFamily=*/true); + css += " font-family: " + fontFamily + ";"; + } + if (fontSize() > 0) + css += " font-size: " + QString::number(fontSize()) + "px;"; + if (backgroundColor().isValid()) { + // Get the colors of the gradient and the border: + QColor topBgColor; + QColor bottomBgColor; + Note::getGradientColors(backgroundColor(), &topBgColor, &bottomBgColor); + // Produce the CSS code: + QString gradientFileName = Basket::saveGradientBackground(backgroundColor(), font(baseFont), gradientFolderPath); + css += " background: " + bottomBgColor.name() + " url('" + gradientFolderName + gradientFileName + "') repeat-x;"; + css += " border-top: solid " + topBgColor.name() + " 1px;"; + css += " border-bottom: solid " + Tools::mixColor(topBgColor, bottomBgColor).name() + " 1px;"; + } + + if (css.isEmpty()) + return ""; + else + return " .tag_" + id() + " {" + css + " }\n"; +} + +void State::merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor) +{ + *result = State(); // Reset to default values. + *emblemsCount = 0; + *haveInvisibleTags = false; + + for (List::const_iterator it = states.begin(); it != states.end(); ++it) { + State *state = *it; + bool isVisible = false; + // For each propertie, if that properties have a value (is not default) is the current state of the list, + // 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. + if (!state->emblem().isEmpty()) { + ++*emblemsCount; + isVisible = true; + } + if (state->bold() && !result->bold()) { + result->setBold(true); + isVisible = true; + } + if (state->italic() && !result->italic()) { + result->setItalic(true); + isVisible = true; + } + if (state->underline() && !result->underline()) { + result->setUnderline(true); + isVisible = true; + } + if (state->strikeOut() && !result->strikeOut()) { + result->setStrikeOut(true); + isVisible = true; + } + if (state->textColor().isValid() && !result->textColor().isValid()) { + result->setTextColor(state->textColor()); + isVisible = true; + } + if (!state->fontName().isEmpty() && result->fontName().isEmpty()) { + result->setFontName(state->fontName()); + isVisible = true; + } + if (state->fontSize() > 0 && result->fontSize() <= 0) { + result->setFontSize(state->fontSize()); + isVisible = true; + } + if (state->backgroundColor().isValid() && !result->backgroundColor().isValid() && state->backgroundColor() != backgroundColor) { // vv + result->setBackgroundColor(state->backgroundColor()); // This is particular: if the note background color is the same as the basket one, don't use that. + isVisible = true; + } + // 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: + if (!isVisible) + *haveInvisibleTags = true; + } +} + +void State::copyTo(State *other) +{ + other->m_id = m_id; + other->m_name = m_name; + other->m_emblem = m_emblem; + other->m_bold = m_bold; + other->m_italic = m_italic; + other->m_underline = m_underline; + other->m_strikeOut = m_strikeOut; + other->m_textColor = m_textColor; + other->m_fontName = m_fontName; + other->m_fontSize = m_fontSize; + other->m_backgroundColor = m_backgroundColor; + other->m_textEquivalent = m_textEquivalent; + other->m_onAllTextLines = m_onAllTextLines; // TODO + //TODO: other->m_parentTag; +} + +/** class Tag: */ + +Tag::List Tag::all = Tag::List(); + +long Tag::nextStateUid = 1; + +long Tag::getNextStateUid() +{ + return nextStateUid++; // Return the next Uid and THEN increment the Uid +} + +Tag::Tag() +{ + static int tagNumber = 0; + ++tagNumber; + QString sAction = "tag_shortcut_number_" + QString::number(tagNumber); + m_action = new KAction("FAKE TEXT", "FAKE ICON", KShortcut(), Global::bnpView, SLOT(activatedTagShortcut()), Global::bnpView->actionCollection(), sAction); + m_action->setShortcutConfigurable(false); // We do it in the tag properties dialog + + m_inheritedBySiblings = false; +} + +Tag::~Tag() +{ + delete m_action; +} + +void Tag::setName(const QString &name) +{ + m_name = name; + m_action->setText("TAG SHORTCUT: " + name); // TODO: i18n (for debug purpose only by now). +} + +State* Tag::stateForId(const QString &id) +{ + for (List::iterator it = all.begin(); it != all.end(); ++it) + for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) + if ((*it2)->id() == id) + return *it2; + return 0; +} + +Tag* Tag::tagForKAction(KAction *action) +{ + for (List::iterator it = all.begin(); it != all.end(); ++it) + if ((*it)->m_action == action) + return *it; + return 0; +} + +QMap<QString, QString> Tag::loadTags(const QString &path/* = QString()*//*, bool merge = false*/) +{ + QMap<QString, QString> mergedStates; + + bool merge = !path.isEmpty(); + QString fullPath = (merge ? path : Global::savesFolder() + "tags.xml"); + QString doctype = "basketTags"; + + QDir dir; + if (!dir.exists(fullPath)) { + if (merge) + return mergedStates; + DEBUG_WIN << "Tags file does not exist: Creating it..."; + createDefaultTagsSet(fullPath); + } + + QDomDocument *document = XMLWork::openFile(doctype, fullPath); + if (!document) { + DEBUG_WIN << "<font color=red>FAILED to read the tags file</font>"; + return mergedStates; + } + + QDomElement docElem = document->documentElement(); + if (!merge) + nextStateUid = docElem.attribute("nextStateUid", QString::number(nextStateUid)).toLong(); + + QDomNode node = docElem.firstChild(); + while (!node.isNull()) { + QDomElement element = node.toElement(); + if ( (!element.isNull()) && element.tagName() == "tag" ) { + Tag *tag = new Tag(); + // Load properties: + QString name = XMLWork::getElementText(element, "name"); + QString shortcut = XMLWork::getElementText(element, "shortcut"); + QString inherited = XMLWork::getElementText(element, "inherited", "false"); + tag->setName(name); + tag->setShortcut(KShortcut(shortcut)); + tag->setInheritedBySiblings(XMLWork::trueOrFalse(inherited)); + // Load states: + QDomNode subNode = element.firstChild(); + while (!subNode.isNull()) { + QDomElement subElement = subNode.toElement(); + if ( (!subElement.isNull()) && subElement.tagName() == "state" ) { + State *state = new State(subElement.attribute("id"), tag); + state->setName( XMLWork::getElementText(subElement, "name") ); + state->setEmblem( XMLWork::getElementText(subElement, "emblem") ); + QDomElement textElement = XMLWork::getElement(subElement, "text"); + state->setBold( XMLWork::trueOrFalse(textElement.attribute("bold", "false")) ); + state->setItalic( XMLWork::trueOrFalse(textElement.attribute("italic", "false")) ); + state->setUnderline( XMLWork::trueOrFalse(textElement.attribute("underline", "false")) ); + state->setStrikeOut( XMLWork::trueOrFalse(textElement.attribute("strikeOut", "false")) ); + QString textColor = textElement.attribute("color", ""); + state->setTextColor(textColor.isEmpty() ? QColor() : QColor(textColor)); + QDomElement fontElement = XMLWork::getElement(subElement, "font"); + state->setFontName(fontElement.attribute("name", "")); + QString fontSize = fontElement.attribute("size", ""); + state->setFontSize(fontSize.isEmpty() ? -1 : fontSize.toInt()); + QString backgroundColor = XMLWork::getElementText(subElement, "backgroundColor", ""); + state->setBackgroundColor(backgroundColor.isEmpty() ? QColor() : QColor(backgroundColor)); + QDomElement textEquivalentElement = XMLWork::getElement(subElement, "textEquivalent"); + state->setTextEquivalent( textEquivalentElement.attribute("string", "") ); + state->setOnAllTextLines( XMLWork::trueOrFalse(textEquivalentElement.attribute("onAllTextLines", "false")) ); + tag->appendState(state); + } + subNode = subNode.nextSibling(); + } + // If the Tag is Valid: + if (tag->countStates() > 0) { + // Rename Things if Needed: + State *firstState = tag->states().first(); + if (tag->countStates() == 1 && firstState->name().isEmpty()) + firstState->setName(tag->name()); + if (tag->name().isEmpty()) + tag->setName(firstState->name()); + // Add or Merge the Tag: + if (!merge) { + all.append(tag); + } else { + Tag *similarTag = tagSimilarTo(tag); + // Tag does not exists, add it: + if (similarTag == 0) { + // We are merging the new states, so we should choose new and unique (on that computer) ids for those states: + for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it) { + State *state = *it; + QString uid = state->id(); + QString newUid = "tag_state_" + QString::number(getNextStateUid()); + state->setId(newUid); + mergedStates[uid] = newUid; + } + // TODO: if shortcut is already assigned to a previous note, do not import it, keep the user settings! + all.append(tag); + // Tag already exists, rename to theire ids: + } else { + State::List::iterator it2 = similarTag->states().begin(); + for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it, ++it2) { + State *state = *it; + State *similarState = *it2; + QString uid = state->id(); + QString newUid = similarState->id(); + if (uid != newUid) + mergedStates[uid] = newUid; + } + delete tag; // Already exists, not to be merged. Delete the shortcut and all. + } + } + } + } + node = node.nextSibling(); + } + + return mergedStates; +} + +Tag* Tag::tagSimilarTo(Tag *tagToTest) +{ + // Tags are considered similar if they have the same name, the same number of states, in the same order, and the same look. + // Keyboard shortcut, text equivalent and onEveryLines are user settings, and thus not considered during the comparision. + // Default tags (To Do, Important, Idea...) do not take into account the name of the tag and states during the comparision. + // Default tags are equal only if they have the same number of states, in the same order, and the same look. + // This is because default tag names are translated differently in every countries, but they are essentialy the same! + // User tags begins with "tag_state_" followed by a number. Default tags are the other ones. + + // Browse all tags: + for (List::iterator it = all.begin(); it != all.end(); ++it) { + Tag *tag = *it; + bool same = true; + bool sameName; + bool defaultTag = true; + // We test only name and look. Shorcut and whenever it is inherited by sibling new notes are user settings only! + sameName = tag->name() == tagToTest->name(); + if (tag->countStates() != tagToTest->countStates()) + continue; // Tag is different! + // We found a tag with same name, check if every states/look are same too: + State::List::iterator itTest = tagToTest->states().begin(); + for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2, ++itTest) { + State *state = *it2; + State *stateToTest = *itTest; + if (state->id().startsWith("tag_state_") || stateToTest->id().startsWith("tag_state_")) { defaultTag = false; } + if (state->name() != stateToTest->name()) { sameName = false; } + if (state->emblem() != stateToTest->emblem()) { same = false; break; } + if (state->bold() != stateToTest->bold()) { same = false; break; } + if (state->italic() != stateToTest->italic()) { same = false; break; } + if (state->underline() != stateToTest->underline()) { same = false; break; } + if (state->strikeOut() != stateToTest->strikeOut()) { same = false; break; } + if (state->textColor() != stateToTest->textColor()) { same = false; break; } + if (state->fontName() != stateToTest->fontName()) { same = false; break; } + if (state->fontSize() != stateToTest->fontSize()) { same = false; break; } + if (state->backgroundColor() != stateToTest->backgroundColor()) { same = false; break; } + // Text equivalent (as well as onAllTextLines) is also a user setting! + } + // We found an existing tag that is "exactly" the same: + if (same && (sameName || defaultTag)) + return tag; + } + + // Not found: + return 0; +} + +void Tag::saveTags() +{ + DEBUG_WIN << "Saving tags..."; + saveTagsTo(all, Global::savesFolder() + "tags.xml"); +} + +void Tag::saveTagsTo(QValueList<Tag*> &list, const QString &fullPath) +{ + // Create Document: + QDomDocument document(/*doctype=*/"basketTags"); + QDomElement root = document.createElement("basketTags"); + root.setAttribute("nextStateUid", nextStateUid); + document.appendChild(root); + + // Save all tags: + for (List::iterator it = list.begin(); it != list.end(); ++it) { + Tag *tag = *it; + // Create tag node: + QDomElement tagNode = document.createElement("tag"); + root.appendChild(tagNode); + // Save tag properties: + XMLWork::addElement( document, tagNode, "name", tag->name() ); + XMLWork::addElement( document, tagNode, "shortcut", tag->shortcut().toStringInternal() ); + XMLWork::addElement( document, tagNode, "inherited", XMLWork::trueOrFalse(tag->inheritedBySiblings()) ); + // Save all states: + for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) { + State *state = *it2; + // Create state node: + QDomElement stateNode = document.createElement("state"); + tagNode.appendChild(stateNode); + // Save state properties: + stateNode.setAttribute("id", state->id()); + XMLWork::addElement( document, stateNode, "name", state->name() ); + XMLWork::addElement( document, stateNode, "emblem", state->emblem() ); + QDomElement textNode = document.createElement("text"); + stateNode.appendChild(textNode); + QString textColor = (state->textColor().isValid() ? state->textColor().name() : ""); + textNode.setAttribute( "bold", XMLWork::trueOrFalse(state->bold()) ); + textNode.setAttribute( "italic", XMLWork::trueOrFalse(state->italic()) ); + textNode.setAttribute( "underline", XMLWork::trueOrFalse(state->underline()) ); + textNode.setAttribute( "strikeOut", XMLWork::trueOrFalse(state->strikeOut()) ); + textNode.setAttribute( "color", textColor ); + QDomElement fontNode = document.createElement("font"); + stateNode.appendChild(fontNode); + fontNode.setAttribute( "name", state->fontName() ); + fontNode.setAttribute( "size", state->fontSize() ); + QString backgroundColor = (state->backgroundColor().isValid() ? state->backgroundColor().name() : ""); + XMLWork::addElement( document, stateNode, "backgroundColor", backgroundColor ); + QDomElement textEquivalentNode = document.createElement("textEquivalent"); + stateNode.appendChild(textEquivalentNode); + textEquivalentNode.setAttribute( "string", state->textEquivalent() ); + textEquivalentNode.setAttribute( "onAllTextLines", XMLWork::trueOrFalse(state->onAllTextLines()) ); + } + } + + // Write to Disk: + if (!Basket::safelySaveToFile(fullPath, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + document.toString())) + DEBUG_WIN << "<font color=red>FAILED to save tags</font>!"; +} + +void Tag::copyTo(Tag *other) +{ + other->m_name = m_name; + other->m_action->setShortcut(m_action->shortcut()); + other->m_inheritedBySiblings = m_inheritedBySiblings; +} + +void Tag::createDefaultTagsSet(const QString &fullPath) +{ + QString xml = QString( + "<!DOCTYPE basketTags>\n" + "<basketTags>\n" + " <tag>\n" + " <name>%1</name>\n" // "To Do" + " <shortcut>Ctrl+1</shortcut>\n" + " <inherited>true</inherited>\n" + " <state id=\"todo_unchecked\">\n" + " <name>%2</name>\n" // "Unchecked" + " <emblem>tag_checkbox</emblem>\n" + " <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"false\" color=\"\" />\n" + " <font name=\"\" size=\"\" />\n" + " <backgroundColor></backgroundColor>\n" + " <textEquivalent string=\"[ ]\" onAllTextLines=\"false\" />\n" + " </state>\n" + " <state id=\"todo_done\">\n" + " <name>%3</name>\n" // "Done" + " <emblem>tag_checkbox_checked</emblem>\n" + " <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"true\" color=\"\" />\n" + " <font name=\"\" size=\"\" />\n" + " <backgroundColor></backgroundColor>\n" + " <textEquivalent string=\"[x]\" onAllTextLines=\"false\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%4</name>\n" // "Progress" + " <shortcut>Ctrl+2</shortcut>\n" + " <inherited>true</inherited>\n" + " <state id=\"progress_000\">\n" + " <name>%5</name>\n" // "0 %" + " <emblem>tag_progress_000</emblem>\n" + " <textEquivalent string=\"[ ]\" />\n" + " </state>\n" + " <state id=\"progress_025\">\n" + " <name>%6</name>\n" // "25 %" + " <emblem>tag_progress_025</emblem>\n" + " <textEquivalent string=\"[= ]\" />\n" + " </state>\n" + " <state id=\"progress_050\">\n" + " <name>%7</name>\n" // "50 %" + " <emblem>tag_progress_050</emblem>\n" + " <textEquivalent string=\"[== ]\" />\n" + " </state>\n" + " <state id=\"progress_075\">\n" + " <name>%8</name>\n" // "75 %" + " <emblem>tag_progress_075</emblem>\n" + " <textEquivalent string=\"[=== ]\" />\n" + " </state>\n" + " <state id=\"progress_100\">\n" + " <name>%9</name>\n" // "100 %" + " <emblem>tag_progress_100</emblem>\n" + " <textEquivalent string=\"[====]\" />\n" + " </state>\n" + " </tag>\n" + "\n") + .arg( i18n("To Do"), i18n("Unchecked"), i18n("Done") ) // %1 %2 %3 + .arg( i18n("Progress"), i18n("0 %"), i18n("25 %") ) // %4 %5 %6 + .arg( i18n("50 %"), i18n("75 %"), i18n("100 %") ) // %7 %8 %9 + + QString( + " <tag>\n" + " <name>%1</name>\n" // "Priority" + " <shortcut>Ctrl+3</shortcut>\n" + " <inherited>true</inherited>\n" + " <state id=\"priority_low\">\n" + " <name>%2</name>\n" // "Low" + " <emblem>tag_priority_low</emblem>\n" + " <textEquivalent string=\"{1}\" />\n" + " </state>\n" + " <state id=\"priority_medium\">\n" + " <name>%3</name>\n" // "Medium + " <emblem>tag_priority_medium</emblem>\n" + " <textEquivalent string=\"{2}\" />\n" + " </state>\n" + " <state id=\"priority_high\">\n" + " <name>%4</name>\n" // "High" + " <emblem>tag_priority_high</emblem>\n" + " <textEquivalent string=\"{3}\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%5</name>\n" // "Preference" + " <shortcut>Ctrl+4</shortcut>\n" + " <inherited>true</inherited>\n" + " <state id=\"preference_bad\">\n" + " <name>%6</name>\n" // "Bad" + " <emblem>tag_preference_bad</emblem>\n" + " <textEquivalent string=\"(* )\" />\n" + " </state>\n" + " <state id=\"preference_good\">\n" + " <name>%7</name>\n" // "Good" + " <emblem>tag_preference_good</emblem>\n" + " <textEquivalent string=\"(** )\" />\n" + " </state>\n" + " <state id=\"preference_excelent\">\n" + " <name>%8</name>\n" // "Excellent" + " <emblem>tag_preference_excelent</emblem>\n" // "excelent": typo error, but we should keep compatibility with old versions. + " <textEquivalent string=\"(***)\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%9</name>\n" // "Highlight" + " <shortcut>Ctrl+5</shortcut>\n" + " <state id=\"highlight\">\n" + " <backgroundColor>#ffffcc</backgroundColor>\n" + " <textEquivalent string=\"=>\" />\n" + " </state>\n" + " </tag>\n" + "\n") + .arg( i18n("Priority"), i18n("Low"), i18n("Medium") ) // %1 %2 %3 + .arg( i18n("High"), i18n("Preference"), i18n("Bad") ) // %4 %5 %6 + .arg( i18n("Good"), i18n("Excellent"), i18n("Highlight") ) // %7 %8 %9 + + QString( + " <tag>\n" + " <name>%1</name>\n" // "Important" + " <shortcut>Ctrl+6</shortcut>\n" + " <state id=\"important\">\n" + " <emblem>tag_important</emblem>\n" + " <backgroundColor>#ffcccc</backgroundColor>\n" + " <textEquivalent string=\"!!\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%2</name>\n" // "Very Important" + " <shortcut>Ctrl+7</shortcut>\n" + " <state id=\"very_important\">\n" + " <emblem>tag_important</emblem>\n" + " <text color=\"#ffffff\" />\n" + " <backgroundColor>#ff0000</backgroundColor>\n" + " <textEquivalent string=\"/!\\\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%3</name>\n" // "Information" + " <shortcut>Ctrl+8</shortcut>\n" + " <state id=\"information\">\n" + " <emblem>messagebox_info</emblem>\n" + " <textEquivalent string=\"(i)\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%4</name>\n" // "Idea" + " <shortcut>Ctrl+9</shortcut>\n" + " <state id=\"idea\">\n" + " <emblem>ktip</emblem>\n" + " <textEquivalent string=\"%5\" />\n" // I. + " </state>\n" + " </tag>""\n" + "\n" + " <tag>\n" + " <name>%6</name>\n" // "Title" + " <shortcut>Ctrl+0</shortcut>\n" + " <state id=\"title\">\n" + " <text bold=\"true\" />\n" + " <textEquivalent string=\"##\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <name>%7</name>\n" // "Code" + " <state id=\"code\">\n" + " <font name=\"monospace\" />\n" + " <textEquivalent string=\"|\" onAllTextLines=\"true\" />\n" + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <state id=\"work\">\n" + " <name>%8</name>\n" // "Work" + " <text color=\"#ff8000\" />\n" + " <textEquivalent string=\"%9\" />\n" // W. + " </state>\n" + " </tag>""\n" + "\n") + .arg( i18n("Important"), i18n("Very Important"), i18n("Information") ) // %1 %2 %3 + .arg( i18n("Idea"), i18n("The initial of 'Idea'", "I."), i18n("Title") ) // %4 %5 %6 + .arg( i18n("Code"), i18n("Work"), i18n("The initial of 'Work'", "W.") ) // %7 %8 %9 + + QString( + " <tag>\n" + " <state id=\"personal\">\n" + " <name>%1</name>\n" // "Personal" + " <text color=\"#008000\" />\n" + " <textEquivalent string=\"%2\" />\n" // P. + " </state>\n" + " </tag>\n" + "\n" + " <tag>\n" + " <state id=\"funny\">\n" + " <name>%3</name>\n" // "Funny" + " <emblem>tag_fun</emblem>\n" + " </state>\n" + " </tag>\n" + "</basketTags>\n" + "") + .arg( i18n("Personal"), i18n("The initial of 'Personal'", "P."), i18n("Funny") ); // %1 %2 %3 + + // Write to Disk: + QFile file(fullPath); + if (file.open(IO_WriteOnly)) { + QTextStream stream(&file); + stream.setEncoding(QTextStream::UnicodeUTF8); + stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; + stream << xml; + file.close(); + } else + DEBUG_WIN << "<font color=red>FAILED to create the tags file</font>!"; +} + +#include <kapplication.h> +#include <qrect.h> +#include <qstyle.h> +#include <qcheckbox.h> +#include <qbitmap.h> +#include <kglobalsettings.h> +#include <qimage.h> +#include <qradiobutton.h> +#include <kiconeffect.h> + +/** class IndentedMenuItem: */ + +IndentedMenuItem::IndentedMenuItem(const QString &text, const QString &icon, const QString &shortcut) + : m_text(text), m_icon(icon), m_shortcut(shortcut) +{ +} + +IndentedMenuItem::~IndentedMenuItem() +{ +} + +void IndentedMenuItem::paint(QPainter *painter, const QColorGroup &cg, bool active, bool enabled, int x, int y, int w, int h) +{ + QPen pen = painter->pen(); + QFont font = painter->font(); + + int iconSize = KIcon::SizeSmall; + int iconMargin = StateMenuItem::iconMargin(); + + /* When an item is disabled, it often have a 3D sunken look. + * This is done by calling this paint routine two times, with different pen color and offset. + * A disabled item is first painted in the rect (x+1, y+1, w, h) and with pen of cg.light() color, + * It is then drawn a second time in the rect (x, y, w, h). + * 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. + * 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). + */ + bool drawingEtchedText = !enabled && !active && painter->pen().color() != cg.mid()/*== cg.foreground()*/; + if (drawingEtchedText) { + QString styleName = kapp->style().name(); + if (styleName == "plastik" || styleName == "lipstik") + painter->setPen(cg.light()); + drawingEtchedText = !enabled && !active && painter->pen().color() != cg.foreground(); + } else + drawingEtchedText = !enabled && !active && painter->pen().color() == cg.light(); + if (!m_icon.isEmpty() && !drawingEtchedText) { + QPixmap icon = kapp->iconLoader()->loadIcon(m_icon, KIcon::Small, iconSize, + (enabled ? (active ? KIcon::ActiveState : KIcon::DefaultState) : KIcon::DisabledState), + /*path_store=*/0L, /*canReturnNull=*/true); + painter->drawPixmap(x, y + (h-iconSize)/2, icon); + } + /* Pen and font are already set to the good ones, so we can directly draw the text. + * 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). + * So, I set the pen myself. + * 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 + * and keep the background to white, drawing a white text over it is... very bad. But I can't see what can be done. + */ + if (active && enabled) + painter->setPen(KGlobalSettings::highlightedTextColor()); + painter->drawText(x + iconSize + iconMargin, y, w - iconSize - iconMargin, h, AlignLeft | AlignVCenter | DontClip | ShowPrefix, m_text/*painter->pen().color().name()*/); + + if (!m_shortcut.isEmpty()) { + painter->setPen(pen); + if (active && enabled) + painter->setPen(KGlobalSettings::highlightedTextColor()); + painter->setFont(font); + painter->setClipping(false); + painter->drawText(x + 5 + w, y, 3000, h, AlignLeft | AlignVCenter | DontClip | ShowPrefix, m_shortcut); + } +} + +QSize IndentedMenuItem::sizeHint() +{ + int iconSize = KIcon::SizeSmall; + int iconMargin = StateMenuItem::iconMargin(); + QSize textSize = QFontMetrics(KGlobalSettings::menuFont()).size( AlignLeft | AlignVCenter | ShowPrefix | DontClip, m_text ); + return QSize(iconSize + iconMargin + textSize.width(), textSize.height()); +} + +/** class StateMenuItem: */ + +StateMenuItem::StateMenuItem(State *state, const QString &shortcut, bool withTagName) + : m_state(state), m_shortcut(shortcut) +{ + m_name = (withTagName && m_state->parentTag() ? m_state->parentTag()->name() : m_state->name()); +} + +StateMenuItem::~StateMenuItem() +{ +} + +void StateMenuItem::paint(QPainter *painter, const QColorGroup &cg, bool active, bool enabled, int x, int y, int w, int h) +{ + QPen pen = painter->pen(); + QFont font = painter->font(); + + int iconSize = 16; // We use 16 instead of KIcon::SizeSmall (the size of icons in menus) because tags will always be 16*16 icons + + if (!active && m_state->backgroundColor().isValid()) + painter->fillRect(x/*-1*/, y/*-1*/, w/*+2*/, h/*+2*/, m_state->backgroundColor()); + /* When an item is disabled, it often have a 3D sunken look. + * This is done by calling this paint routine two times, with different pen color and offset. + * A disabled item is first painted in the rect (x+1, y+1, w, h) and with pen of cg.light() color, + * It is then drawn a second time in the rect (x, y, w, h). + * 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. + * 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). + */ + bool drawingEtchedText = !enabled && !active && painter->pen().color() != cg.mid()/*== cg.foreground()*/; + if (drawingEtchedText) { + QString styleName = kapp->style().name(); + if (styleName == "plastik" || styleName == "lipstik") + painter->setPen(cg.light()); + drawingEtchedText = !enabled && !active && painter->pen().color() != cg.foreground(); + } else + drawingEtchedText = !enabled && !active && painter->pen().color() == cg.light(); + if (!m_state->emblem().isEmpty() && !drawingEtchedText) { + QPixmap icon = kapp->iconLoader()->loadIcon(m_state->emblem(), KIcon::Small, iconSize, + (enabled ? (active ? KIcon::ActiveState : KIcon::DefaultState) : KIcon::DisabledState), + /*path_store=*/0L, /*canReturnNull=*/true); + painter->drawPixmap(x, y + (h-iconSize)/2, icon); + } + if (enabled && !active && m_state->textColor().isValid()) + painter->setPen(m_state->textColor()); + /* Pen and font are already set to the good ones, so we can directly draw the text. + * 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). + * So, I set the pen myself. + * 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 + * and keep the background to white, drawing a white text over it is... very bad. But I can't see what can be done. + */ + if (active && enabled) + painter->setPen(KGlobalSettings::highlightedTextColor()); + painter->setFont( m_state->font(painter->font()) ); + painter->drawText(x + iconSize + iconMargin(), y, w - iconSize - iconMargin(), h, AlignLeft | AlignVCenter | DontClip | ShowPrefix, m_name); + + if (!m_shortcut.isEmpty()) { + painter->setPen(pen); + if (active && enabled) + painter->setPen(KGlobalSettings::highlightedTextColor()); + painter->setFont(font); + painter->setClipping(false); + painter->drawText(x + 5 + w, y, 3000, h, AlignLeft | AlignVCenter | DontClip | ShowPrefix, m_shortcut); + } +} + +QSize StateMenuItem::sizeHint() +{ + int iconSize = 16; // We use 16 instead of KIcon::SizeSmall (the size of icons in menus) because tags will always be 16*16 icons + QFont theFont = m_state->font(KGlobalSettings::menuFont()); + QSize textSize = QFontMetrics(theFont).size( AlignLeft | AlignVCenter | ShowPrefix | DontClip, m_name ); + return QSize(iconSize + iconMargin() + textSize.width(), textSize.height()); +} + +QIconSet StateMenuItem::checkBoxIconSet(bool checked, QColorGroup cg) +{ + int width = kapp->style().pixelMetric(QStyle::PM_IndicatorWidth, 0); + int height = kapp->style().pixelMetric(QStyle::PM_IndicatorHeight, 0); + QRect rect(0, 0, width, height); + + QColor menuBackgroundColor = (dynamic_cast<KStyle*>(&(kapp->style())) == NULL ? cg.background() : cg.background().light(103)); + + // Enabled, Not hovering + QPixmap pixmap(width, height); + pixmap.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws + QPainter painter(&pixmap); + int style = QStyle::Style_Enabled | QStyle::Style_Active | (checked ? QStyle::Style_On : QStyle::Style_Off); + QColor background = cg.color(QColorGroup::Background); + kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style); + painter.end(); + + // Enabled, Hovering + QPixmap pixmapHover(width, height); + pixmapHover.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws + painter.begin(&pixmapHover); + style |= QStyle::Style_MouseOver; + cg.setColor(QColorGroup::Background, KGlobalSettings::highlightColor()); + kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style); + painter.end(); + + // Disabled + QPixmap pixmapDisabled(width, height); + pixmapDisabled.fill(menuBackgroundColor); // In case the pixelMetric() haven't returned a bigger rectangle than what drawPrimitive() draws + painter.begin(&pixmapDisabled); + style = /*QStyle::Style_Enabled | */QStyle::Style_Active | (checked ? QStyle::Style_On : QStyle::Style_Off); + cg.setColor(QColorGroup::Background, background); + kapp->style().drawPrimitive(QStyle::PE_Indicator, &painter, rect, cg, style); + painter.end(); + + QIconSet iconSet(pixmap); + iconSet.setPixmap(pixmapHover, QIconSet::Automatic, QIconSet::Active); + iconSet.setPixmap(pixmapDisabled, QIconSet::Automatic, QIconSet::Disabled); + return iconSet; +} + +QIconSet StateMenuItem::radioButtonIconSet(bool checked, QColorGroup cg) +{ + int width = kapp->style().pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, 0); + int height = kapp->style().pixelMetric(QStyle::PM_ExclusiveIndicatorHeight, 0); + QRect rect(0, 0, width, height); + + int style = QStyle::Style_Default | QStyle::Style_Enabled | (checked ? QStyle::Style_On : QStyle::Style_Off); + + QPixmap pixmap(width, height); + pixmap.fill(Qt::red); + QPainter painter(&pixmap); + /* We can't use that line of code (like for checkboxes): + * //kapp->style().drawPrimitive(QStyle::PE_ExclusiveIndicator, &painter, rect, cg, style); + * because Plastik (and derived styles) don't care of the QStyle::Style_On flag and will ALWAYS draw an unchecked radiobutton. + * So, we use another method: + */ + QRadioButton rb(0); + rb.setChecked(checked); + kapp->style().drawControl(QStyle::CE_RadioButton, &painter, &rb, rect, cg, style); + painter.end(); + /* Some styles like Plastik (and derived ones) have QStyle::PE_ExclusiveIndicator drawing a radiobutton disc, as wanted, + * and leave pixels ouside it untouched, BUT QStyle::PE_ExclusiveIndicatorMask is a fully black square. + * So, we can't apply the mask to make the radiobutton circle transparent outside. + * We're using an hack by filling the pixmap in Qt::red, drawing the radiobutton and then creating an heuristic mask. + * The heuristic mask is created using the 4 edge pixels (that are red) and by making transparent every pixels that are of this color: + */ + pixmap.setMask(pixmap.createHeuristicMask()); + + QPixmap pixmapHover(width, height); + pixmapHover.fill(Qt::red); + painter.begin(&pixmapHover); + //kapp->style().drawPrimitive(QStyle::PE_ExclusiveIndicator, &painter, rect, cg, style); + style |= QStyle::Style_MouseOver; + cg.setColor(QColorGroup::Background, KGlobalSettings::highlightColor()); + kapp->style().drawControl(QStyle::CE_RadioButton, &painter, &rb, rect, cg, style); + painter.end(); + pixmapHover.setMask(pixmapHover.createHeuristicMask()); + + QIconSet iconSet(pixmap); + iconSet.setPixmap(pixmapHover, QIconSet::Automatic, QIconSet::Active); + return iconSet; +} |