From 800e5ec91c165eea7499750b2c0ae1e61d1bdca1 Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Wed, 16 Oct 2013 21:01:39 +0200 Subject: [PATCH] use the ctivity class and Kactivities just building, issues with linking with kactivities, not used yet --- src/plasma/containment.cpp | 7 + src/plasma/containment.h | 7 + src/plasma/corona.cpp | 16 ++ src/plasma/corona.h | 11 + src/plasma/private/containment_p.cpp | 4 + src/plasma/private/containment_p.h | 2 + src/shell/CMakeLists.txt | 4 + src/shell/activity.cpp | 401 +++++++++++++++++++++++++++ src/shell/activity.h | 170 ++++++++++++ src/shell/kidenticongenerator.cpp | 302 ++++++++++++++++++++ src/shell/kidenticongenerator.h | 48 ++++ 11 files changed, 972 insertions(+) create mode 100644 src/shell/activity.cpp create mode 100644 src/shell/activity.h create mode 100644 src/shell/kidenticongenerator.cpp create mode 100644 src/shell/kidenticongenerator.h diff --git a/src/plasma/containment.cpp b/src/plasma/containment.cpp index 1322b0b50..1b5be33a3 100644 --- a/src/plasma/containment.cpp +++ b/src/plasma/containment.cpp @@ -179,6 +179,7 @@ void Containment::restore(KConfigGroup &group) setLocation((Plasma::Types::Location)group.readEntry("location", (int)d->location)); setFormFactor((Plasma::Types::FormFactor)group.readEntry("formfactor", (int)d->formFactor)); + d->lastScreen = group.readEntry("lastScreen", d->lastScreen); setWallpaper(group.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper)); @@ -245,6 +246,7 @@ void Containment::save(KConfigGroup &g) const } group.writeEntry("screen", d->screen); + group.writeEntry("lastScreen", d->lastScreen); group.writeEntry("formfactor", (int)d->formFactor); group.writeEntry("location", (int)d->location); group.writeEntry("activityId", d->activityId); @@ -452,6 +454,11 @@ int Containment::screen() const return d->screen; } +int Containment::lastScreen() const +{ + return d->lastScreen; +} + void Containment::setDrawWallpaper(bool drawWallpaper) { if (d->drawWallpaper == drawWallpaper) { diff --git a/src/plasma/containment.h b/src/plasma/containment.h index 5e810546c..874fb3977 100644 --- a/src/plasma/containment.h +++ b/src/plasma/containment.h @@ -148,6 +148,13 @@ class PLASMA_EXPORT Containment : public Applet */ int screen() const; + /** + * @return the last screen number this containment had + * only returns -1 if it's never ever been on a screen + * @since 4.5 + */ + int lastScreen() const; + /** * @reimp * @sa Applet::save(KConfigGroup &) diff --git a/src/plasma/corona.cpp b/src/plasma/corona.cpp index f72dfd5c2..cf833e464 100644 --- a/src/plasma/corona.cpp +++ b/src/plasma/corona.cpp @@ -175,6 +175,22 @@ Containment *Corona::containmentForScreen(int screen) const return 0; } +Containment *Corona::containmentForScreen(int screen, const QString &defaultPluginIfNonExistent) +{ + Containment *containment = containmentForScreen(screen); + if (!containment && !defaultPluginIfNonExistent.isEmpty()) { + // screen requests are allowed to bypass immutability + if (screen >= 0 && screen < numScreens()) { + containment = d->addContainment(defaultPluginIfNonExistent, QVariantList(), 0); + if (containment) { + containment->setScreen(screen); + } + } + } + + return containment; +} + QList Corona::containments() const { return d->containments; diff --git a/src/plasma/corona.h b/src/plasma/corona.h index 927d801d3..a09d569cf 100644 --- a/src/plasma/corona.h +++ b/src/plasma/corona.h @@ -99,6 +99,17 @@ public: */ Containment *containmentForScreen(int screen) const; + /** + * Returns the Containment for a given physical screen and desktop, creating one + * if none exists + * + * @param screen number of the physical screen to locate + * @param defaultPluginIfNonExistent the plugin to load by default; "null" is an empty + * Containment and "default" creates the default plugin + * @since 4.6 + */ + Containment *containmentForScreen(int screen, const QString &defaultPluginIfNonExistent); + /** * Returns the number of screens available to plasma. * Subclasses should override this method as the default diff --git a/src/plasma/private/containment_p.cpp b/src/plasma/private/containment_p.cpp index 5bbdbd2b9..a03b53176 100644 --- a/src/plasma/private/containment_p.cpp +++ b/src/plasma/private/containment_p.cpp @@ -132,6 +132,10 @@ void ContainmentPrivate::setScreen(int newScreen) #endif KConfigGroup c = q->config(); c.writeEntry("screen", screen); + if (newScreen != -1) { + lastScreen = newScreen; + c.writeEntry("lastScreen", lastScreen); + } emit q->configNeedsSaving(); emit q->screenChanged(oldScreen, newScreen, q); } diff --git a/src/plasma/private/containment_p.h b/src/plasma/private/containment_p.h index ad0c8d6b3..6c92d98d8 100644 --- a/src/plasma/private/containment_p.h +++ b/src/plasma/private/containment_p.h @@ -50,6 +50,7 @@ public: formFactor(Types::Planar), location(Types::Floating), screen(-1), // no screen + lastScreen(-1), // never had a screen type(Plasma::Types::NoContainmentType), drawWallpaper(false) { @@ -103,6 +104,7 @@ public: QString wallpaper; QHash localActionPlugins; int screen; + int lastScreen; QString activityId; Types::ContainmentType type; bool drawWallpaper : 1; diff --git a/src/shell/CMakeLists.txt b/src/shell/CMakeLists.txt index 549246764..558a8c534 100644 --- a/src/shell/CMakeLists.txt +++ b/src/shell/CMakeLists.txt @@ -43,10 +43,12 @@ set(widgetexplorer_SRC ) add_executable(plasma-shell + activity.cpp main.cpp containmentconfigview.cpp currentcontainmentactionsmodel.cpp desktopview.cpp + kidenticongenerator.cpp panelview.cpp panelconfigview.cpp panelshadows.cpp @@ -75,6 +77,8 @@ target_link_libraries(plasma-shell KF5::KDeclarative KF5::KI18n KF5::XmlGui + KF5::KIconThemes + /opt/kde5/lib64/libkactivities.so ) target_include_directories(plasma-shell PRIVATE "${CMAKE_BINARY_DIR}") diff --git a/src/shell/activity.cpp b/src/shell/activity.cpp new file mode 100644 index 000000000..e1f26ca4a --- /dev/null +++ b/src/shell/activity.cpp @@ -0,0 +1,401 @@ +/* + * Copyright 2010 Chani Armitage + * + * 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, + * 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 Library 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 "shellcorona.h" +#include "kidenticongenerator.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "activity.h" + +Activity::Activity(const QString &id, Plasma::Corona *parent) + : QObject(parent), + m_id(id), + m_plugin("default"), + m_info(new KActivities::Info(id, this)), + m_activityConsumer(new KActivities::Consumer(this)), + m_corona(parent), + m_current(false) +{ + m_name = m_info->name(); + m_icon = m_info->icon(); + + connect(m_info, SIGNAL(infoChanged()), this, SLOT(activityChanged())); + connect(m_info, SIGNAL(stateChanged(KActivities::Info::State)), this, SLOT(activityStateChanged(KActivities::Info::State))); + connect(m_info, SIGNAL(started()), this, SLOT(opened())); + connect(m_info, SIGNAL(stopped()), this, SLOT(closed())); + connect(m_info, SIGNAL(removed()), this, SLOT(removed())); + + connect(m_activityConsumer, SIGNAL(currentActivityChanged(QString)), this, SLOT(checkIfCurrent())); + checkIfCurrent(); + + //find your containments + foreach (Plasma::Containment *cont, m_corona->containments()) { + if (cont->containmentType() == Plasma::Types::DesktopContainment || + cont->containmentType() == Plasma::Types::CustomContainment) { + insertContainment(cont); + } + } + + //qDebug() << m_containments.size(); +} + +Activity::~Activity() +{ +} + +void Activity::activityChanged() +{ + setName(m_info->name()); + setIcon(m_info->icon()); +} + +void Activity::activityStateChanged(KActivities::Info::State state) +{ + Q_UNUSED(state) + emit stateChanged(); +} + +QString Activity::id() +{ + return m_id; +} + +QString Activity::name() +{ + return m_name; +} + +QPixmap Activity::pixmap(const QSize &size) +{ + if (m_info->isValid() && !m_info->icon().isEmpty()) { + return KIcon(m_info->icon()).pixmap(size); + } else { + return KIdenticonGenerator::self()->generatePixmap(size.width(), m_id); + } +} + +bool Activity::isCurrent() +{ + return m_current; + //TODO maybe plasmaapp should cache the current activity to reduce dbus calls? +} + +void Activity::checkIfCurrent() +{ + const bool current = m_id == m_activityConsumer->currentActivity(); + if (current != m_current) { + m_current = current; + emit currentStatusChanged(); + } +} + +KActivities::Info::State Activity::state() +{ + return m_info->state(); +} + +void Activity::remove() +{ + KActivities::Controller().removeActivity(m_id); +} + +void Activity::removed() +{ + if (! m_containments.isEmpty()) { + //FIXME only m_corona has authority to remove properly + qDebug() << "!!!!! if your widgets are locked you've hit a BUG now"; + foreach (Plasma::Containment *c, m_containments) { + c->destroy(); + } + } + + const QString name = "activities/" + m_id; + QFile::remove(KStandardDirs::locateLocal("appdata", name)); +} + +Plasma::Containment* Activity::containmentForScreen(int screen) +{ + Plasma::Containment *containment = m_containments.value(screen); + + if (!containment) { + qDebug() << "adding containment for" << screen; + // first look to see if there are any unnasigned containments that are candidates for + // being sucked into this Activity + foreach (Plasma::Containment *c, m_corona->containments()) { + if ((c->containmentType() == Plasma::Types::DesktopContainment || + c->containmentType() == Plasma::Types::CustomContainment) && + c->activity().isEmpty() && + m_containments.key(c, -2) == -2) { + containment = c; + containment->setScreen(screen); + break; + } + } + + if (!containment) { + // we ask for the containment for the screen with a default plugin, because + // this allows the corona to either grab one for us that already exists matching + // screen, or create a new one. this also works regardless of immutability + containment = m_corona->containmentForScreen(screen, m_plugin); + + if (!containment || !containment->activity().isEmpty()) { + // possibly a plugin failure, let's go for the default + containment = m_corona->containmentForScreen(screen, "default"); + } + + //we don't want to steal contaiments from other activities + if (!containment) { + // we failed to even get the default; we're screwed. + Q_ASSERT(false); + return 0; + } + + if (!containment->activity().isEmpty() && + containment->activity() != m_id) { + // we got a containment, but it belongs to some other activity; let's unassign it + // from a screen and grab a new one + containment->setScreen(-1); + containment = m_corona->containmentForScreen(screen, m_plugin); + + if (!containment) { + // possibly a plugin failure, let's go for the default + containment = m_corona->containmentForScreen(screen, "default"); + } + + if (containment) { + containment->setScreen(screen); + } + } + } + + if (containment) { + insertContainment(containment, screen); + m_corona->requestConfigSync(); + } + } else if (containment->screen() != screen) { + // ensure the containment _also_ knows which screen we think it is on; + // can happen when swapping between activities without stopping them first + containment->setScreen(screen); + } + + return containment; +} + +void Activity::activate() +{ + KActivities::Controller().setCurrentActivity(m_id); +} + +void Activity::ensureActive() +{ + if (m_containments.isEmpty()) { + opened(); + } + + checkScreens(); +} + +void Activity::checkScreens() +{ + //ensure there's a containment for every screen & desktop. + int numScreens = m_corona->numScreens(); + + for (int screen = 0; screen < numScreens; ++screen) { + containmentForScreen(screen); + } +} + +void Activity::setName(const QString &name) +{ + if (m_name == name) { + return; + } + + m_name = name; +} + +void Activity::setIcon(const QString &icon) +{ + if (m_icon == icon) { + return; + } + + m_icon = icon; +} + +void Activity::save(KConfig &external) +{ + foreach (const QString &group, external.groupList()) { + KConfigGroup cg(&external, group); + cg.deleteGroup(); + } + + //TODO: multi-screen saving/restoring, where each screen can be + // independently restored: put each screen's containments into a + // different group, e.g. [Screens][0][Containments], [Screens][1][Containments], etc + KConfigGroup dest(&external, "Containments"); + KConfigGroup dummy; + foreach (Plasma::Containment *c, m_containments) { + c->save(dummy); + KConfigGroup group(&dest, QString::number(c->id())); + c->config().copyTo(&group); + } + + external.sync(); +} + +void Activity::close() +{ + KActivities::Controller().stopActivity(m_id); +} + +void Activity::closed() +{ + const QString name = "activities/" + m_id; + KConfig external(name, KConfig::SimpleConfig, QStandardPaths::GenericDataLocation); + + //passing an empty string for the group name turns a kconfig into a kconfiggroup + KConfigGroup group = external.group(QString()); + m_corona->exportLayout(group, m_containments.values()); + + //hrm, shouldn't the containments' deleted signals have done this for us? + if (!m_containments.isEmpty()) { + qDebug() << "isn't close supposed to *remove* containments??"; + m_containments.clear(); + } +} + +void Activity::replaceContainment(Plasma::Containment* containment) +{ + insertContainment(containment, true); +} + +void Activity::insertContainment(Plasma::Containment* cont, bool force) +{ + int screen = cont->lastScreen(); + + qDebug() << screen; + if (screen == -1) { + //the migration can't set lastScreen, so maybe we need to assign the containment here + qDebug() << "found a lost one"; + screen = 0; + } + + if (!force && m_containments.contains(screen)) { + //this almost certainly means someone has been meddling where they shouldn't + //but we should protect them from harm anyways + qDebug() << "@!@!@!@!@!@@@@rejecting containment!!!"; + cont->setActivity(QString()); + return; + } + + insertContainment(cont, screen); +} + +void Activity::insertContainment(Plasma::Containment* containment, int screen) +{ + //ensure it's hooked up + containment->setActivity(m_id); + + m_containments.insert(screen, containment); + connect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDestroyed(QObject*))); +} + +void Activity::containmentDestroyed(QObject *object) +{ + //safe here because we are not accessing it + Plasma::Containment *deletedCont = static_cast(object); + + QHash::iterator i; + for (i = m_containments.begin(); i != m_containments.end(); ++i) { + Plasma::Containment *cont = i.value(); + if (cont == deletedCont) { + m_containments.remove(i.key()); + break; + } + } +} + +void Activity::open() +{ + KActivities::Controller().startActivity(m_id); +} + +void Activity::opened() +{ + if (!m_containments.isEmpty()) { + qDebug() << "already open!"; + return; + } + + const QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/activities/" + m_id); + qDebug() << "&&&&&&&&&&&&&&&" << fileName; + if (QFile::exists(fileName)) { + { + KConfig external(fileName, KConfig::SimpleConfig); + + foreach (Plasma::Containment *newContainment, m_corona->importLayout(external.group(QByteArray()))) { + insertContainment(newContainment); + //ensure it's hooked up (if something odd happened we don't want orphan containments) + newContainment->setActivity(m_id); + } + } + + QFile::remove(fileName); + } + + if (m_containments.isEmpty()) { + //TODO check if we need more for screens/desktops + qDebug() << "open failed (bad file?). creating new containment"; + checkScreens(); + } + + m_corona->requireConfigSync(); +} + +void Activity::setDefaultPlugin(const QString &plugin) +{ + m_plugin = plugin; + //FIXME save&restore this setting +} + +const KActivities::Info * Activity::info() const +{ + return m_info; +} + +#include "activity.moc" + +// vim: sw=4 sts=4 et tw=100 diff --git a/src/shell/activity.h b/src/shell/activity.h new file mode 100644 index 000000000..d57156e44 --- /dev/null +++ b/src/shell/activity.h @@ -0,0 +1,170 @@ +/* + * Copyright 2010 Chani Armitage + * + * 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, + * 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 Library 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. + */ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + +#include +#include + +#include + +class QSize; +class QString; +class QPixmap; +class KConfig; + +namespace KActivities +{ + class Consumer; +} // namespace KActivities + + +namespace Plasma +{ + class Containment; + class Corona; +} // namespace Plasma + +class DesktopCorona; + +/** + * This class represents one activity. + * an activity has an ID and a name, from nepomuk. + * it also is associated with one or more containments. + * + * do NOT construct these yourself; use DesktopCorona::activity() + */ +class Activity : public QObject +{ + Q_OBJECT +public: + Activity(const QString &id, Plasma::Corona *parent = 0); + ~Activity(); + + QString id(); + QString name(); + QPixmap pixmap(const QSize &size); //FIXME do we want diff. sizes? updates? + + enum State { + Invalid = KActivities::Info::Invalid, + Running = KActivities::Info::Running, + Starting = KActivities::Info::Starting, + Stopped = KActivities::Info::Stopped, + Stopping = KActivities::Info::Stopping, + PreCreation = 32 + }; + + /** + * whether this is the currently active activity + */ + bool isCurrent(); + + /** + * state of the activity + */ + KActivities::Info::State state(); + + /** + * save (copy) the activity out to an @p external config + */ + void save(KConfig &external); + + /** + * return the containment that belongs on @p screen and @p desktop + * or null if none exists + */ + Plasma::Containment* containmentForScreen(int screen); + + /** + * make this activity's containments the active ones, loading them if necessary + */ + void ensureActive(); + + /** + * set the plugin to use when creating new containments + */ + void setDefaultPlugin(const QString &plugin); + + /** + * @returns the info object for this activity + */ + const KActivities::Info * info() const; + +Q_SIGNALS: + void infoChanged(); + void stateChanged(); + void currentStatusChanged(); + +public Q_SLOTS: + void setName(const QString &name); + void setIcon(const QString &icon); + + /** + * delete the activity forever + */ + void remove(); + + /** + * make this activity the current activity + */ + void activate(); + + /** + * save and remove all our containments + */ + void close(); + + /** + * load the saved containment(s) for this activity + */ + void open(); + + /** + * forcibly insert a containment, replacing the one on its screen/desktop + */ + void replaceContainment(Plasma::Containment* containment); + +private Q_SLOTS: + void containmentDestroyed(QObject *object); + void activityChanged(); + void activityStateChanged(KActivities::Info::State); + void checkIfCurrent(); + + void removed(); + void opened(); + void closed(); + +private: + void insertContainment(Plasma::Containment* cont, bool force=false); + void insertContainment(Plasma::Containment* containment, int screen); + void checkScreens(); + + QString m_id; + QString m_name; + QString m_icon; + QString m_plugin; + QHash m_containments; + KActivities::Info *m_info; + KActivities::Consumer *m_activityConsumer; + Plasma::Corona *m_corona; + bool m_current; +}; + +#endif diff --git a/src/shell/kidenticongenerator.cpp b/src/shell/kidenticongenerator.cpp new file mode 100644 index 000000000..4505666ae --- /dev/null +++ b/src/shell/kidenticongenerator.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2010 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * 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 Library/Lesser 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 "kidenticongenerator.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#define VALUE_LIMIT_UP 192 +#define VALUE_LIMIT_DOWN 64 + +class KIdenticonGenerator::Private { +public: + QPixmap generatePattern(int size, quint32 hash, QIcon::Mode mode); + + QString elementName(const QString & element, QIcon::Mode mode); + QColor colorForHash(quint32 hash) const; + quint32 hash(const QString & data); + + static KIdenticonGenerator * instance; + + Plasma::Theme *theme; + Plasma::Svg shapes; + Plasma::Svg svg; +}; + +QPixmap KIdenticonGenerator::Private::generatePattern(int size, quint32 hash, QIcon::Mode mode) +{ + // We are dividing the pixmap into 9 blocks - 3 x 3 + int blockSize = size / 3; + + // pulling parts of the hash + quint32 tmp = hash; + + quint8 block[4]; + block[0] = tmp & 31; tmp >>= 5; + block[1] = tmp & 31; tmp >>= 5; + block[2] = tmp & 31; tmp >>= 5; + + // Painting alpha channel + QPixmap pixmapAlpha(size, size); + pixmapAlpha.fill(Qt::black); + + QPainter painterAlpha(& pixmapAlpha); + + QRectF rect(0, 0, blockSize + 0.5, blockSize + 0.5); + + for (int i = 0; i < 4; i++) { + // Painting the corner item + rect.moveTopLeft(QPoint(0, 0)); + shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[0] + 1)); + + // Painting side item + rect.moveTopLeft(QPoint(blockSize, 0)); + shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[1] + 1)); + + // Rotating the canvas to paint other edges + painterAlpha.translate(size, 0); + painterAlpha.rotate(90); + } + + // Painting center item + rect.moveTopLeft(QPoint(blockSize, blockSize)); + shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[2] + 1)); + + painterAlpha.end(); + + // Painting final pixmap + QPixmap pixmapResult(size, size); + + + pixmapResult.fill(Qt::transparent); + + // QRadialGradient gradient(50, 50, 100); + // gradient.setColorAt(0, color.lighter()); + // gradient.setColorAt(1, color.darker()); + + QPainter resultPainter(& pixmapResult); + // resultPainter.fillRect(0, 0, size, size, gradient); + svg.paint(& resultPainter, QRect(0, 0, size, size), elementName("content", mode)); + + resultPainter.end(); + + pixmapResult.setAlphaChannel(pixmapAlpha); + + // QImage itmp = pixmapResult.toImage(); + // KIconEffect::colorize(itmp, colorForHash(hash), 1.0); + // pixmapResult = pixmapResult.fromImage(itmp); + + return pixmapResult; +} + +QColor KIdenticonGenerator::Private::colorForHash(quint32 hash) const +{ + // Color is chosen according to hash + QColor color; + + // Getting the value from color svg, but we must restrain it to + // values in range from VALUE_LIMIT_DOWN to VALUE_LIMIT_UP + + int value = theme->color(Plasma::Theme::TextColor).value(); + if (value < VALUE_LIMIT_DOWN) { + value = VALUE_LIMIT_DOWN; + } else if (value > VALUE_LIMIT_UP) { + value = VALUE_LIMIT_UP; + } + + color.setHsv( + hash % 359 + 1, // hue depending on hash + 250, // high saturation level + value + ); + + return color; + +} + +QString KIdenticonGenerator::Private::elementName(const QString & element, QIcon::Mode mode) +{ + QString prefix; + + switch (mode) { + case QIcon::Normal: + prefix = "normal-"; + break; + + case QIcon::Disabled: + prefix = "disabled-"; + break; + + case QIcon::Selected: + prefix = "selected-"; + break; + + case QIcon::Active: + prefix = "active-"; + break; + + default: + break; + + } + + if (svg.hasElement(prefix + element)) { + return prefix + element; + } else { + return element; + } +} + +quint32 KIdenticonGenerator::Private::hash(const QString & data) +{ + // qHash function doesn't give random enough results + // and gives similar hashes for similar strings. + + QByteArray bytes = QCryptographicHash::hash(data.toUtf8(), QCryptographicHash::Md5); + + // Generating hash + quint32 hash = 0; + + char * hashBytes = (char *) & hash; + for (int i = 0; i < bytes.size(); i++) { + // Using XOR for mixing the bytes because + // it is fast and cryptographically safe + // (more than enough for our use-case) + hashBytes[i % 4] ^= bytes.at(i); + } + + return hash; +} + +KIdenticonGenerator * KIdenticonGenerator::Private::instance = NULL; + +KIdenticonGenerator * KIdenticonGenerator::self() +{ + if (!Private::instance) { + Private::instance = new KIdenticonGenerator(); + } + + return Private::instance; +} + +KIdenticonGenerator::KIdenticonGenerator() + : d(new Private()) +{ + d->theme = new Plasma::Theme(0); + // loading SVGs + d->shapes.setImagePath("widgets/identiconshapes"); + d->shapes.setContainsMultipleImages(true); + + d->svg.setImagePath("widgets/identiconsvg"); + d->svg.setContainsMultipleImages(true); +} + +KIdenticonGenerator::~KIdenticonGenerator() +{ + delete d->theme; +} + +#define generateIconModes( PARAM ) \ + for (int omode = QIcon::Normal; omode <= QIcon::Selected; omode++) { \ + QIcon::Mode mode = (QIcon::Mode)omode; \ + result.addPixmap(generatePixmap(size, PARAM, mode), mode); \ + } + +QIcon KIdenticonGenerator::generate(int size, quint32 hash) +{ + QIcon result; + generateIconModes(hash); + return result; +} + +QIcon KIdenticonGenerator::generate(int size, const QString & data) +{ + QIcon result; + generateIconModes(data); + return result; +} + +QIcon KIdenticonGenerator::generate(int size, const QIcon & icon) +{ + QIcon result; + generateIconModes(icon); + return result; +} + +QPixmap KIdenticonGenerator::generatePixmap(int size, QString id, QIcon::Mode mode) +{ + return generatePixmap(size, d->hash(id), mode); +} + +QPixmap KIdenticonGenerator::generatePixmap(int size, quint32 hash, QIcon::Mode mode) +{ + QPixmap pixmap(size, size); + pixmap.fill(Qt::transparent); + + // Painting background and the pattern + { + QPainter painter(& pixmap); + d->svg.paint(& painter, QRect(0, 0, size, size), d->elementName("background", mode)); + painter.drawPixmap(0, 0, d->generatePattern(size, hash, mode)); + painter.end(); + } + + // coloring the painted image + QImage itmp = pixmap.toImage(); + KIconEffect::colorize(itmp, d->colorForHash(hash), 1.0); + if (mode == QIcon::Disabled) { + KIconEffect::toGray(itmp, 0.7); + } + pixmap = pixmap.fromImage(itmp); + + // Drawing the overlay + { + QPainter painter(& pixmap); + d->svg.paint(& painter, QRect(0, 0, size, size), d->elementName("overlay", mode)); + } + + return pixmap; +} + +QPixmap KIdenticonGenerator::generatePixmap(int size, const QIcon & icon, QIcon::Mode mode) +{ + QPixmap pixmap(size, size); + pixmap.fill(Qt::transparent); + + QRect paintRect(0, 0, size, size); + + // Painting background and the pattern + QPainter painter(& pixmap); + d->svg.paint(& painter, QRect(0, 0, size, size), d->elementName("background", mode)); + + icon.paint(& painter, paintRect, Qt::AlignCenter, mode); + + painter.end(); + + return pixmap; +} + diff --git a/src/shell/kidenticongenerator.h b/src/shell/kidenticongenerator.h new file mode 100644 index 000000000..0c3ca5eab --- /dev/null +++ b/src/shell/kidenticongenerator.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * 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 Library/Lesser 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. + */ + +#ifndef KIDENTICONGENERATOR_H +#define KIDENTICONGENERATOR_H + +#include +#include + +#include + +class KIdenticonGenerator { +public: + static KIdenticonGenerator * self(); + ~KIdenticonGenerator(); + + QPixmap generatePixmap(int size, QString id, QIcon::Mode mode = QIcon::Normal); + QPixmap generatePixmap(int size, quint32 hash, QIcon::Mode mode = QIcon::Normal); + QPixmap generatePixmap(int size, const QIcon & icon, QIcon::Mode mode = QIcon::Normal); + + QIcon generate(int size, const QString & data); + QIcon generate(int size, quint32 hash); + QIcon generate(int size, const QIcon & icon); + +private: + KIdenticonGenerator(); + + class Private; + Private * const d; +}; + +#endif // KIDENTICONGENERATOR_H