File indexing completed on 2022-09-27 13:41:20

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