/* Copyright © 2005-2007 Fredrik Höglund * (c)GPL2 (c)GPL3 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 or at your option version 3 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 General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * additional code: Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar) */ #include #include "thememodel.h" #include #include "crtheme.h" #include "cfgfile.h" #include #include //#define DUMP_FOUND_THEMES /////////////////////////////////////////////////////////////////////////////// XCursorThemeModel::XCursorThemeModel (QObject *parent) : QAbstractTableModel(parent) { insertThemes(); } XCursorThemeModel::~XCursorThemeModel() { qDeleteAll(mList); mList.clear(); } QVariant XCursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const { // Only provide text for the headers if (role != Qt::DisplayRole) return QVariant(); // Horizontal header labels if (orientation == Qt::Horizontal) { switch (section) { case NameColumn: return tr("Name"); case DescColumn: return tr("Description"); default: return QVariant(); } } // Numbered vertical header lables return QString(section); } QVariant XCursorThemeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mList.count()) return QVariant(); const XCursorThemeData *theme = mList.at(index.row()); // Text label if (role == Qt::DisplayRole) { switch (index.column()) { case NameColumn: return theme->title(); case DescColumn: return theme->description(); default: return QVariant(); } } // Description for the first name column if (role == XCursorThemeData::DisplayDetailRole && index.column() == NameColumn) return theme->description(); // Icon for the name column if (role == Qt::DecorationRole && index.column() == NameColumn) return theme->icon(); // return QVariant(); } void XCursorThemeModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); Q_UNUSED(order); // Sorting of the model isn't implemented, as the KCM currently uses // a sorting proxy model. } const XCursorThemeData *XCursorThemeModel::theme(const QModelIndex &index) { if (!index.isValid()) return NULL; if (index.row() < 0 || index.row() >= mList.count()) return NULL; return mList.at(index.row()); } QModelIndex XCursorThemeModel::findIndex(const QString &name) { uint hash = qHash(name); for (int i = 0; i < mList.count(); ++i) { const XCursorThemeData *theme = mList.at(i); if (theme->hash() == hash) return index(i, 0); } return QModelIndex(); } QModelIndex XCursorThemeModel::defaultIndex() { return findIndex(mDefaultName); } const QStringList XCursorThemeModel::searchPaths() { if (!mBaseDirs.isEmpty()) return mBaseDirs; // Get the search path from Xcursor QString path = XcursorLibraryPath(); // Separate the paths mBaseDirs = path.split(':', QString::SkipEmptyParts); // Remove duplicates QMutableStringListIterator i(mBaseDirs); while (i.hasNext()) { const QString path = i.next(); QMutableStringListIterator j(i); while (j.hasNext()) if (j.next() == path) j.remove(); } // Expand all occurrences of ~/ to the home dir mBaseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/'); return mBaseDirs; } bool XCursorThemeModel::hasTheme(const QString &name) const { const uint hash = qHash(name); foreach (const XCursorThemeData *theme, mList) if (theme->hash() == hash) return true; return false; } bool XCursorThemeModel::isCursorTheme(const QString &theme, const int depth) { // Prevent infinite recursion if (depth > 10) return false; // Search each icon theme directory for 'theme' foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists() || !dir.cd(theme)) continue; // If there's a cursors subdir, we'll assume this is a cursor theme if (dir.exists("cursors")) return true; // If the theme doesn't have an index.theme file, it can't inherit any themes if (!dir.exists("index.theme")) continue; // Open the index.theme file, so we can get the list of inherited themes QMultiMap cfg = loadCfgFile(dir.path()+"/index.theme", true); QStringList inherits = cfg.values("icon theme/inherits"); // Recurse through the list of inherited themes, to check if one of them is a cursor theme // note that items are reversed for (int f = inherits.size()-1; f >= 0; --f) { QString inh = inherits.at(f); // Avoid possible DoS if (inh == theme) continue; if (isCursorTheme(inh, depth+1)) return true; } } return false; } bool XCursorThemeModel::handleDefault(const QDir &themeDir) { QFileInfo info(themeDir.path()); // If "default" is a symlink if (info.isSymLink()) { QFileInfo target(info.symLinkTarget()); if (target.exists() && (target.isDir() || target.isSymLink())) mDefaultName = target.fileName(); return true; } // If there's no cursors subdir, or if it's empty if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors").entryList(QDir::Files | QDir::NoDotAndDotDot).isEmpty()) { if (themeDir.exists("index.theme")) { XCursorThemeData theme(themeDir); if (!theme.inherits().isEmpty()) mDefaultName = theme.inherits().at(0); } return true; } mDefaultName = QLatin1String("default"); return false; } //#define DUMP_FOUND_THEMES void XCursorThemeModel::processThemeDir(const QDir &themeDir) { #ifdef DUMP_FOUND_THEMES qDebug() << "looking at:" << themeDir.path(); #endif bool haveCursors = themeDir.exists("cursors"); // Special case handling of "default", since it's usually either a // symlink to another theme, or an empty theme that inherits another theme if (mDefaultName.isNull() && themeDir.dirName() == "default") { if (handleDefault(themeDir)) return; } // If the directory doesn't have a cursors subdir and lacks an // index.theme file it can't be a cursor theme. if (!themeDir.exists("index.theme") && !haveCursors) { //qDebug() << "IS NOT THEME" << themeDir; return; } // Create a cursor theme object for the theme dir XCursorThemeData *theme = new XCursorThemeData(themeDir); // Skip this theme if it's hidden #ifdef DUMP_FOUND_THEMES qDebug() << " theme name:" << theme->name() << "\n theme title:" << theme->title() << "\n theme desc:" << theme->description() << "\n theme sample:" << theme->sample() << "\n theme inherits:" << theme->inherits(); #endif if (theme->isHidden()) { //qDebug() << "HIDDEN THEME" << theme->name() << themeDir; delete theme; return; } // If there's no cursors subdirectory we'll do a recursive scan // to check if the theme inherits a theme with one if (!haveCursors) { bool foundCursorTheme = false; foreach (const QString &name, theme->inherits()) { if ((foundCursorTheme = isCursorTheme(name))) break; } if (!foundCursorTheme) { delete theme; return; } } // Append the theme to the list mList.append(theme); } void XCursorThemeModel::insertThemes() { // Scan each base dir for Xcursor themes and add them to the list foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); //qDebug() << "TOPLEVEL" << baseDir; if (!dir.exists()) continue; //qDebug() << " continue passed"; // Process each subdir in the directory foreach (const QString &name, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Executable)) { //qDebug() << " SUBDIR" << name; // Don't process the theme if a theme with the same name already exists // in the list. Xcursor will pick the first one it finds in that case, // and since we use the same search order, the one Xcursor picks should // be the one already in the list if (hasTheme(name)) { qDebug() << "duplicate theme:" << dir.path()+name; } if (!dir.cd(name)) { qDebug() << "can't cd:" << dir.path()+name; continue; } processThemeDir(dir); dir.cdUp(); // Return to the base dir } } // The theme Xcursor will end up using if no theme is configured //if (mDefaultName.isNull() || !hasTheme(mDefaultName)) mDefaultName = legacy->name(); } bool XCursorThemeModel::addTheme(const QDir &dir) { XCursorThemeData *theme = new XCursorThemeData(dir); // Don't add the theme to the list if it's hidden if (theme->isHidden()) { delete theme; return false; } // If an item with the same name already exists in the list, // we'll remove it before inserting the new one. for (int i = 0; i < mList.count(); ++i) { if (mList.at(i)->hash() == theme->hash()) { removeTheme(index(i, 0)); break; } } // Append the theme to the list beginInsertRows(QModelIndex(), rowCount(), rowCount()); mList.append(theme); endInsertRows(); return true; } void XCursorThemeModel::removeTheme(const QModelIndex &index) { if (!index.isValid()) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); delete mList.takeAt(index.row()); endRemoveRows(); }