File indexing completed on 2024-04-14 14:32:48

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 "DeviceModel.h"
0022 
0023 #include "Profile.h"
0024 #include "ProfileModel.h"
0025 
0026 #include "CdDeviceInterface.h"
0027 #include "CdInterface.h"
0028 #include "CdProfileInterface.h"
0029 
0030 #include <KLocalizedString>
0031 #include <QDateTime>
0032 #include <QDebug>
0033 
0034 DeviceModel::DeviceModel(CdInterface *cdInterface, QObject *parent)
0035     : QStandardItemModel(parent)
0036     , m_cdInterface(cdInterface)
0037 {
0038     qDBusRegisterMetaType<CdStringMap>();
0039 
0040     // listen to colord for events
0041     connect(m_cdInterface, &CdInterface::DeviceAdded, this, &DeviceModel::deviceAddedEmit);
0042     connect(m_cdInterface, &CdInterface::DeviceRemoved, this, &DeviceModel::deviceRemoved);
0043     connect(m_cdInterface, &CdInterface::DeviceChanged, this, &DeviceModel::deviceChanged);
0044 
0045     // Ask for devices
0046     auto async = m_cdInterface->GetDevices();
0047     auto watcher = new QDBusPendingCallWatcher(async, this);
0048     connect(watcher, &QDBusPendingCallWatcher::finished, this, &DeviceModel::gotDevices);
0049 }
0050 
0051 int DeviceModel::findDeviceIndex(const QDBusObjectPath &device) const
0052 {
0053     for (int i = 0; i < rowCount(); i++) {
0054         auto _item = item(i);
0055         if (_item) {
0056             auto path = _item->data(ObjectPathRole).value<QDBusObjectPath>();
0057             if (path == device) {
0058                 return i;
0059             }
0060         }
0061     }
0062     return -1;
0063 }
0064 void DeviceModel::gotDevices(QDBusPendingCallWatcher *call)
0065 {
0066     QDBusPendingReply<ObjectPathList> reply = *call;
0067     if (reply.isError()) {
0068         qWarning() << "Unexpected message" << reply.error().message();
0069     } else {
0070         const ObjectPathList devices = reply.argumentAt<0>();
0071         for (const QDBusObjectPath &device : devices) {
0072             deviceAdded(device, false);
0073         }
0074         Q_EMIT changed();
0075     }
0076     call->deleteLater();
0077 }
0078 
0079 void DeviceModel::removeProfile(const QDBusObjectPath &profilePath, const QDBusObjectPath &devicePath)
0080 {
0081     CdDeviceInterface device(QStringLiteral("org.freedesktop.ColorManager"), devicePath.path(), QDBusConnection::systemBus());
0082     if (device.isValid()) {
0083         device.RemoveProfile(profilePath);
0084     }
0085 }
0086 
0087 void DeviceModel::deviceChanged(const QDBusObjectPath &objectPath)
0088 {
0089     int row = findItem(objectPath);
0090     if (row == -1) {
0091         qWarning() << "Device not found" << objectPath.path();
0092         return;
0093     }
0094 
0095     CdDeviceInterface device(QStringLiteral("org.freedesktop.ColorManager"), objectPath.path(), QDBusConnection::systemBus());
0096     if (!device.isValid()) {
0097         return;
0098     }
0099 
0100     const ObjectPathList profiles = device.profiles();
0101 
0102     // Normally just the profile list bound this device
0103     // is what changes including "Modified" property
0104     QStandardItem *stdItem = item(row);
0105     for (int i = 0; i < profiles.size(); ++i) {
0106         // Look for the desired profile
0107         QStandardItem *child = findProfile(stdItem, profiles.at(i));
0108         if (child) {
0109             // Check if the state has changed
0110             Qt::CheckState state = i ? Qt::Unchecked : Qt::Checked;
0111             if (child->checkState() != state) {
0112                 child->setCheckState(state);
0113             }
0114         } else {
0115             // Inserts the profile with the parent object Path
0116             QStandardItem *profileItem = createProfileItem(profiles.at(i), objectPath, !i);
0117             if (profileItem) {
0118                 stdItem->insertRow(i, profileItem);
0119             }
0120         }
0121     }
0122 
0123     // Remove the extra items it might have
0124     removeProfilesNotInList(stdItem, profiles);
0125 
0126     Q_EMIT changed();
0127 }
0128 
0129 void DeviceModel::deviceAdded(const QDBusObjectPath &objectPath, bool emitChanged)
0130 {
0131     if (findItem(objectPath) != -1) {
0132         qWarning() << "Device is already on the list" << objectPath.path();
0133         return;
0134     }
0135 
0136     CdDeviceInterface device(QStringLiteral("org.freedesktop.ColorManager"), objectPath.path(), QDBusConnection::systemBus());
0137     if (!device.isValid()) {
0138         return;
0139     }
0140 
0141     const QString deviceId = device.deviceId();
0142     QString kind = device.kind();
0143     const QString model = device.model();
0144     const QString vendor = device.vendor();
0145     const QString colorspace = device.colorspace();
0146     const ObjectPathList profiles = device.profiles();
0147 
0148     QStandardItem *item = new QStandardItem;
0149     item->setData(QVariant::fromValue(objectPath), ObjectPathRole);
0150     item->setData(true, IsDeviceRole);
0151 
0152     if (kind == QLatin1String("display")) {
0153         item->setData(QStringLiteral("video-display"), Qt::DecorationRole);
0154     } else if (kind == QLatin1String("scanner")) {
0155         item->setData(QStringLiteral("scanner"), Qt::DecorationRole);
0156     } else if (kind == QLatin1String("printer")) {
0157         if (colorspace == QLatin1String("gray")) {
0158             item->setData(QStringLiteral("printer-laser"), Qt::DecorationRole);
0159         } else {
0160             item->setData(QStringLiteral("printer"), Qt::DecorationRole);
0161         }
0162     } else if (kind == QLatin1String("webcam")) {
0163         item->setData(QStringLiteral("camera-web"), Qt::DecorationRole);
0164     }
0165     item->setData(colorspace, ColorspaceRole);
0166 
0167     if (model.isEmpty() && vendor.isEmpty()) {
0168         item->setText(deviceId);
0169     } else if (model.isEmpty()) {
0170         item->setText(vendor);
0171     } else if (vendor.isEmpty()) {
0172         item->setText(model);
0173     } else {
0174         item->setText(vendor % QLatin1String(" - ") % model);
0175     }
0176 
0177     item->setData(QString(kind % item->text()), SortRole);
0178 
0179     // Convert our Device Kind to Profile Kind
0180     if (kind == QLatin1String("display")) {
0181         kind = QStringLiteral("display-device");
0182     } else if (kind == QLatin1String("camera") || kind == QLatin1String("scanner") || kind == QLatin1String("webcam")) {
0183         kind = QStringLiteral("input-device");
0184     } else if (kind == QLatin1String("printer")) {
0185         kind = QStringLiteral("output-device");
0186     } else {
0187         kind = QStringLiteral("unknown");
0188     }
0189     item->setData(kind, ProfileKindRole);
0190     item->setData(QStringLiteral("device"), ItemTypeRole);
0191     appendRow(item);
0192 
0193     QList<QStandardItem *> profileItems;
0194     for (const QDBusObjectPath &profileObjectPath : profiles) {
0195         QStandardItem *profileItem = createProfileItem(profileObjectPath, objectPath, profileItems.isEmpty());
0196         if (profileItem) {
0197             profileItems << profileItem;
0198         }
0199     }
0200     item->appendRows(profileItems);
0201 
0202     if (emitChanged) {
0203         Q_EMIT changed();
0204     }
0205 }
0206 
0207 void DeviceModel::deviceAddedEmit(const QDBusObjectPath &objectPath)
0208 {
0209     deviceAdded(objectPath);
0210 }
0211 
0212 void DeviceModel::deviceRemoved(const QDBusObjectPath &objectPath)
0213 {
0214     int row = findItem(objectPath);
0215     if (row != -1) {
0216         removeRow(row);
0217     }
0218 
0219     Q_EMIT changed();
0220 }
0221 
0222 void DeviceModel::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0223 {
0224     Q_UNUSED(serviceName)
0225     if (newOwner.isEmpty() || oldOwner != newOwner) {
0226         // colord has quit or restarted
0227         removeRows(0, rowCount());
0228         Q_EMIT changed();
0229     }
0230 }
0231 
0232 QStandardItem *DeviceModel::createProfileItem(const QDBusObjectPath &objectPath, const QDBusObjectPath &parentObjectPath, bool checked)
0233 {
0234     CdProfileInterface profile(QStringLiteral("org.freedesktop.ColorManager"), objectPath.path(), QDBusConnection::systemBus());
0235     if (!profile.isValid()) {
0236         return nullptr;
0237     }
0238 
0239     QStandardItem *stdItem = new QStandardItem;
0240     const QString dataSource = ProfileModel::getProfileDataSource(profile.metadata());
0241     const QString kind = profile.kind();
0242     const QString filename = profile.filename();
0243     QString title = profile.title();
0244     const qlonglong created = profile.created();
0245 
0246     bool canRemoveProfile = true;
0247     if (title.isEmpty()) {
0248         QString colorspace = profile.colorspace();
0249         if (colorspace == QLatin1String("rgb")) {
0250             title = i18nc("colorspace", "Default RGB");
0251         } else if (colorspace == QLatin1String("cmyk")) {
0252             title = i18nc("colorspace", "Default CMYK");
0253         } else if (colorspace == QLatin1String("gray")) {
0254             title = i18nc("colorspace", "Default Gray");
0255         }
0256         canRemoveProfile = false;
0257     } else {
0258         QDateTime createdDT;
0259         createdDT.setSecsSinceEpoch(created);
0260         title = Profile::profileWithSource(dataSource, title, createdDT);
0261 
0262         if (dataSource == QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_EDID)) {
0263             canRemoveProfile = false;
0264         }
0265     }
0266     stdItem->setText(title);
0267     stdItem->setData(canRemoveProfile, CanRemoveProfileRole);
0268 
0269     stdItem->setData(QVariant::fromValue(objectPath), ObjectPathRole);
0270     stdItem->setData(QVariant::fromValue(parentObjectPath), ParentObjectPathRole);
0271     stdItem->setData(filename, FilenameRole);
0272     stdItem->setData(kind, ProfileKindRole);
0273     stdItem->setData(QString(ProfileModel::getSortChar(kind) % title), SortRole);
0274     stdItem->setCheckable(true);
0275     stdItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
0276     stdItem->setData(QStringLiteral("profile"), ItemTypeRole);
0277 
0278     return stdItem;
0279 }
0280 
0281 QStandardItem *DeviceModel::findProfile(QStandardItem *parent, const QDBusObjectPath &objectPath)
0282 {
0283     QStandardItem *child;
0284     for (int i = 0; i < parent->rowCount(); ++i) {
0285         child = parent->child(i);
0286         if (child->data(ObjectPathRole).value<QDBusObjectPath>() == objectPath) {
0287             return child;
0288         }
0289     }
0290     return nullptr;
0291 }
0292 
0293 void DeviceModel::removeProfilesNotInList(QStandardItem *parent, const ObjectPathList &profiles)
0294 {
0295     QStandardItem *child;
0296     for (int i = 0; i < parent->rowCount(); ++i) {
0297         child = parent->child(i);
0298         // If the profile object path is not on the list remove it
0299         if (!profiles.contains(child->data(ObjectPathRole).value<QDBusObjectPath>())) {
0300             parent->removeRow(i);
0301             // i index is now pointing to a different value
0302             // we need to go back so we don't skip an item
0303             --i;
0304         }
0305     }
0306 }
0307 
0308 int DeviceModel::findItem(const QDBusObjectPath &objectPath)
0309 {
0310     for (int i = 0; i < rowCount(); ++i) {
0311         if (item(i)->data(ObjectPathRole).value<QDBusObjectPath>() == objectPath) {
0312             return i;
0313         }
0314     }
0315     return -1;
0316 }
0317 
0318 QVariant DeviceModel::headerData(int section, Qt::Orientation orientation, int role) const
0319 {
0320     if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0321         return i18n("Devices");
0322     }
0323     return QVariant();
0324 }
0325 
0326 bool DeviceModel::setData(const QModelIndex &index, const QVariant &value, int role)
0327 {
0328     Q_UNUSED(value)
0329     Q_UNUSED(role)
0330 
0331     QStandardItem *stdItem = itemFromIndex(index);
0332     QDBusObjectPath parentObjPath = stdItem->data(ParentObjectPathRole).value<QDBusObjectPath>();
0333     CdDeviceInterface device(QStringLiteral("org.freedesktop.ColorManager"), parentObjPath.path(), QDBusConnection::systemBus());
0334     if (device.isValid()) {
0335         device.MakeProfileDefault(stdItem->data(ObjectPathRole).value<QDBusObjectPath>());
0336     }
0337 
0338     // We return false since colord will Q_EMIT a DeviceChanged signal telling us about this change
0339     return false;
0340 }
0341 
0342 Qt::ItemFlags DeviceModel::flags(const QModelIndex &index) const
0343 {
0344     QStandardItem *stdItem = itemFromIndex(index);
0345     if (stdItem && stdItem->isCheckable() && stdItem->checkState() == Qt::Unchecked) {
0346         return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
0347     }
0348     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0349 }
0350 
0351 QHash<int, QByteArray> DeviceModel::roleNames() const
0352 {
0353     return {{Qt::DisplayRole, "title"},
0354             {ObjectPathRole, "objectPath"},
0355             {ParentObjectPathRole, "parentObjectPath"},
0356             {FilenameRole, "fileName"},
0357             {ProfileKindRole, "profileKind"},
0358             {SortRole, "sortString"},
0359             {CanRemoveProfileRole, "canRemoveProfile"},
0360             {Qt::CheckStateRole, "profileCheckState"},
0361             {ItemTypeRole, "itemType"},
0362             {ColorspaceRole, "colorspace"},
0363             {Qt::DecorationRole, "iconName"}};
0364 }
0365 
0366 #include "moc_DeviceModel.cpp"