File indexing completed on 2024-04-14 04:15:17

0001 /*
0002     SPDX-FileCopyrightText: 2001 The Kompany
0003     SPDX-FileCopyrightText: 2002-2003 Ilya Konstantinov <kde-devel@future.shiny.co.il>
0004     SPDX-FileCopyrightText: 2002-2003 Marcus Meissner <marcus@jet.franken.de>
0005     SPDX-FileCopyrightText: 2003 Nadeem Hasan <nhasan@nadmm.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kamera.h"
0011 
0012 #include <QApplication>
0013 #include <QIcon>
0014 #include <QLabel>
0015 #include <QListView>
0016 #include <QMenu>
0017 #include <QStandardItemModel>
0018 #include <QVBoxLayout>
0019 
0020 #include "kameradevice.h"
0021 #include "kcm_kamera_log.h"
0022 #include <KActionCollection>
0023 #include <KConfig>
0024 #include <KConfigGroup>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 #include <KProtocolInfo>
0028 #include <KToolBar>
0029 
0030 K_PLUGIN_CLASS_WITH_JSON(KKameraConfig, "kcm_kamera.json")
0031 
0032 // --------------- Camera control center module widget ---
0033 KKameraConfig::KKameraConfig(QObject *parent, const KPluginMetaData &md)
0034     : KCModule(parent)
0035 {
0036 #ifdef DEBUG_KAMERA_KCONTROL
0037     QLoggingCategory::setFilterRules(QStringLiteral("kamera.kcm.debug = true"));
0038 #endif
0039     m_devicePopup = new QMenu(widget());
0040     m_actions = new KActionCollection(this);
0041     m_config = new KConfig(KProtocolInfo::config(QStringLiteral("camera")), KConfig::SimpleConfig);
0042     m_deviceModel = new QStandardItemModel(this);
0043 
0044     m_context = gp_context_new();
0045     if (m_context) {
0046         // Register the callback functions
0047         gp_context_set_cancel_func(m_context, cbGPCancel, this);
0048         gp_context_set_idle_func(m_context, cbGPIdle, this);
0049 
0050         displayGPSuccessDialogue();
0051     } else {
0052         displayGPFailureDialogue();
0053     }
0054 }
0055 
0056 KKameraConfig::~KKameraConfig()
0057 {
0058     delete m_config;
0059 }
0060 
0061 void KKameraConfig::defaults()
0062 {
0063 }
0064 
0065 void KKameraConfig::displayGPFailureDialogue()
0066 {
0067     auto topLayout = new QVBoxLayout(widget());
0068     topLayout->setSpacing(0);
0069     topLayout->setContentsMargins(0, 0, 0, 0);
0070     auto label = new QLabel(i18n("Unable to initialize the gPhoto2 libraries."), widget());
0071     topLayout->addWidget(label);
0072 }
0073 
0074 void KKameraConfig::displayGPSuccessDialogue()
0075 {
0076     // set the kcontrol module buttons
0077     setButtons(Help | Apply);
0078 
0079     // create a layout with two vertical boxes
0080     auto topLayout = new QVBoxLayout(widget());
0081     topLayout->setSpacing(0);
0082     topLayout->setContentsMargins(0, 0, 0, 0);
0083 
0084     m_toolbar = new KToolBar(widget(), "ToolBar");
0085     topLayout->addWidget(m_toolbar);
0086     m_toolbar->setMovable(false);
0087 
0088     // create list of devices - this is the large white box
0089     m_deviceSel = new QListView(widget());
0090     topLayout->addWidget(m_deviceSel);
0091 
0092     m_deviceSel->setModel(m_deviceModel);
0093 
0094     connect(m_deviceSel, &QListView::customContextMenuRequested, this, &KKameraConfig::slot_deviceMenu);
0095     connect(m_deviceSel, &QListView::doubleClicked, this, &KKameraConfig::slot_configureCamera);
0096     connect(m_deviceSel, &QListView::activated, this, &KKameraConfig::slot_deviceSelected);
0097     connect(m_deviceSel, &QListView::clicked, this, &KKameraConfig::slot_deviceSelected);
0098 
0099     m_deviceSel->setViewMode(QListView::IconMode);
0100     m_deviceSel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0101     m_deviceSel->setContextMenuPolicy(Qt::CustomContextMenu);
0102 
0103     // create actions, add to the toolbar
0104     QAction *act;
0105     act = m_actions->addAction(QStringLiteral("camera_add"));
0106     act->setIcon(QIcon::fromTheme(QStringLiteral("camera-photo")));
0107     act->setText(i18n("Add"));
0108     connect(act, &QAction::triggered, this, &KKameraConfig::slot_addCamera);
0109     act->setWhatsThis(i18n("Click this button to add a new camera."));
0110     m_toolbar->addAction(act);
0111     m_toolbar->addSeparator();
0112 
0113     act = m_actions->addAction(QStringLiteral("camera_test"));
0114     act->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok")));
0115     act->setText(i18n("Test"));
0116     connect(act, &QAction::triggered, this, &KKameraConfig::slot_testCamera);
0117     act->setWhatsThis(i18n("Click this button to test the connection to the selected camera."));
0118     m_toolbar->addAction(act);
0119 
0120     act = m_actions->addAction(QStringLiteral("camera_remove"));
0121     act->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
0122     act->setText(i18n("Remove"));
0123     connect(act, &QAction::triggered, this, &KKameraConfig::slot_removeCamera);
0124     act->setWhatsThis(i18n("Click this button to remove the selected camera from the list."));
0125     m_toolbar->addAction(act);
0126 
0127     act = m_actions->addAction(QStringLiteral("camera_configure"));
0128     act->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0129     act->setText(i18n("Configure..."));
0130     connect(act, &QAction::triggered, this, &KKameraConfig::slot_configureCamera);
0131     act->setWhatsThis(
0132         i18n("Click this button to change the configuration of the selected camera.<br><br>The availability of this feature and the contents of the "
0133              "Configuration dialog depend on the camera model."));
0134     m_toolbar->addAction(act);
0135 
0136     act = m_actions->addAction(QStringLiteral("camera_summary"));
0137     act->setIcon(QIcon::fromTheme(QStringLiteral("hwinfo")));
0138     act->setText(i18n("Information"));
0139     connect(act, &QAction::triggered, this, &KKameraConfig::slot_cameraSummary);
0140     act->setWhatsThis(
0141         i18n("Click this button to view a summary of the current status of the selected camera.<br><br>The availability of this feature and the contents of "
0142              "the Information dialog depend on the camera model."));
0143     m_toolbar->addAction(act);
0144     m_toolbar->addSeparator();
0145 
0146     act = m_actions->addAction(QStringLiteral("camera_cancel"));
0147     act->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
0148     act->setText(i18n("Cancel"));
0149     connect(act, &QAction::triggered, this, &KKameraConfig::slot_cancelOperation);
0150     act->setWhatsThis(i18n("Click this button to cancel the current camera operation."));
0151     act->setEnabled(false);
0152     m_toolbar->addAction(act);
0153 }
0154 
0155 void KKameraConfig::populateDeviceListView()
0156 {
0157     m_deviceModel->clear();
0158     CameraDevicesMap::ConstIterator itEnd = m_devices.constEnd();
0159     for (CameraDevicesMap::ConstIterator it = m_devices.constBegin(); it != itEnd; ++it) {
0160         if (it.value()) {
0161             auto deviceItem = new QStandardItem;
0162             deviceItem->setEditable(false);
0163             deviceItem->setText(it.key());
0164             deviceItem->setIcon(QIcon::fromTheme(QStringLiteral("camera-photo")));
0165             m_deviceModel->appendRow(deviceItem);
0166         }
0167     }
0168     slot_deviceSelected(m_deviceSel->currentIndex());
0169 }
0170 
0171 void KKameraConfig::save()
0172 {
0173     CameraDevicesMap::Iterator it;
0174 
0175     for (it = m_devices.begin(); it != m_devices.end(); it++) {
0176         it.value()->save(m_config);
0177     }
0178     m_config->sync();
0179 }
0180 
0181 void KKameraConfig::load()
0182 {
0183     QStringList groupList = m_config->groupList();
0184 
0185     QStringList::Iterator it;
0186     int i, count;
0187     CameraList *list;
0188     CameraAbilitiesList *al;
0189     GPPortInfoList *il;
0190     const char *model, *value;
0191     KCamera *kcamera;
0192 
0193     for (it = groupList.begin(); it != groupList.end(); it++) {
0194         if (*it != QStringLiteral("<default>")) {
0195             KConfigGroup cg(m_config, *it);
0196             if (cg.readEntry("Path").contains(QStringLiteral("usb:"))) {
0197                 continue;
0198             }
0199 
0200             // Load configuration for Serial port cameras
0201             qCDebug(KAMERA_KCONTROL) << "Loading configuration for serial port camera: " << *it;
0202             kcamera = new KCamera(*it, cg.readEntry("Path"));
0203             connect(kcamera, qOverload<const QString &>(&KCamera::error), this, qOverload<const QString &>(&KKameraConfig::slot_error));
0204 
0205             connect(kcamera,
0206                     qOverload<const QString &, const QString &>(&KCamera::error),
0207                     this,
0208                     qOverload<const QString &, const QString &>(&KKameraConfig::slot_error));
0209 
0210             kcamera->load(m_config);
0211             m_devices[*it] = kcamera;
0212         }
0213     }
0214     m_cancelPending = false;
0215 
0216     gp_list_new(&list);
0217 
0218     gp_abilities_list_new(&al);
0219     gp_abilities_list_load(al, m_context);
0220     gp_port_info_list_new(&il);
0221     gp_port_info_list_load(il);
0222     gp_abilities_list_detect(al, il, list, m_context);
0223     gp_abilities_list_free(al);
0224     gp_port_info_list_free(il);
0225 
0226     count = gp_list_count(list);
0227 
0228     QMap<QString, QString> ports, names;
0229 
0230     for (i = 0; i < count; i++) {
0231         gp_list_get_name(list, i, &model);
0232         gp_list_get_value(list, i, &value);
0233 
0234         ports[value] = model;
0235         if (!strcmp(value, "usb:")) {
0236             names[model] = value;
0237         }
0238     }
0239 
0240     if (ports.contains(QStringLiteral("usb:")) && names[ports[QStringLiteral("usb:")]] != QStringLiteral("usb:")) {
0241         ports.remove(QStringLiteral("usb:"));
0242     }
0243 
0244     QMap<QString, QString>::iterator portit;
0245 
0246     for (portit = ports.begin(); portit != ports.end(); portit++) {
0247         qCDebug(KAMERA_KCONTROL) << "Adding USB camera: " << portit.value() << " at " << portit.key();
0248 
0249         kcamera = new KCamera(portit.value(), portit.key());
0250 
0251         connect(kcamera, qOverload<const QString &>(&KCamera::error), this, qOverload<const QString &>(&KKameraConfig::slot_error));
0252 
0253         connect(kcamera,
0254                 qOverload<const QString &, const QString &>(&KCamera::error),
0255                 this,
0256                 qOverload<const QString &, const QString &>(&KKameraConfig::slot_error));
0257 
0258         m_devices[portit.value()] = kcamera;
0259     }
0260     populateDeviceListView();
0261 
0262     gp_list_free(list);
0263 }
0264 
0265 void KKameraConfig::beforeCameraOperation()
0266 {
0267     m_cancelPending = false;
0268 
0269     m_actions->action(QStringLiteral("camera_test"))->setEnabled(false);
0270     m_actions->action(QStringLiteral("camera_remove"))->setEnabled(false);
0271     m_actions->action(QStringLiteral("camera_configure"))->setEnabled(false);
0272     m_actions->action(QStringLiteral("camera_summary"))->setEnabled(false);
0273 
0274     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(true);
0275 }
0276 
0277 void KKameraConfig::afterCameraOperation()
0278 {
0279     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(false);
0280 
0281     // if we're regaining control after a Cancel...
0282     if (m_cancelPending) {
0283         qApp->restoreOverrideCursor();
0284         m_cancelPending = false;
0285     }
0286 
0287     // if any item was selected before the operation was run
0288     // it makes sense for the relevant toolbar buttons to be enabled
0289     slot_deviceSelected(m_deviceSel->currentIndex());
0290 }
0291 
0292 QString KKameraConfig::suggestName(const QString &name)
0293 {
0294     QString new_name = name;
0295     new_name.remove(QLatin1Char('/')); // we cannot have a slash in a URI's host
0296 
0297     if (!m_devices.contains(new_name))
0298         return new_name;
0299 
0300     // try new names with a number appended until we find a free one
0301     int i = 1;
0302     while (i++ < 0xffff) {
0303         new_name = name + QStringLiteral(" (") + QString::number(i) + QLatin1Char(')');
0304         if (!m_devices.contains(new_name))
0305             return new_name;
0306     }
0307 
0308     return {};
0309 }
0310 
0311 void KKameraConfig::slot_addCamera()
0312 {
0313     auto m_device = new KCamera(QString(), QString());
0314     connect(m_device, qOverload<const QString &>(&KCamera::error), this, qOverload<const QString &>(&KKameraConfig::slot_error));
0315 
0316     connect(m_device,
0317             qOverload<const QString &, const QString &>(&KCamera::error),
0318             this,
0319             qOverload<const QString &, const QString &>(&KKameraConfig::slot_error));
0320 
0321     KameraDeviceSelectDialog dialog(widget(), m_device);
0322     if (dialog.exec() == QDialog::Accepted) {
0323         dialog.save();
0324         m_device->setName(suggestName(m_device->model()));
0325         m_devices.insert(m_device->name(), m_device);
0326         populateDeviceListView();
0327         setNeedsSave(true);
0328     } else {
0329         delete m_device;
0330     }
0331 }
0332 
0333 void KKameraConfig::slot_removeCamera()
0334 {
0335     const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
0336     if (m_devices.contains(name)) {
0337         KCamera *m_device = m_devices.value(name);
0338         m_devices.remove(name);
0339         delete m_device;
0340         m_config->deleteGroup(name);
0341         populateDeviceListView();
0342         setNeedsSave(true);
0343     }
0344 }
0345 
0346 void KKameraConfig::slot_testCamera()
0347 {
0348     beforeCameraOperation();
0349 
0350     const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
0351     if (m_devices.contains(name)) {
0352         KCamera *m_device = m_devices.value(name);
0353         if (m_device->test()) {
0354             KMessageBox::information(widget(), i18n("Camera test was successful."));
0355         }
0356     }
0357 
0358     afterCameraOperation();
0359 }
0360 
0361 void KKameraConfig::slot_configureCamera()
0362 {
0363     const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
0364     if (m_devices.contains(name)) {
0365         KCamera *m_device = m_devices[name];
0366         m_device->configure();
0367     }
0368 }
0369 
0370 void KKameraConfig::slot_cameraSummary()
0371 {
0372     const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
0373     if (m_devices.contains(name)) {
0374         KCamera *m_device = m_devices[name];
0375         QString summary = m_device->summary();
0376         if (!summary.isNull()) {
0377             KMessageBox::information(widget(), summary);
0378         }
0379     }
0380 }
0381 
0382 void KKameraConfig::slot_cancelOperation()
0383 {
0384     m_cancelPending = true;
0385     // Prevent the user from keeping clicking Cancel
0386     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(false);
0387     // and indicate that the click on Cancel did have some effect
0388     qApp->setOverrideCursor(Qt::WaitCursor);
0389 }
0390 
0391 void KKameraConfig::slot_deviceMenu(const QPoint &point)
0392 {
0393     QModelIndex index = m_deviceSel->indexAt(point);
0394     if (index.isValid()) {
0395         m_devicePopup->clear();
0396         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_test")));
0397         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_remove")));
0398         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_configure")));
0399         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_summary")));
0400         m_devicePopup->exec(m_deviceSel->viewport()->mapToGlobal(point));
0401     }
0402 }
0403 
0404 void KKameraConfig::slot_deviceSelected(const QModelIndex &index)
0405 {
0406     bool isValid = index.isValid();
0407     m_actions->action(QStringLiteral("camera_test"))->setEnabled(isValid);
0408     m_actions->action(QStringLiteral("camera_remove"))->setEnabled(isValid);
0409     m_actions->action(QStringLiteral("camera_configure"))->setEnabled(isValid);
0410     m_actions->action(QStringLiteral("camera_summary"))->setEnabled(isValid);
0411 }
0412 
0413 void KKameraConfig::cbGPIdle(GPContext * /*context*/, void * /*data*/)
0414 {
0415     /*KKameraConfig *self( reinterpret_cast<KKameraConfig*>(data) );*/
0416     qApp->processEvents();
0417 }
0418 
0419 GPContextFeedback KKameraConfig::cbGPCancel(GPContext * /*context*/, void *data)
0420 {
0421     auto self(reinterpret_cast<KKameraConfig *>(data));
0422 
0423     // Since in practice no camera driver supports idle callbacks yet,
0424     // we'll use the cancel callback as opportunity to process events
0425     qApp->processEvents();
0426 
0427     // If a cancel request is pending, ask gphoto to cancel
0428     if (self->m_cancelPending) {
0429         return GP_CONTEXT_FEEDBACK_CANCEL;
0430     } else {
0431         return GP_CONTEXT_FEEDBACK_OK;
0432     }
0433 }
0434 
0435 void KKameraConfig::slot_error(const QString &message)
0436 {
0437     KMessageBox::error(widget(), message);
0438 }
0439 
0440 void KKameraConfig::slot_error(const QString &message, const QString &details)
0441 {
0442     KMessageBox::detailedError(widget(), message, details);
0443 }
0444 
0445 #include "kamera.moc"
0446 
0447 #include "moc_kamera.cpp"