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"