1 /*
2 
3     Copyright (C) 2001 The Kompany
4 		  2002-2003	Ilya Konstantinov <kde-devel@future.shiny.co.il>
5 		  2002-2003	Marcus Meissner <marcus@jet.franken.de>
6 		  2003		Nadeem Hasan <nhasan@nadmm.com>
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 
22 */
23 
24 #include "kamera.h"
25 
26 #include <QLabel>
27 #include <QListView>
28 #include <QVBoxLayout>
29 #include <QApplication>
30 #include <QStandardItemModel>
31 #include <QMenu>
32 #include <QIcon>
33 
34 #include <KConfig>
35 #include <KMessageBox>
36 #include <KLocalizedString>
37 #include <KToolBar>
38 #include <KProtocolInfo>
39 #include <KActionCollection>
40 #include <KConfigGroup>
41 #include "kameraconfigdialog.h"
42 #include "kameradevice.h"
43 
K_PLUGIN_FACTORY(KKameraConfigFactory,registerPlugin<KKameraConfig> ();)44 K_PLUGIN_FACTORY(KKameraConfigFactory, registerPlugin<KKameraConfig>();)
45 
46 Q_LOGGING_CATEGORY(KAMERA_KCONTROL, "kamera.kcontrol")
47 
48 // --------------- Camera control center module widget ---
49 
50 KKameraConfig::KKameraConfig(QWidget *parent, const QVariantList &)
51     : KCModule(parent)
52 {
53 #ifdef DEBUG_KAMERA_KCONTROL
54     QLoggingCategory::setFilterRules(QStringLiteral("kamera.kcontrol.debug = true"));
55 #endif
56     m_devicePopup = new QMenu(this);
57 	m_actions = new KActionCollection(this);
58     m_config = new KConfig(KProtocolInfo::config(QStringLiteral("camera")), KConfig::SimpleConfig);
59     m_deviceModel = new QStandardItemModel(this);
60 
61 	m_context = gp_context_new();
62 	if (m_context) {
63 		// Register the callback functions
64 		gp_context_set_cancel_func(m_context, cbGPCancel, this);
65 		gp_context_set_idle_func(m_context, cbGPIdle, this);
66 
67 		displayGPSuccessDialogue();
68 	} else {
69 		displayGPFailureDialogue();
70 	}
71 }
72 
~KKameraConfig()73 KKameraConfig::~KKameraConfig()
74 {
75     delete m_config;
76 }
77 
defaults()78 void KKameraConfig::defaults()
79 {
80 }
81 
displayGPFailureDialogue()82 void KKameraConfig::displayGPFailureDialogue()
83 {
84 	QVBoxLayout *topLayout = new QVBoxLayout(this);
85 	topLayout->setSpacing(0);
86 	topLayout->setContentsMargins(0, 0, 0, 0);
87 	QLabel *label = new QLabel(i18n("Unable to initialize the gPhoto2 libraries."), this);
88 	topLayout->addWidget(label);
89 }
90 
displayGPSuccessDialogue()91 void KKameraConfig::displayGPSuccessDialogue()
92 {
93 	// set the kcontrol module buttons
94 	setButtons(Help | Apply );
95 
96 	// create a layout with two vertical boxes
97 	QVBoxLayout *topLayout = new QVBoxLayout(this);
98 	topLayout->setSpacing(0);
99 	topLayout->setContentsMargins(0, 0, 0, 0);
100 
101 	m_toolbar = new KToolBar(this, "ToolBar");
102 	topLayout->addWidget(m_toolbar);
103 	m_toolbar->setMovable(false);
104 
105     // create list of devices - this is the large white box
106 	m_deviceSel = new QListView(this);
107 	topLayout->addWidget(m_deviceSel);
108 
109 	m_deviceSel->setModel(m_deviceModel);
110 
111 	connect(m_deviceSel, &QListView::customContextMenuRequested,
112 		this, &KKameraConfig::slot_deviceMenu);
113 	connect(m_deviceSel, &QListView::doubleClicked,
114 		this, &KKameraConfig::slot_configureCamera);
115 	connect(m_deviceSel, &QListView::activated,
116 		this, &KKameraConfig::slot_deviceSelected);
117 	connect(m_deviceSel, &QListView::clicked,
118 		this, &KKameraConfig::slot_deviceSelected);
119 
120 	m_deviceSel->setViewMode(QListView::IconMode);
121 	m_deviceSel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
122 	m_deviceSel->setContextMenuPolicy(Qt::CustomContextMenu);
123 
124     // create actions, add to the toolbar
125 	QAction *act;
126     act = m_actions->addAction(QStringLiteral("camera_add"));
127     act->setIcon(QIcon::fromTheme(QStringLiteral("camera-photo")));
128 	act->setText(i18n("Add"));
129 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_addCamera);
130 	act->setWhatsThis(i18n("Click this button to add a new camera."));
131 	m_toolbar->addAction(act);
132 	m_toolbar->addSeparator();
133 
134     act = m_actions->addAction(QStringLiteral("camera_test"));
135     act->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok")));
136 	act->setText(i18n("Test"));
137 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_testCamera);
138 	act->setWhatsThis(i18n("Click this button to test the connection to the selected camera."));
139 	m_toolbar->addAction(act);
140 
141     act = m_actions->addAction(QStringLiteral("camera_remove"));
142     act->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
143 	act->setText(i18n("Remove"));
144 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_removeCamera);
145 	act->setWhatsThis(i18n("Click this button to remove the selected camera from the list."));
146 	m_toolbar->addAction(act);
147 
148     act = m_actions->addAction(QStringLiteral("camera_configure"));
149     act->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
150 	act->setText(i18n("Configure..."));
151 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_configureCamera);
152 	act->setWhatsThis(i18n("Click this button to change the configuration of the selected camera.<br><br>The availability of this feature and the contents of the Configuration dialog depend on the camera model."));
153 	m_toolbar->addAction(act);
154 
155     act = m_actions->addAction(QStringLiteral("camera_summary"));
156     act->setIcon(QIcon::fromTheme(QStringLiteral("hwinfo")));
157 	act->setText(i18n("Information"));
158 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_cameraSummary);
159 	act->setWhatsThis(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 the Information dialog depend on the camera model."));
160 	m_toolbar->addAction(act);
161 	m_toolbar->addSeparator();
162 
163     act = m_actions->addAction(QStringLiteral("camera_cancel"));
164     act->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
165 	act->setText(i18n("Cancel"));
166 	connect(act, &QAction::triggered, this, &KKameraConfig::slot_cancelOperation);
167 	act->setWhatsThis(i18n("Click this button to cancel the current camera operation."));
168 	act->setEnabled(false);
169 	m_toolbar->addAction(act);
170 }
171 
populateDeviceListView()172 void KKameraConfig::populateDeviceListView()
173 {
174 	m_deviceModel->clear();
175     CameraDevicesMap::ConstIterator itEnd = m_devices.constEnd();
176     for (CameraDevicesMap::ConstIterator it = m_devices.constBegin(); it != itEnd; ++it) {
177 		if (it.value()) {
178 			QStandardItem *deviceItem = new QStandardItem;
179 			deviceItem->setEditable(false);
180 			deviceItem->setText(it.key());
181             deviceItem->setIcon(QIcon::fromTheme(QStringLiteral("camera-photo")));
182 			m_deviceModel->appendRow(deviceItem);
183 		}
184 	}
185 	slot_deviceSelected(m_deviceSel->currentIndex());
186 }
187 
save(void)188 void KKameraConfig::save(void)
189 {
190 	CameraDevicesMap::Iterator it;
191 
192 	for (it = m_devices.begin(); it != m_devices.end(); it++)
193 	{
194 		it.value()->save(m_config);
195 	}
196 	m_config->sync();
197 }
198 
load(void)199 void KKameraConfig::load(void)
200 {
201 	QStringList groupList = m_config->groupList();
202 
203 	QStringList::Iterator it;
204 	int i, count;
205 	CameraList *list;
206 	CameraAbilitiesList *al;
207 	GPPortInfoList *il;
208 	const char *model, *value;
209 	KCamera *kcamera;
210 
211 	for (it = groupList.begin(); it != groupList.end(); it++) {
212         if (*it != QStringLiteral("<default>"))	{
213 			KConfigGroup cg(m_config, *it);
214             if (cg.readEntry("Path").contains(QStringLiteral("usb:"))) {
215 				continue;
216 			}
217 
218 			// Load configuration for Serial port cameras
219 			qCDebug(KAMERA_KCONTROL) << "Loading configuration for serial port camera: "
220                                      << *it;
221 			kcamera = new KCamera(*it, cg.readEntry("Path"));
222             connect(kcamera, qOverload<const QString&>(&KCamera::error),
223                 this, qOverload<const QString&>(&KKameraConfig::slot_error));
224 
225             connect(kcamera, qOverload<const QString&, const QString&>(&KCamera::error),
226                 this, qOverload<const QString&, const QString&>(&KKameraConfig::slot_error));
227 
228 			kcamera->load(m_config);
229 			m_devices[*it] = kcamera;
230 		}
231 	}
232 	m_cancelPending = false;
233 
234 	gp_list_new (&list);
235 
236 	gp_abilities_list_new (&al);
237 	gp_abilities_list_load (al, m_context);
238 	gp_port_info_list_new (&il);
239 	gp_port_info_list_load (il);
240 	gp_abilities_list_detect (al, il, list, m_context);
241 	gp_abilities_list_free (al);
242 	gp_port_info_list_free (il);
243 
244 	count = gp_list_count (list);
245 
246 	QMap<QString,QString>	ports, names;
247 
248 	for (i = 0 ; i<count ; i++) {
249 		gp_list_get_name  (list, i, &model);
250 		gp_list_get_value (list, i, &value);
251 
252 		ports[value] = model;
253 		if (!strcmp(value,"usb:")) {
254 			names[model] = value;
255 		}
256 	}
257 
258     if (ports.contains(QStringLiteral("usb:")) && names[ports[QStringLiteral("usb:")]]!=QStringLiteral("usb:")) {
259         ports.remove(QStringLiteral("usb:"));
260 	}
261 
262 	QMap<QString,QString>::iterator portit;
263 
264 	for (portit = ports.begin() ; portit != ports.end(); portit++) {
265 		qCDebug(KAMERA_KCONTROL) << "Adding USB camera: " << portit.value() << " at " << portit.key();
266 
267 		kcamera = new KCamera(portit.value(), portit.key());
268 
269         connect(kcamera, qOverload<const QString&>(&KCamera::error),
270             this, qOverload<const QString&>(&KKameraConfig::slot_error));
271 
272         connect(kcamera, qOverload<const QString&, const QString&>(&KCamera::error),
273             this, qOverload<const QString&, const QString&>(&KKameraConfig::slot_error));
274 
275 		m_devices[portit.value()] = kcamera;
276 	}
277 	populateDeviceListView();
278 
279 	gp_list_free (list);
280 }
281 
beforeCameraOperation()282 void KKameraConfig::beforeCameraOperation()
283 {
284 	m_cancelPending = false;
285 
286     m_actions->action(QStringLiteral("camera_test"))->setEnabled(false);
287     m_actions->action(QStringLiteral("camera_remove"))->setEnabled(false);
288     m_actions->action(QStringLiteral("camera_configure"))->setEnabled(false);
289     m_actions->action(QStringLiteral("camera_summary"))->setEnabled(false);
290 
291     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(true);
292 }
293 
afterCameraOperation()294 void KKameraConfig::afterCameraOperation()
295 {
296     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(false);
297 
298 	// if we're regaining control after a Cancel...
299 	if (m_cancelPending) {
300 		qApp->restoreOverrideCursor();
301 		m_cancelPending = false;
302 	}
303 
304 	// if any item was selected before the operation was run
305 	// it makes sense for the relevant toolbar buttons to be enabled
306 	slot_deviceSelected(m_deviceSel->currentIndex());
307 }
308 
suggestName(const QString & name)309 QString KKameraConfig::suggestName(const QString &name)
310 {
311     QString new_name = name;
312     new_name.remove(QLatin1Char('/')); // we cannot have a slash in a URI's host
313 
314 	if (!m_devices.contains(new_name)) return new_name;
315 
316 	// try new names with a number appended until we find a free one
317 	int i = 1;
318     while (i++ < 0xffff) {
319         new_name = name + QStringLiteral(" (") + QString::number(i) + QLatin1Char(')');
320 		if (!m_devices.contains(new_name)) return new_name;
321 	}
322 
323 	return QString();
324 }
325 
slot_addCamera()326 void KKameraConfig::slot_addCamera()
327 {
328         KCamera *m_device = new KCamera(QString(), QString());
329     connect(m_device, qOverload<const QString&>(&KCamera::error),
330                 this, qOverload<const QString&>(&KKameraConfig::slot_error));
331 
332         connect(m_device, qOverload<const QString&, const QString&>(&KCamera::error),
333                 this, qOverload<const QString&,  const QString&>(&KKameraConfig::slot_error));
334 
335 	KameraDeviceSelectDialog dialog(this, m_device);
336 	if (dialog.exec() == QDialog::Accepted) {
337 		dialog.save();
338 		m_device->setName(suggestName(m_device->model()));
339 		m_devices.insert(m_device->name(), m_device);
340 		populateDeviceListView();
341         Q_EMIT changed(true);
342 	} else {
343 		delete m_device;
344 	}
345 }
346 
slot_removeCamera()347 void KKameraConfig::slot_removeCamera()
348 {
349 	const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
350 	if (m_devices.contains(name)) {
351 		KCamera *m_device = m_devices.value(name);
352 		m_devices.remove(name);
353 		delete m_device;
354 		m_config->deleteGroup(name);
355 		populateDeviceListView();
356         Q_EMIT changed(true);
357 	}
358 }
359 
slot_testCamera()360 void KKameraConfig::slot_testCamera()
361 {
362 	beforeCameraOperation();
363 
364 	const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
365 	if (m_devices.contains(name)) {
366 		KCamera *m_device = m_devices.value(name);
367         if (m_device->test()) {
368 			KMessageBox::information(this, i18n("Camera test was successful."));
369         }
370 	}
371 
372 	afterCameraOperation();
373 }
374 
slot_configureCamera()375 void KKameraConfig::slot_configureCamera()
376 {
377 	const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
378 	if (m_devices.contains(name)) {
379 		KCamera *m_device = m_devices[name];
380 		m_device->configure();
381 	}
382 }
383 
slot_cameraSummary()384 void KKameraConfig::slot_cameraSummary()
385 {
386 	const QString name = m_deviceSel->currentIndex().data(Qt::DisplayRole).toString();
387 	if (m_devices.contains(name)) {
388 		KCamera *m_device = m_devices[name];
389 		QString summary = m_device->summary();
390 		if (!summary.isNull()) {
391 			KMessageBox::information(this, summary);
392 		}
393 	}
394 }
395 
slot_cancelOperation()396 void KKameraConfig::slot_cancelOperation()
397 {
398 	m_cancelPending = true;
399 	// Prevent the user from keeping clicking Cancel
400     m_actions->action(QStringLiteral("camera_cancel"))->setEnabled(false);
401 	// and indicate that the click on Cancel did have some effect
402 	qApp->setOverrideCursor(Qt::WaitCursor);
403 }
404 
slot_deviceMenu(const QPoint & point)405 void KKameraConfig::slot_deviceMenu(const QPoint &point)
406 {
407 	QModelIndex index = m_deviceSel->indexAt(point);
408 	if (index.isValid()) {
409 		m_devicePopup->clear();
410         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_test")));
411         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_remove")));
412         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_configure")));
413         m_devicePopup->addAction(m_actions->action(QStringLiteral("camera_summary")));
414 		m_devicePopup->exec(m_deviceSel->viewport()->mapToGlobal(point));
415 	}
416 }
417 
slot_deviceSelected(const QModelIndex & index)418 void KKameraConfig::slot_deviceSelected(const QModelIndex &index)
419 {
420 	bool isValid = index.isValid();
421     m_actions->action(QStringLiteral("camera_test"))->setEnabled(isValid);
422     m_actions->action(QStringLiteral("camera_remove"))->setEnabled(isValid);
423     m_actions->action(QStringLiteral("camera_configure"))->setEnabled(isValid);
424     m_actions->action(QStringLiteral("camera_summary"))->setEnabled(isValid);
425 }
426 
cbGPIdle(GPContext *,void *)427 void KKameraConfig::cbGPIdle(GPContext * /*context*/, void * /*data*/)
428 {
429 	/*KKameraConfig *self( reinterpret_cast<KKameraConfig*>(data) );*/
430 	qApp->processEvents();
431 }
432 
cbGPCancel(GPContext *,void * data)433 GPContextFeedback KKameraConfig::cbGPCancel(GPContext * /*context*/, void *data)
434 {
435 	KKameraConfig *self( reinterpret_cast<KKameraConfig*>(data) );
436 
437 	// Since in practice no camera driver supports idle callbacks yet,
438 	// we'll use the cancel callback as opportunity to process events
439 	qApp->processEvents();
440 
441 	// If a cancel request is pending, ask gphoto to cancel
442     if (self->m_cancelPending) {
443 		return GP_CONTEXT_FEEDBACK_CANCEL;
444     } else {
445 		return GP_CONTEXT_FEEDBACK_OK;
446     }
447 }
448 
quickHelp() const449 QString KKameraConfig::quickHelp() const
450 {
451 	return i18n("<h1>Digital Camera</h1>\n"
452 	  "This module allows you to configure support for your digital camera.\n"
453 	  "You need to select the camera's model and the port it is connected\n"
454 	  "to on your computer (e.g. USB, Serial, Firewire). If your camera does not\n"
455 	  "appear on the list of <i>Supported Cameras</i>, go to the\n"
456 	  "<a href=\"http://www.gphoto.org\">GPhoto web site</a> for a possible update.<br><br>\n"
457 	  "To view and download images from the digital camera, go to the address\n"
458 	  "<a href=\"camera:/\">camera:/</a> in Konqueror and other KDE applications.");
459 }
460 
slot_error(const QString & message)461 void KKameraConfig::slot_error(const QString &message)
462 {
463 	KMessageBox::error(this, message);
464 }
465 
slot_error(const QString & message,const QString & details)466 void KKameraConfig::slot_error(const QString &message, const QString &details)
467 {
468 	KMessageBox::detailedError(this, message, details);
469 }
470 
471 #include "kamera.moc"
472