File indexing completed on 2024-04-21 15:08:50

0001 /***************************************************************************
0002  *   Copyright (C) 2012-2016 by Daniel Nicoletti <dantti12@gmail.com>      *
0003  *   2022 by Han Young <hanyoung@protonmail.com>                           *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; see the file COPYING. If not, write to       *
0017  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0018  *   Boston, MA 02110-1301, USA.                                           *
0019  ***************************************************************************/
0020 
0021 #include "ProfileModel.h"
0022 #include "Profile.h"
0023 
0024 #include "CdInterface.h"
0025 #include "CdProfileInterface.h"
0026 
0027 #include <QDBusConnection>
0028 #include <QDBusMetaType>
0029 #include <QDateTime>
0030 #include <QDebug>
0031 #include <QFileInfo>
0032 #include <QIcon>
0033 
0034 #include <KLocalizedString>
0035 
0036 typedef QList<QDBusObjectPath> ObjectPathList;
0037 
0038 ProfileModel::ProfileModel(CdInterface *cdInterface, QObject *parent)
0039     : QStandardItemModel(parent)
0040 {
0041     qDBusRegisterMetaType<ObjectPathList>();
0042     qDBusRegisterMetaType<CdStringMap>();
0043 
0044     // listen to colord for events
0045     connect(cdInterface, &CdInterface::ProfileAdded, this, &ProfileModel::profileAddedEmit);
0046     connect(cdInterface, &CdInterface::ProfileRemoved, this, &ProfileModel::profileRemoved);
0047     connect(cdInterface, &CdInterface::ProfileChanged, this, &ProfileModel::profileChanged);
0048 
0049     // Ask for profiles
0050     auto async = cdInterface->GetProfiles();
0051     auto watcher = new QDBusPendingCallWatcher(async, this);
0052     connect(watcher, &QDBusPendingCallWatcher::finished, this, &ProfileModel::gotProfiles);
0053 }
0054 
0055 void ProfileModel::gotProfiles(QDBusPendingCallWatcher *call)
0056 {
0057     QDBusPendingReply<ObjectPathList> reply = *call;
0058     if (reply.isError()) {
0059         qWarning() << "Unexpected message" << reply.error().message();
0060     } else {
0061         const ObjectPathList profiles = reply.argumentAt<0>();
0062         for (const QDBusObjectPath &profile : profiles) {
0063             profileAdded(profile, false);
0064         }
0065         Q_EMIT changed();
0066     }
0067     call->deleteLater();
0068 }
0069 
0070 void ProfileModel::profileChanged(const QDBusObjectPath &objectPath)
0071 {
0072     int row = findItem(objectPath);
0073     if (row == -1) {
0074         qWarning() << "Profile not found" << objectPath.path();
0075         return;
0076     }
0077 
0078     Q_EMIT dataChanged(index(row, 0), index(row, 0));
0079 }
0080 
0081 void ProfileModel::profileAdded(const QDBusObjectPath &objectPath, bool emitChanged)
0082 {
0083     if (findItem(objectPath) != -1) {
0084         qWarning() << "Profile is already on the list" << objectPath.path();
0085         return;
0086     }
0087 
0088     CdProfileInterface profile(QStringLiteral("org.freedesktop.ColorManager"), objectPath.path(), QDBusConnection::systemBus());
0089     if (!profile.isValid()) {
0090         return;
0091     }
0092 
0093     // Verify if the profile has a filename
0094     QString filename = profile.filename();
0095     if (filename.isEmpty()) {
0096         return;
0097     }
0098 
0099     // Check if we can read the profile
0100     QFileInfo fileInfo(filename);
0101     if (!fileInfo.isReadable()) {
0102         return;
0103     }
0104 
0105     const QString dataSource = getProfileDataSource(profile.metadata());
0106     const QString profileId = profile.profileId();
0107     QString title = profile.title();
0108     const QString kind = profile.kind();
0109     const QString colorspace = profile.colorspace();
0110     const qlonglong created = profile.created();
0111 
0112     QStandardItem *item = new QStandardItem;
0113     item->setData(colorspace, ColorspaceRole);
0114     // Choose a nice icon
0115     if (kind == QLatin1String("display-device")) {
0116         item->setData(QStringLiteral("video-display"), Qt::DecorationRole);
0117     } else if (kind == QLatin1String("input-device")) {
0118         item->setData(QStringLiteral("scanner"), Qt::DecorationRole);
0119     } else if (kind == QLatin1String("output-device")) {
0120         if (colorspace == QLatin1String("gray")) {
0121             item->setData(QStringLiteral("printer-laser"), Qt::DecorationRole);
0122         } else {
0123             item->setData(QStringLiteral("printer"), Qt::DecorationRole);
0124         }
0125     } else if (kind == QLatin1String("colorspace-conversion")) {
0126         item->setData(QStringLiteral("view-refresh"), Qt::DecorationRole);
0127     } else if (kind == QLatin1String("abstract")) {
0128         item->setData(QStringLiteral("insert-link"), Qt::DecorationRole);
0129     } else if (kind == QLatin1String("named-color")) {
0130         item->setData(QStringLiteral("view-preview"), Qt::DecorationRole);
0131     } else {
0132         item->setData(QStringLiteral("image-missing"), Qt::DecorationRole);
0133     }
0134 
0135     // Sets the profile title
0136     if (title.isEmpty()) {
0137         title = profileId;
0138     } else {
0139         QDateTime createdDT;
0140         createdDT.setSecsSinceEpoch(created);
0141         title = Profile::profileWithSource(dataSource, title, createdDT);
0142     }
0143     item->setText(title);
0144 
0145     item->setData(QVariant::fromValue(objectPath), ObjectPathRole);
0146     item->setData(QString(getSortChar(kind) + title), SortRole);
0147     item->setData(filename, FilenameRole);
0148     item->setData(kind, ProfileKindRole);
0149 
0150     bool canRemoveProfile = false;
0151     if (!filename.isNull() && fileInfo.isWritable() && dataSource != QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_EDID)) {
0152         // if we can write we can also remove the profile
0153         canRemoveProfile = true;
0154     }
0155     item->setData(canRemoveProfile, CanRemoveProfileRole);
0156 
0157     appendRow(item);
0158 
0159     if (emitChanged) {
0160         Q_EMIT changed();
0161     }
0162 }
0163 
0164 void ProfileModel::profileAddedEmit(const QDBusObjectPath &objectPath)
0165 {
0166     profileAdded(objectPath);
0167 }
0168 
0169 void ProfileModel::profileRemoved(const QDBusObjectPath &objectPath)
0170 {
0171     int row = findItem(objectPath);
0172     if (row != -1) {
0173         removeRow(row);
0174     }
0175 
0176     Q_EMIT changed();
0177 }
0178 
0179 void ProfileModel::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0180 {
0181     Q_UNUSED(serviceName)
0182     if (newOwner.isEmpty() || oldOwner != newOwner) {
0183         // colord has quit or restarted
0184         removeRows(0, rowCount());
0185         Q_EMIT changed();
0186     }
0187 }
0188 
0189 int ProfileModel::findItem(const QDBusObjectPath &objectPath)
0190 {
0191     for (int i = 0; i < rowCount(); ++i) {
0192         if (item(i)->data(ObjectPathRole).value<QDBusObjectPath>() == objectPath) {
0193             return i;
0194         }
0195     }
0196     return -1;
0197 }
0198 
0199 QChar ProfileModel::getSortChar(const QString &kind)
0200 {
0201     if (kind == QLatin1String("display-device")) {
0202         return QLatin1Char('1');
0203     }
0204     if (kind == QLatin1String("input-device")) {
0205         return QLatin1Char('2');
0206     }
0207     if (kind == QLatin1String("output-device")) {
0208         return QLatin1Char('3');
0209     }
0210     return QLatin1Char('4');
0211 }
0212 
0213 QString ProfileModel::getProfileDataSource(const CdStringMap &metadata)
0214 {
0215     QString dataSource;
0216     auto it = metadata.constFind(QStringLiteral("DATA_source"));
0217     if (it != metadata.constEnd()) {
0218         dataSource = it.value();
0219     }
0220     return dataSource;
0221 }
0222 
0223 QVariant ProfileModel::headerData(int section, Qt::Orientation orientation, int role) const
0224 {
0225     if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0226         return i18n("Profiles");
0227     }
0228     return QVariant();
0229 }
0230 
0231 Qt::ItemFlags ProfileModel::flags(const QModelIndex &index) const
0232 {
0233     QStandardItem *stdItem = itemFromIndex(index);
0234     if (stdItem && stdItem->isCheckable() && stdItem->checkState() == Qt::Unchecked) {
0235         return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
0236     }
0237     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0238 }
0239 
0240 QHash<int, QByteArray> ProfileModel::roleNames() const
0241 {
0242     return {{Qt::DisplayRole, "title"},
0243             {ObjectPathRole, "objectPath"},
0244             {ParentObjectPathRole, "parentObjectPath"},
0245             {FilenameRole, "fileName"},
0246             {ProfileKindRole, "profileKind"},
0247             {CanRemoveProfileRole, "canRemove"},
0248             {SortRole, "sortString"},
0249             {ColorspaceRole, "colorspace"},
0250             {Qt::DecorationRole, "iconName"}};
0251 }
0252 
0253 #include "moc_ProfileModel.cpp"