/*************************************************************************** * 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 #include #include #include #include #include #include #include #include #include "tag.h" #include "xmlwork.h" #include "global.h" #include "debugwindow.h" #include "bnpview.h" #include "tools.h" #include "basket.h" #include /** 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 Tag::loadTags(const QString &path/* = QString()*//*, bool merge = false*/) { QMap 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 << "FAILED to read the tags file"; 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 &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, "\n" + document.toString())) DEBUG_WIN << "FAILED to save tags!"; } 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( "\n" "\n" " \n" " %1\n" // "To Do" " Ctrl+1\n" " true\n" " \n" " %2\n" // "Unchecked" " tag_checkbox\n" " \n" " \n" " \n" " \n" " \n" " \n" " %3\n" // "Done" " tag_checkbox_checked\n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " %4\n" // "Progress" " Ctrl+2\n" " true\n" " \n" " %5\n" // "0 %" " tag_progress_000\n" " \n" " \n" " \n" " %6\n" // "25 %" " tag_progress_025\n" " \n" " \n" " \n" " %7\n" // "50 %" " tag_progress_050\n" " \n" " \n" " \n" " %8\n" // "75 %" " tag_progress_075\n" " \n" " \n" " \n" " %9\n" // "100 %" " tag_progress_100\n" " \n" " \n" " \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( " \n" " %1\n" // "Priority" " Ctrl+3\n" " true\n" " \n" " %2\n" // "Low" " tag_priority_low\n" " \n" " \n" " \n" " %3\n" // "Medium " tag_priority_medium\n" " \n" " \n" " \n" " %4\n" // "High" " tag_priority_high\n" " \n" " \n" " \n" "\n" " \n" " %5\n" // "Preference" " Ctrl+4\n" " true\n" " \n" " %6\n" // "Bad" " tag_preference_bad\n" " \n" " \n" " \n" " %7\n" // "Good" " tag_preference_good\n" " \n" " \n" " \n" " %8\n" // "Excellent" " tag_preference_excelent\n" // "excelent": typo error, but we should keep compatibility with old versions. " \n" " \n" " \n" "\n" " \n" " %9\n" // "Highlight" " Ctrl+5\n" " \n" " #ffffcc\n" " \" />\n" " \n" " \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( " \n" " %1\n" // "Important" " Ctrl+6\n" " \n" " tag_important\n" " #ffcccc\n" " \n" " \n" " \n" "\n" " \n" " %2\n" // "Very Important" " Ctrl+7\n" " \n" " tag_important\n" " \n" " #ff0000\n" " \n" " \n" " \n" "\n" " \n" " %3\n" // "Information" " Ctrl+8\n" " \n" " messagebox_info\n" " \n" " \n" " \n" "\n" " \n" " %4\n" // "Idea" " Ctrl+9\n" " \n" " ktip\n" " \n" // I. " \n" " ""\n" "\n" " \n" " %6\n" // "Title" " Ctrl+0\n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " %7\n" // "Code" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " \n" " %8\n" // "Work" " \n" " \n" // W. " \n" " ""\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( " \n" " \n" " %1\n" // "Personal" " \n" " \n" // P. " \n" " \n" "\n" " \n" " \n" " %3\n" // "Funny" " tag_fun\n" " \n" " \n" "\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 << "\n"; stream << xml; file.close(); } else DEBUG_WIN << "FAILED to create the tags file!"; } #include #include #include #include #include #include #include #include #include /** 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(&(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; }