File indexing completed on 2024-04-28 08:09:24

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 "kameradevice.h"
0011 
0012 #include <QComboBox>
0013 #include <QGroupBox>
0014 #include <QHBoxLayout>
0015 #include <QLabel>
0016 #include <QListView>
0017 #include <QPushButton>
0018 #include <QRadioButton>
0019 #include <QStackedWidget>
0020 #include <QStandardItemModel>
0021 #include <QVBoxLayout>
0022 
0023 #include <KConfig>
0024 #include <KConfigGroup>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 
0028 extern "C" {
0029 #include <gphoto2.h>
0030 }
0031 
0032 #include "kameraconfigdialog.h"
0033 
0034 // Define some parts of the old API
0035 #define GP_PROMPT_OK 0
0036 #define GP_PROMPT_CANCEL -1
0037 
0038 static const int INDEX_NONE = 0;
0039 static const int INDEX_SERIAL = 1;
0040 static const int INDEX_USB = 2;
0041 static GPContext *glob_context = nullptr;
0042 
0043 #ifdef DEBUG
0044 static void gp_errordumper(GPLogLevel level, const char *domain, const char *str, void *data)
0045 {
0046     qCDebug(KAMERA_KCONTROL) << "GP_LOG: " << str;
0047 }
0048 
0049 // Use with
0050 // gp_log_add_func(GP_LOG_LEVEL, gp_errordumper, NULL);
0051 // where LEVEL = { DATA, DEBUG, ERROR, VERBOSE, ALL }
0052 #endif
0053 
0054 KCamera::KCamera(const QString &name, const QString &path)
0055 {
0056     m_name = name;
0057     m_model = name;
0058     m_path = path;
0059     m_camera = nullptr;
0060     m_abilitylist = nullptr;
0061 }
0062 
0063 KCamera::~KCamera()
0064 {
0065     if (m_camera)
0066         gp_camera_free(m_camera);
0067     if (m_abilitylist)
0068         gp_abilities_list_free(m_abilitylist);
0069 }
0070 
0071 bool KCamera::initInformation()
0072 {
0073     if (m_model.isNull()) {
0074         return false;
0075     }
0076 
0077     if (gp_abilities_list_new(&m_abilitylist) != GP_OK) {
0078         Q_EMIT error(i18n("Could not allocate memory for the abilities list."));
0079         return false;
0080     }
0081     if (gp_abilities_list_load(m_abilitylist, glob_context) != GP_OK) {
0082         Q_EMIT error(i18n("Could not load ability list."));
0083         return false;
0084     }
0085     int index = gp_abilities_list_lookup_model(m_abilitylist, m_model.toLocal8Bit().data());
0086     if (index < 0) {
0087         Q_EMIT error(
0088             i18n("Description of abilities for camera %1 is not available."
0089                  " Configuration options may be incorrect.",
0090                  m_model));
0091         return false;
0092     }
0093     gp_abilities_list_get_abilities(m_abilitylist, index, &m_abilities);
0094     return true;
0095 }
0096 
0097 bool KCamera::initCamera()
0098 {
0099     if (m_camera) {
0100         return m_camera;
0101     } else {
0102         int result;
0103 
0104         initInformation();
0105 
0106         if (m_model.isNull() || m_path.isNull()) {
0107             return false;
0108         }
0109 
0110         result = gp_camera_new(&m_camera);
0111         if (result != GP_OK) {
0112             // m_camera is not initialized, so we cannot get result as string
0113             Q_EMIT error(i18n("Could not access driver. Check your gPhoto2 installation."));
0114             return false;
0115         }
0116 
0117         // set the camera's model
0118         GPPortInfo info;
0119         GPPortInfoList *il;
0120         gp_port_info_list_new(&il);
0121         gp_port_info_list_load(il);
0122         gp_port_info_list_get_info(il, gp_port_info_list_lookup_path(il, m_path.toLocal8Bit().data()), &info);
0123         gp_camera_set_abilities(m_camera, m_abilities);
0124         gp_camera_set_port_info(m_camera, info);
0125         gp_port_info_list_free(il);
0126 
0127         // this might take some time (esp. for non-existent camera) - better be done asynchronously
0128         result = gp_camera_init(m_camera, glob_context);
0129         if (result != GP_OK) {
0130             gp_camera_free(m_camera);
0131             m_camera = nullptr;
0132             Q_EMIT error(i18n("Unable to initialize camera. Check your port settings and camera connectivity and try again."),
0133                          QString::fromLocal8Bit(gp_result_as_string(result)));
0134             return false;
0135         }
0136 
0137         return m_camera;
0138     }
0139 }
0140 
0141 Camera *KCamera::camera()
0142 {
0143     initCamera();
0144     return m_camera;
0145 }
0146 
0147 QString KCamera::summary()
0148 {
0149     int result;
0150     CameraText summary;
0151 
0152     initCamera();
0153 
0154     result = gp_camera_get_summary(m_camera, &summary, glob_context);
0155     if (result != GP_OK) {
0156         return i18n("No camera summary information is available.\n");
0157     }
0158     return QString::fromLocal8Bit(summary.text);
0159 }
0160 
0161 bool KCamera::configure()
0162 {
0163     CameraWidget *window;
0164     int result;
0165 
0166     initCamera();
0167 
0168     result = gp_camera_get_config(m_camera, &window, glob_context);
0169     if (result != GP_OK) {
0170         Q_EMIT error(i18n("Camera configuration failed."), QString::fromLocal8Bit(gp_result_as_string(result)));
0171         return false;
0172     }
0173 
0174     KameraConfigDialog kcd(m_camera, window);
0175     result = kcd.exec() ? GP_PROMPT_OK : GP_PROMPT_CANCEL;
0176 
0177     if (result == GP_PROMPT_OK) {
0178         result = gp_camera_set_config(m_camera, window, glob_context);
0179         if (result != GP_OK) {
0180             Q_EMIT error(i18n("Camera configuration failed."), QString::fromLocal8Bit(gp_result_as_string(result)));
0181             return false;
0182         }
0183     }
0184 
0185     return true;
0186 }
0187 
0188 bool KCamera::test()
0189 {
0190     // TODO: Make testing non-blocking (maybe via KIO?)
0191     // Currently, a failed serial test times out at about 30 sec.
0192     return camera() != nullptr;
0193 }
0194 
0195 void KCamera::load(KConfig *config)
0196 {
0197     KConfigGroup group = config->group(m_name);
0198     if (m_model.isNull()) {
0199         m_model = group.readEntry("Model");
0200     }
0201     if (m_path.isNull()) {
0202         m_path = group.readEntry("Path");
0203     }
0204     invalidateCamera();
0205 }
0206 
0207 void KCamera::save(KConfig *config)
0208 {
0209     KConfigGroup group = config->group(m_name);
0210     group.writeEntry("Model", m_model);
0211     group.writeEntry("Path", m_path);
0212 }
0213 
0214 QString KCamera::portName()
0215 {
0216     const QString port = m_path.left(m_path.indexOf(QLatin1Char(':'))).toLower();
0217     if (port == QStringLiteral("serial"))
0218         return i18n("Serial");
0219     if (port == QStringLiteral("usb"))
0220         return i18n("USB");
0221     return i18n("Unknown port");
0222 }
0223 
0224 void KCamera::setName(const QString &name)
0225 {
0226     m_name = name;
0227 }
0228 
0229 void KCamera::setModel(const QString &model)
0230 {
0231     m_model = model;
0232     invalidateCamera();
0233     initInformation();
0234 }
0235 
0236 void KCamera::setPath(const QString &path)
0237 {
0238     m_path = path;
0239     invalidateCamera();
0240 }
0241 
0242 void KCamera::invalidateCamera()
0243 {
0244     if (m_camera) {
0245         gp_camera_free(m_camera);
0246         m_camera = nullptr;
0247     }
0248 }
0249 
0250 bool KCamera::isTestable() const
0251 {
0252     return true;
0253 }
0254 
0255 bool KCamera::isConfigurable()
0256 {
0257     initInformation();
0258     return m_abilities.operations & GP_OPERATION_CONFIG;
0259 }
0260 
0261 QStringList KCamera::supportedPorts()
0262 {
0263     initInformation();
0264     QStringList ports;
0265     if (m_abilities.port & GP_PORT_SERIAL) {
0266         ports.append(QStringLiteral("serial"));
0267     }
0268     if (m_abilities.port & GP_PORT_USB) {
0269         ports.append(QStringLiteral("usb"));
0270     }
0271     return ports;
0272 }
0273 
0274 CameraAbilities KCamera::abilities() const
0275 {
0276     return m_abilities;
0277 }
0278 
0279 // ---------- KameraSelectCamera ------------
0280 
0281 KameraDeviceSelectDialog::KameraDeviceSelectDialog(QWidget *parent, KCamera *device)
0282     : QDialog(parent)
0283 {
0284     setWindowTitle(i18n("Select Camera Device"));
0285 
0286     setModal(true);
0287     m_device = device;
0288     connect(m_device, qOverload<const QString &>(&KCamera::error), this, qOverload<const QString &>(&KameraDeviceSelectDialog::slot_error));
0289 
0290     connect(m_device,
0291             qOverload<const QString &, const QString &>(&KCamera::error),
0292             this,
0293             qOverload<const QString &, const QString &>(&KameraDeviceSelectDialog::slot_error));
0294 
0295     // a layout with horizontal boxes - this gives the two columns
0296     auto topLayout = new QHBoxLayout(this);
0297 
0298     // the models list
0299     m_modelSel = new QListView(this);
0300     m_model = new QStandardItemModel(this);
0301     m_model->setColumnCount(1);
0302     m_model->setHeaderData(0, Qt::Horizontal, i18nc("@title:column", "Supported Cameras"));
0303     m_modelSel->setModel(m_model);
0304 
0305     topLayout->addWidget(m_modelSel);
0306     connect(m_modelSel, &QListView::activated, this, &KameraDeviceSelectDialog::slot_setModel);
0307     connect(m_modelSel, &QListView::clicked, this, &KameraDeviceSelectDialog::slot_setModel);
0308 
0309     // make sure listview only as wide as it needs to be
0310     m_modelSel->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred));
0311 
0312     auto rightLayout = new QVBoxLayout();
0313     rightLayout->setContentsMargins(0, 0, 0, 0);
0314     topLayout->addLayout(rightLayout);
0315 
0316     m_portSelectGroup = new QGroupBox(i18n("Port"), this);
0317     auto vertLayout = new QVBoxLayout;
0318     m_portSelectGroup->setLayout(vertLayout);
0319     m_portSelectGroup->setMinimumSize(100, 120);
0320     rightLayout->addWidget(m_portSelectGroup);
0321     // Create port type selection radiobuttons.
0322     m_serialRB = new QRadioButton(i18n("Serial"));
0323     vertLayout->addWidget(m_serialRB);
0324     m_serialRB->setWhatsThis(
0325         i18n("If this option is checked, the camera has "
0326              "to be connected to one of the computer's serial ports (known as COM "
0327              "ports in Microsoft Windows.)"));
0328     m_USBRB = new QRadioButton(i18n("USB"));
0329     vertLayout->addWidget(m_USBRB);
0330     m_USBRB->setWhatsThis(
0331         i18n("If this option is checked, the camera has to "
0332              "be connected to one of the computer's USB ports, or to a USB hub."));
0333 
0334     m_portSettingsGroup = new QGroupBox(i18n("Port Settings"), this);
0335     auto lay = new QVBoxLayout;
0336     m_portSettingsGroup->setLayout(lay);
0337     rightLayout->addWidget(m_portSettingsGroup);
0338     // Create port settings widget stack
0339     m_settingsStack = new QStackedWidget;
0340     auto grid2 = new QWidget(m_settingsStack);
0341     auto gridLayout2 = new QGridLayout(grid2);
0342     grid2->setLayout(gridLayout2);
0343     auto label2 = new QLabel(i18n("Port"), grid2);
0344     gridLayout2->addWidget(label2, 0, 0, Qt::AlignLeft);
0345 
0346     lay->addWidget(grid2);
0347     lay->addWidget(m_settingsStack);
0348     connect(m_serialRB, &QRadioButton::toggled, this, &KameraDeviceSelectDialog::changeCurrentIndex);
0349     connect(m_USBRB, &QRadioButton::toggled, this, &KameraDeviceSelectDialog::changeCurrentIndex);
0350 
0351     // none tab
0352     m_settingsStack->insertWidget(INDEX_NONE, new QLabel(i18n("No port type selected."), m_settingsStack));
0353 
0354     // serial tab
0355     auto grid = new QWidget(m_settingsStack);
0356     auto gridLayout = new QGridLayout(grid);
0357     grid->setLayout(gridLayout);
0358 
0359     auto label = new QLabel(i18n("Port:"), grid);
0360     m_serialPortCombo = new QComboBox(grid);
0361     m_serialPortCombo->setEditable(true);
0362     m_serialPortCombo->setWhatsThis(
0363         i18n("Specify here the serial port to "
0364              "which you connect the camera."));
0365 
0366     gridLayout->addWidget(label, 1, 0, Qt::AlignLeft);
0367     gridLayout->addWidget(m_serialPortCombo, 1, 1, Qt::AlignRight);
0368     m_settingsStack->insertWidget(INDEX_SERIAL, grid);
0369 
0370     m_settingsStack->insertWidget(INDEX_USB, new QLabel(i18n("No further configuration is required for USB cameras."), m_settingsStack));
0371 
0372     // Add the ok/cancel buttons to the bottom of the right side
0373     m_OkCancelButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0374     QPushButton *okButton = m_OkCancelButtonBox->button(QDialogButtonBox::Ok);
0375     QPushButton *cancelButton = m_OkCancelButtonBox->button(QDialogButtonBox::Cancel);
0376     okButton->setDefault(true);
0377     // Set false enabled to allow the use of an equivalent
0378     // to enableButtonOk(true) in slot_setModel.
0379     okButton->setEnabled(false);
0380     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0381     connect(okButton, &QPushButton::clicked, this, &KameraDeviceSelectDialog::accept);
0382     connect(cancelButton, &QPushButton::clicked, this, &KameraDeviceSelectDialog::close);
0383     // add a spacer
0384     rightLayout->addStretch();
0385 
0386     rightLayout->addWidget(m_OkCancelButtonBox);
0387 
0388     // query gphoto2 for existing serial ports
0389     GPPortInfoList *list;
0390     GPPortInfo info;
0391     int gphoto_ports = 0;
0392     gp_port_info_list_new(&list);
0393     if (gp_port_info_list_load(list) >= 0) {
0394         gphoto_ports = gp_port_info_list_count(list);
0395     }
0396     for (int i = 0; i < gphoto_ports; i++) {
0397         if (gp_port_info_list_get_info(list, i, &info) >= 0) {
0398             char *xpath;
0399             gp_port_info_get_path(info, &xpath);
0400             if (strncmp(xpath, "serial:", 7) == 0) {
0401                 m_serialPortCombo->addItem(QString::fromLocal8Bit(xpath).mid(7));
0402             }
0403         }
0404     }
0405     gp_port_info_list_free(list);
0406 
0407     populateCameraListView();
0408     load();
0409 
0410     m_portSelectGroup->setEnabled(false);
0411     m_portSettingsGroup->setEnabled(false);
0412 }
0413 
0414 void KameraDeviceSelectDialog::changeCurrentIndex()
0415 {
0416     auto send = dynamic_cast<QRadioButton *>(sender());
0417     if (send) {
0418         if (send == m_serialRB) {
0419             m_settingsStack->setCurrentIndex(INDEX_SERIAL);
0420         } else if (send == m_USBRB) {
0421             m_settingsStack->setCurrentIndex(INDEX_USB);
0422         }
0423     }
0424 }
0425 
0426 bool KameraDeviceSelectDialog::populateCameraListView()
0427 {
0428     gp_abilities_list_new(&m_device->m_abilitylist);
0429     gp_abilities_list_load(m_device->m_abilitylist, glob_context);
0430     int numCams = gp_abilities_list_count(m_device->m_abilitylist);
0431     CameraAbilities a;
0432 
0433     if (numCams < 0) {
0434         // XXX libgphoto2 failed to get te camera list
0435         return false;
0436     } else {
0437         for (int x = 0; x < numCams; ++x) {
0438             if (gp_abilities_list_get_abilities(m_device->m_abilitylist, x, &a) == GP_OK) {
0439                 auto cameraItem = new QStandardItem;
0440                 cameraItem->setEditable(false);
0441                 cameraItem->setText(a.model);
0442                 m_model->appendRow(cameraItem);
0443             }
0444         }
0445         return true;
0446     }
0447 }
0448 
0449 void KameraDeviceSelectDialog::save()
0450 {
0451     m_device->setModel(m_modelSel->currentIndex().data(Qt::DisplayRole).toString());
0452 
0453     if (m_serialRB->isChecked()) {
0454         m_device->setPath(QStringLiteral("serial:") + m_serialPortCombo->currentText());
0455     } else if (m_USBRB->isChecked()) {
0456         m_device->setPath(QStringLiteral("usb:"));
0457     }
0458 }
0459 
0460 void KameraDeviceSelectDialog::load()
0461 {
0462     QString path = m_device->path();
0463     QString port = path.left(path.indexOf(QLatin1Char(':'))).toLower();
0464 
0465     if (port == QLatin1String("serial")) {
0466         setPortType(INDEX_SERIAL);
0467     } else if (port == QLatin1String("usb")) {
0468         setPortType(INDEX_USB);
0469     }
0470 
0471     const QList<QStandardItem *> items = m_model->findItems(m_device->model());
0472     for (QStandardItem *item : items) {
0473         const QModelIndex index = m_model->indexFromItem(item);
0474         m_modelSel->selectionModel()->select(index, QItemSelectionModel::Select);
0475     }
0476 }
0477 
0478 void KameraDeviceSelectDialog::slot_setModel(const QModelIndex &modelIndex)
0479 {
0480     m_portSelectGroup->setEnabled(true);
0481     m_portSettingsGroup->setEnabled(true);
0482 
0483     QString model = modelIndex.data(Qt::DisplayRole).toString();
0484 
0485     CameraAbilities abilities;
0486     int index = gp_abilities_list_lookup_model(m_device->m_abilitylist, model.toLocal8Bit().data());
0487     if (index < 0) {
0488         slot_error(
0489             i18n("Description of abilities for camera %1 is not available."
0490                  " Configuration options may be incorrect.",
0491                  model));
0492     }
0493     int result = gp_abilities_list_get_abilities(m_device->m_abilitylist, index, &abilities);
0494     if (result == GP_OK) {
0495         // enable radiobuttons for supported port types
0496         m_serialRB->setEnabled(abilities.port & GP_PORT_SERIAL);
0497         m_USBRB->setEnabled(abilities.port & GP_PORT_USB);
0498         // if there's only one available port type, make sure it's selected
0499         if (abilities.port == GP_PORT_SERIAL) {
0500             setPortType(INDEX_SERIAL);
0501         }
0502         if (abilities.port == GP_PORT_USB) {
0503             setPortType(INDEX_USB);
0504         }
0505     } else {
0506         slot_error(
0507             i18n("Description of abilities for camera %1 is not available."
0508                  " Configuration options may be incorrect.",
0509                  model));
0510     }
0511     QPushButton *okButton = m_OkCancelButtonBox->button(QDialogButtonBox::Ok);
0512     okButton->setEnabled(true);
0513 }
0514 
0515 void KameraDeviceSelectDialog::setPortType(int type)
0516 {
0517     // Enable the correct button
0518     if (type == INDEX_USB) {
0519         m_USBRB->setChecked(true);
0520     } else if (type == INDEX_SERIAL) {
0521         m_serialRB->setChecked(true);
0522     }
0523 
0524     // Bring the right tab to the front
0525     m_settingsStack->setCurrentIndex(type);
0526 }
0527 
0528 void KameraDeviceSelectDialog::slot_error(const QString &message)
0529 {
0530     KMessageBox::error(this, message);
0531 }
0532 
0533 void KameraDeviceSelectDialog::slot_error(const QString &message, const QString &details)
0534 {
0535     KMessageBox::detailedError(this, message, details);
0536 }
0537 
0538 #include "moc_kameradevice.cpp"