1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2  * @file   MainWindow.cpp
3  * @author Sebastien Fourey
4  * @date   July 2010
5  * @brief  Declaration of the class MainWindow
6  *
7  * This file is part of the ZArt software's source code.
8  *
9  * Copyright Sebastien Fourey / GREYC Ensicaen (2010-...)
10  *
11  *                    https://foureys.users.greyc.fr/
12  *
13  * This software is a computer program whose purpose is to demonstrate
14  * the possibilities of the GMIC image processing language by offering the
15  * choice of several manipulations on a video stream acquired from a webcam. In
16  * other words, ZArt is a GUI for G'MIC real-time manipulations on the output
17  * of a webcam.
18  *
19  * This software is governed by the CeCILL  license under French law and
20  * abiding by the rules of distribution of free software.  You can  use,
21  * modify and/ or redistribute the software under the terms of the CeCILL
22  * license as circulated by CEA, CNRS and INRIA at the following URL
23  * "http://www.cecill.info". See also the directory "Licence" which comes
24  * with this source code for the full text of the CeCILL license.
25  *
26  * As a counterpart to the access to the source code and  rights to copy,
27  * modify and redistribute granted by the license, users are provided only
28  * with a limited warranty  and the software's author,  the holder of the
29  * economic rights,  and the successive licensors  have only  limited
30  * liability.
31  *
32  * In this respect, the user's attention is drawn to the risks associated
33  * with loading,  using,  modifying and/or developing or reproducing the
34  * software by the user in light of its specific status of free software,
35  * that may mean  that it is complicated to manipulate,  and  that  also
36  * therefore means  that it is reserved for developers  and  experienced
37  * professionals having in-depth computer knowledge. Users are therefore
38  * encouraged to load and test the software's suitability as regards their
39  * requirements in conditions enabling the security of their systems and/or
40  * data to be ensured and,  more generally, to use and operate it in the
41  * same conditions as regards security.
42  *
43  * The fact that you are presently reading this means that you have had
44  * knowledge of the CeCILL license and that you accept its terms.
45  */
46 #include <iostream>
47 
48 #include <QAction>
49 #include <QActionGroup>
50 #include <QButtonGroup>
51 #include <QComboBox>
52 #include <QDesktopServices>
53 #include <QDir>
54 #include <QDoubleSpinBox>
55 #include <QFileDialog>
56 #include <QFileInfo>
57 #include <QGridLayout>
58 #include <QImageWriter>
59 #include <QInputDialog>
60 #include <QKeySequence>
61 #include <QLabel>
62 #include <QList>
63 #include <QMessageBox>
64 #include <QNetworkAccessManager>
65 #include <QNetworkReply>
66 #include <QNetworkRequest>
67 #include <QSettings>
68 #include <QShortcut>
69 #include <QSlider>
70 #include <QToolTip>
71 #include <QTreeWidgetItemIterator>
72 #include <QUrl>
73 #include <QtXml>
74 
75 #include "Common.h"
76 #include "DialogAbout.h"
77 #include "DialogLicense.h"
78 #include "FilterThread.h"
79 #include "FullScreenWidget.h"
80 #include "ImageConverter.h"
81 #include "ImageView.h"
82 #include "MainWindow.h"
83 #include "OutputWindow.h"
84 #include "TreeWidgetPresetItem.h"
85 #include "WebcamSource.h"
86 
87 #if QT_VERSION >= 0x050201
88 #define CURRENTDATA(CBOX) (CBOX->currentData())
89 #else
90 #define CURRENTDATA(CBOX) (CBOX->itemData(CBOX->currentIndex()))
91 #endif
92 
MainWindow(QWidget * parent)93 MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent), _filterThread(0), _source(Webcam), _currentSource(&_webcam), _currentDir("."), _zeroFPS(false), _presetsCount(0)
94 {
95   setupUi(this);
96 
97   _outputWindow = 0;
98 
99   delete _frameImageView->layout();
100   _frameImageView->setLayout(new QGridLayout);
101   _frameImageView->layout()->addWidget(_imageView);
102 
103   QMargins margins = _rightPanel->layout()->contentsMargins();
104   margins.setTop(0);
105   margins.setBottom(0);
106   _rightPanel->layout()->setContentsMargins(margins);
107   _rightPanelSplitter->setCollapsible(0, false);
108 
109   _verticalSplitter->setCollapsible(0, false);
110   _verticalSplitter->setCollapsible(1, true);
111 
112   _leftSplitter->setCollapsible(0, false);
113   _leftSplitter->setCollapsible(1, true);
114 
115   _fullScreenWidget = new FullScreenWidget(this);
116   _fullScreenWidget->setFavesModel(_cbFaves->model());
117   connect(_fullScreenWidget, SIGNAL(escapePressed()), this, SLOT(toggleFullScreenMode()));
118   connect(_fullScreenWidget->imageView(), SIGNAL(keypointPositionsChanged()), this, SLOT(onFullScreenKeypointsEvent()));
119   connect(_fullScreenWidget->imageView(), SIGNAL(resized(QSize)), this, SLOT(fullScreenImageViewResized(QSize)));
120 
121   _displayMode = InWindow;
122 
123   QSettings settings;
124 
125   // Menu and actions
126   QMenu * menu;
127   menu = menuBar()->addMenu("&File");
128 
129   QAction * action = new QAction("&Save presets...", this);
130   action->setShortcut(QKeySequence::SaveAs);
131 #if QT_VERSION >= 0x040600
132   action->setIcon(QIcon::fromTheme("document-save-as"));
133 #endif
134   menu->addAction(action);
135   connect(action, SIGNAL(triggered()), this, SLOT(savePresetsFile()));
136 
137   action = new QAction("&Quit", this);
138   action->setShortcut(QKeySequence::Quit);
139 #if QT_VERSION >= 0x040600
140   action->setIcon(QIcon::fromTheme("application-exit", QIcon(":/images/application-exit.png")));
141 #else
142   action->setIcon(QIcon(":/images/application-exit.png"));
143 #endif
144   connect(action, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
145   menu->addAction(action);
146 
147 #if QT_VERSION >= 0x040600
148   _tbZoomOriginal->setIcon(QIcon::fromTheme("zoom-original", QIcon(":/images/zoom-original.png")));
149   _tbZoomFit->setIcon(QIcon::fromTheme("zoom-fit-best", QIcon(":/images/zoom-fit-best.png")));
150   _tbCamResolutionsRefresh->setIcon(QIcon::fromTheme("view-refresh"));
151 #else
152   _tbZoomOriginal->setIcon(QIcon(":/images/zoom-original.png"));
153   _tbZoomFit->setIcon(QIcon(":/images/zoom-fit-best.png"));
154 #endif
155 
156   connect(_tbCamResolutionsRefresh, SIGNAL(clicked(bool)), this, SLOT(onRefreshCameraResolutions()));
157 
158   // Find available cameras, and setup the default one
159   QList<int> cameras = WebcamSource::getWebcamList();
160   int firstUnused = WebcamSource::getFirstUnusedWebcam();
161   initGUIFromCameraList(cameras, firstUnused);
162 
163   QSize cameraSize = CURRENTDATA(_comboCamResolution).toSize();
164   if (!cameraSize.isValid()) {
165     _imageView->resize(QSize(640, 480));
166   } else {
167     _imageView->resize(cameraSize);
168   }
169   _frameImageView->resize(_imageView->size());
170 
171   // Options menu
172   menu = menuBar()->addMenu("&Options");
173 
174   action = menu->addAction("Show right &panel", this, SLOT(onRightPanel(bool)), QKeySequence("Ctrl+F"));
175   action->setCheckable(true);
176   action->setChecked(settings.value("showRightPanel", true).toBool());
177 
178   action = menu->addAction("&Full screen", this, SLOT(toggleFullScreenMode()), QKeySequence(Qt::Key_F5));
179   action->setShortcutContext(Qt::ApplicationShortcut);
180 
181   _outputWindowAction = new QAction("&Secondary window", this);
182   _outputWindowAction->setShortcut(QKeySequence("Ctrl+O"));
183   _outputWindowAction->setShortcutContext(Qt::ApplicationShortcut);
184   _outputWindowAction->setCheckable(true);
185   connect(_outputWindowAction, SIGNAL(toggled(bool)), this, SLOT(onOutputWindow(bool)));
186   menu->addAction(_outputWindowAction);
187 
188   menu->addSeparator();
189   action = menu->addAction("Detect &cameras", this, SLOT(onDetectCameras()));
190   menu->addSeparator();
191 
192   // Presets
193   QString presetsConfig = settings.value("Presets", QString("Built-in")).toString();
194 
195   QActionGroup * group = new QActionGroup(menu);
196   group->setExclusive(true);
197 
198   // Built-in
199   _builtInPresetsAction = new QAction("&Built-in presets", menu);
200   _builtInPresetsAction->setCheckable(true);
201   connect(_builtInPresetsAction, SIGNAL(toggled(bool)), this, SLOT(onUseBuiltinPresets(bool)));
202   group->addAction(_builtInPresetsAction);
203   menu->addAction(_builtInPresetsAction);
204   _builtInPresetsAction->setChecked(true); // Default to Built-in presets
205 
206   // File
207   action = menu->addAction("&Presets file...", this, SLOT(setPresetsFile()));
208   action->setCheckable(true);
209   group->addAction(action);
210   QString filename = settings.value("PresetsFile", QString()).toString();
211   if (presetsConfig == "File" && !filename.isEmpty()) {
212     setPresetsFile(filename);
213     action->setChecked(true);
214   }
215 
216   // Refresh
217   menu->addSeparator();
218 
219   action = menu->addAction("&Reload presets", this, SLOT(onReloadPresets()), QKeySequence("Ctrl+R"));
220   action->setShortcutContext(Qt::ApplicationShortcut);
221 
222   // Help menu
223   menu = menuBar()->addMenu("&Help");
224 
225   action = menu->addAction("&Visit G'MIC website", this, SLOT(visitGMIC()));
226   action->setIcon(QIcon(":/images/gmic_hat.png"));
227   action = menu->addAction("&License...", this, SLOT(license()));
228   action = menu->addAction("&About...", this, SLOT(about()));
229 
230   _imageView->QWidget::resize(_webcam.width(), _webcam.height());
231 
232   _bgZoom = new QButtonGroup(this);
233   _bgZoom->setExclusive(true);
234   _tbZoomFit->setCheckable(true);
235   _tbZoomFit->setChecked(true);
236   _tbZoomOriginal->setCheckable(true);
237   _bgZoom->addButton(_tbZoomOriginal);
238   _bgZoom->addButton(_tbZoomFit);
239 
240   _cbPreviewMode->addItem("Full", FilterThread::Full);
241   _cbPreviewMode->addItem("Top", FilterThread::TopHalf);
242   _cbPreviewMode->addItem("Left", FilterThread::LeftHalf);
243   _cbPreviewMode->addItem("Bottom", FilterThread::BottomHalf);
244   _cbPreviewMode->addItem("Right", FilterThread::RightHalf);
245   _cbPreviewMode->addItem("Duplicate horizontal", FilterThread::DuplicateHorizontal);
246   _cbPreviewMode->addItem("Duplicate vertical", FilterThread::DuplicateVertical);
247   _cbPreviewMode->addItem("Original", FilterThread::Original);
248 
249 #if (QT_VERSION >= 0x040600)
250   _cbPreviewMode->setItemIcon(0, QIcon::fromTheme("view-fullscreen"));
251   _cbPreviewMode->setItemIcon(1, QIcon::fromTheme("go-up"));
252   _cbPreviewMode->setItemIcon(2, QIcon::fromTheme("go-previous"));
253   _cbPreviewMode->setItemIcon(3, QIcon::fromTheme("go-down"));
254   _cbPreviewMode->setItemIcon(4, QIcon::fromTheme("go-next"));
255   _cbPreviewMode->setItemIcon(5, QIcon::fromTheme("edit-copy"));
256   _cbPreviewMode->setItemIcon(6, QIcon::fromTheme("edit-copy"));
257   _cbPreviewMode->setItemIcon(7, QIcon::fromTheme("go-home"));
258   _tbCamera->setIcon(QIcon::fromTheme("camera-photo", QIcon(":/images/camera.png")));
259 #else
260   _tbCamera->setIcon(QIcon(":images/camera.png");
261 #endif
262 
263   connect(_cbPreviewMode, SIGNAL(activated(int)), this, SLOT(onPreviewModeChanged(int)));
264 
265   connect(_tbZoomOriginal, SIGNAL(clicked()), _imageView, SLOT(zoomOriginal()));
266 
267   connect(_tbZoomFit, SIGNAL(clicked()), _imageView, SLOT(zoomFitBest()));
268 
269   connect(_pbApply, SIGNAL(clicked()), this, SLOT(commandModified()));
270 
271   connect(_tbCamera, SIGNAL(clicked()), this, SLOT(snapshot()));
272 
273   _imageView->setMouseTracking(true);
274 
275   connect(_imageView, SIGNAL(mousePress(QMouseEvent *)), this, SLOT(imageViewMouseEvent(QMouseEvent *)));
276 
277   connect(_imageView, SIGNAL(mouseMove(QMouseEvent *)), this, SLOT(imageViewMouseEvent(QMouseEvent *)));
278 
279   connect(_imageView, SIGNAL(keypointPositionsChanged()), this, SLOT(onImageViewKeypointsEvent()));
280 
281   connect(_imageView, SIGNAL(resized(QSize)), this, SLOT(imageViewResized(QSize)));
282 
283   connect(_treeGPresets, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(presetClicked(QTreeWidgetItem *, int)));
284 
285   connect(_fullScreenWidget->treeWidget(), SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(presetClicked(QTreeWidgetItem *, int)));
286 
287   connect(_commandEditor, SIGNAL(commandModified()), this, SLOT(commandModified()));
288 
289   _sliderWebcamSkipFrames->setRange(0, 10);
290   connect(_sliderWebcamSkipFrames, SIGNAL(valueChanged(int)), this, SLOT(setWebcamSkipFrames(int)));
291 
292   _sliderVideoSkipFrames->setRange(0, 10);
293   connect(_sliderVideoSkipFrames, SIGNAL(valueChanged(int)), this, SLOT(setVideoSkipFrames(int)));
294 
295   _sliderImageFPS->setValue(24);
296   _sliderImageFPS->setToolTip("24 fps");
297   connect(_sliderImageFPS, SIGNAL(valueChanged(int)), this, SLOT(setImageFPS(int)));
298   _zeroFPS = false;
299 
300   _sliderVideoFPS->setValue(24);
301   _sliderVideoFPS->setToolTip("24 fps");
302   connect(_sliderVideoFPS, SIGNAL(valueChanged(int)), this, SLOT(setVideoFPS(int)));
303 
304   _cbVideoFileLoop->setChecked(true);
305   _videoFile.setLoop(true);
306   connect(_cbVideoFileLoop, SIGNAL(toggled(bool)), this, SLOT(onVideoFileLoop(bool)));
307 
308   _webcamParamsWidget->setVisible(_source == Webcam);
309   _imageParamsWidget->setVisible(_source == StillImage);
310   _videoParamsWidget->setVisible(_source == Video);
311 
312   connect(_pbOpenImageFile, SIGNAL(clicked()), this, SLOT(onOpenImageFile()));
313   connect(_pbOpenVideoFile, SIGNAL(clicked()), this, SLOT(onOpenVideoFile()));
314 
315   // Image filter for the "Save as..." dialog
316   QList<QByteArray> formats = QImageWriter::supportedImageFormats();
317   QList<QByteArray>::iterator it = formats.begin();
318   QList<QByteArray>::iterator end = formats.end();
319   _imageFilters = "Image file (";
320   while (it != end) {
321     _imageFilters += QString("*.") + QString(*it).toLower() + " ";
322     ++it;
323   }
324   _imageFilters.chop(1);
325   _imageFilters += ")";
326 
327   _startStopAction = new QAction(this);
328   _startStopAction->setToolTip("Launch processing (Ctrl+P)");
329   _startStopAction->setShortcut(QKeySequence("Ctrl+P"));
330   _startStopAction->setShortcutContext(Qt::ApplicationShortcut);
331   _startStopAction->setCheckable(true);
332 
333   _tbPlay->setDefaultAction(_startStopAction);
334   changePlayButtonAppearence(false);
335   connect(_fullScreenWidget, SIGNAL(spaceBarPressed()), _startStopAction, SLOT(toggle()));
336   connect(_startStopAction, SIGNAL(toggled(bool)), this, SLOT(changePlayButtonAppearence(bool)));
337   connect(_startStopAction, SIGNAL(toggled(bool)), this, SLOT(onPlayAction(bool)));
338 
339   connect(_commandParamsWidget, SIGNAL(valueChanged()), this, SLOT(onCommandParametersChanged()));
340   connect(_fullScreenWidget->commandParamsWidget(), SIGNAL(valueChanged()), this, SLOT(onCommandParametersChangedFullScreen()));
341 
342   if (!settings.value("showRightPanel", true).toBool())
343     _rightPanel->hide();
344 
345   if (_comboWebcam->count()) {
346     _webcam.setCameraIndex(CURRENTDATA(_comboWebcam).toInt());
347     // Update actual source capture size
348     _webcam.start();
349     _webcam.stop();
350   }
351 
352   // Favorites
353 #if QT_VERSION >= 0x040600
354   _tbAddFave->setIcon(QIcon::fromTheme("list-add", QIcon(":/images/list-add.png")));
355   _tbRemoveFave->setIcon(QIcon::fromTheme("list-remove", QIcon(":/images/list-remove.png")));
356 #else
357   _tbAddFave->setIcon(QIcon(":/images/list-add.png"));
358   _tbRemoveFave->setIcon(QIcon(":/images/list-remove.png"));
359 #endif
360   _tbRenameFave->setIcon(QIcon(":/images/rename.png"));
361   int favesCount = settings.value("Faves/Count", 0).toInt();
362   for (int i = 0; i < favesCount; ++i) {
363     QStringList list = settings.value(QString("Faves/%1").arg(i), QStringList()).toStringList();
364     if (list.size() >= 3) {
365       _cbFaves->addItem(list[0], list);
366     }
367   }
368   _cbFaves->setEnabled(favesCount);
369   _tbRenameFave->setEnabled(favesCount);
370   _tbRemoveFave->setEnabled(favesCount);
371   _fullScreenWidget->cbFaves()->setEnabled(favesCount);
372   _tbAddFave->setEnabled(false);
373 
374   connect(_tbAddFave, SIGNAL(clicked(bool)), this, SLOT(onAddFave()));
375   connect(_tbRemoveFave, SIGNAL(clicked(bool)), this, SLOT(onRemoveFave()));
376   connect(_tbRenameFave, SIGNAL(clicked(bool)), this, SLOT(onRenameFave()));
377   connect(_cbFaves, SIGNAL(activated(int)), this, SLOT(onFaveSelected(int)));
378   connect(_fullScreenWidget->cbFaves(), SIGNAL(activated(int)), _cbFaves, SLOT(setCurrentIndex(int)));
379   connect(_fullScreenWidget->cbFaves(), SIGNAL(activated(int)), this, SLOT(onFaveSelected(int)));
380 
381   updateWindowTitle();
382 }
383 
384 MainWindow::~MainWindow()
385 {
386   QSettings settings;
387   for (int i = 0; i < _cameraDefaultResolutionsIndexes.size(); ++i) {
388     const int index = _cameraDefaultResolutionsIndexes[i];
389     settings.setValue(QString("WebcamSource/DefaultResolutionCam%1").arg(i), (index == -1) ? QSize() : WebcamSource::webcamResolutions(i).at(index));
390   }
391   settings.remove("Faves");
392   settings.setValue("Faves/Count", _cbFaves->count());
393   for (int i = 0; i < _cbFaves->count(); ++i) {
394     settings.setValue(QString("Faves/%1").arg(i), _cbFaves->itemData(i).toStringList());
395   }
396   if (_filterThread) {
397     _filterThread->stop();
398     _filterThreadSemaphore.release();
399     _filterThread->wait();
400     delete _filterThread;
401   }
402   delete _fullScreenWidget;
403   if (_outputWindow) {
404     delete _outputWindow;
405   }
406 }
407 
408 void MainWindow::setCurrentPreset(QDomNode node)
409 {
410   QDomNode command = node.namedItem("command");
411   QString text = command.firstChild().toText().data().trimmed();
412   if (text == "_none_") {
413     _commandEditor->clear();
414     _commandEditor->setEnabled(false);
415   } else {
416     _commandEditor->setEnabled(true);
417     _commandEditor->setPlainText(text);
418   }
419   _currentPresetNode = node;
420   if (_displayMode == FullScreen) {
421     _fullScreenWidget->commandParamsWidget()->saveValuesInDOM();
422     _fullScreenWidget->commandParamsWidget()->build(node);
423     _commandParamsWidget->build(node);
424   } else {
425     _commandParamsWidget->saveValuesInDOM();
426     _commandParamsWidget->build(node);
427   }
428 }
429 
430 void MainWindow::showOneSourceImage()
431 {
432   _currentSource->capture();
433   cv::Mat * image = _currentSource->image();
434   if (image) {
435     _imageView->imageMutex().lock();
436     QSize size(image->cols, image->rows);
437     if (_imageView->image().size() != size) {
438       _imageView->image() = QImage(size, QImage::Format_RGB888);
439     }
440     ImageConverter::convert(image, &(_imageView->image()));
441     _imageView->imageMutex().unlock();
442     _imageView->checkSize();
443     _imageView->repaint();
444   }
445 }
446 
447 void MainWindow::about()
448 {
449   DialogAbout * d = new DialogAbout(this);
450   d->exec();
451   delete d;
452 }
453 
454 void MainWindow::visitGMIC()
455 {
456   QDesktopServices::openUrl(QUrl("http://gmic.eu/"));
457 }
458 
459 void MainWindow::license()
460 {
461   DialogLicense * d = new DialogLicense(this);
462   d->exec();
463   delete d;
464 }
465 
466 QString MainWindow::getPreset(const QString & name)
467 {
468   QDomNodeList list = _presets.elementsByTagName("preset");
469   for (int i = 0; i < list.count(); ++i) {
470     QDomNode n = list.at(i);
471     if (n.attributes().namedItem("name").nodeValue() == name) {
472       QDomNode command = n.namedItem("command");
473       return command.firstChild().toText().data().trimmed();
474     }
475   }
476   return QString();
477 }
478 
479 void MainWindow::updateKeypointsInViews()
480 {
481   KeypointList keypoints = (_displayMode == InWindow) ? _commandParamsWidget->keypoints() : _fullScreenWidget->commandParamsWidget()->keypoints();
482   if (_displayMode == InWindow) {
483     _imageView->setKeypoints(keypoints);
484     _imageView->repaint();
485   }
486   _fullScreenWidget->imageView()->setKeypoints(keypoints);
487   if (_displayMode == FullScreen) {
488     _fullScreenWidget->imageView()->repaint();
489   }
490   if (_outputWindow) {
491     _outputWindow->imageView()->setKeypoints(keypoints);
492   }
493   if (_outputWindow && _outputWindow->isVisible() && _outputWindowAction->isChecked()) {
494     _outputWindow->imageView()->repaint();
495   }
496 }
497 
498 void MainWindow::onImageAvailable()
499 {
500   if (_displayMode == InWindow) {
501     _imageView->checkSize();
502     _imageView->repaint();
503   }
504   if (_displayMode == FullScreen) {
505     _fullScreenWidget->imageView()->checkSize();
506     _fullScreenWidget->imageView()->repaint();
507   }
508   if (_outputWindow && _outputWindow->isVisible() && _outputWindowAction->isChecked()) {
509     _outputWindow->imageView()->checkSize();
510     _outputWindow->imageView()->repaint();
511   }
512 }
513 
514 void MainWindow::play()
515 {
516   int pm = _cbPreviewMode->itemData(_cbPreviewMode->currentIndex()).toInt();
517   FilterThread::PreviewMode previewMode = static_cast<FilterThread::PreviewMode>(pm);
518   ImageView * viewA = (_displayMode == InWindow) ? _imageView : _fullScreenWidget->imageView();
519   ImageView * viewB = 0;
520   if (_outputWindow && _outputWindow->isVisible() && _outputWindowAction->isChecked()) {
521     viewB = _outputWindow->imageView();
522   }
523 
524   switch (_source) {
525   case Webcam:
526     _filterThread = new FilterThread(_webcam, _commandEditor->toPlainText(), &viewA->image(), &viewA->imageMutex(), (viewB) ? &viewB->image() : 0, (viewB) ? &viewB->imageMutex() : 0, previewMode,
527                                      _sliderWebcamSkipFrames->value(), -1, 0);
528     break;
529   case StillImage:
530     _filterThread = new FilterThread(_stillImage, _commandEditor->toPlainText(), &viewA->image(), &viewA->imageMutex(), (viewB) ? &viewB->image() : 0, (viewB) ? &viewB->imageMutex() : 0, previewMode,
531                                      0, _sliderImageFPS->value(), &_filterThreadSemaphore);
532     break;
533   case Video:
534     _filterThread = new FilterThread(_videoFile, _commandEditor->toPlainText(), &viewA->image(), &viewA->imageMutex(), (viewB) ? &viewB->image() : 0, (viewB) ? &viewB->imageMutex() : 0, previewMode,
535                                      _sliderVideoSkipFrames->value(), _sliderVideoFPS->value(), 0);
536     break;
537   }
538   connect(_filterThread, SIGNAL(imageAvailable()), this, SLOT(onImageAvailable()));
539   connect(_filterThread, SIGNAL(finished()), this, SLOT(onFilterThreadFinished()));
540   connect(_filterThread, SIGNAL(endOfCapture()), this, SLOT(onEndOfSource()));
541   if (_displayMode == FullScreen) {
542     _filterThread->setArguments(_fullScreenWidget->commandParamsWidget()->valueString());
543   } else {
544     _filterThread->setArguments(_commandParamsWidget->valueString());
545   }
546   imageViewResized(_imageView->size());
547   if (_outputWindow && _outputWindow->isVisible()) {
548     outputWindowImageViewResized(_outputWindow->imageView()->size());
549   }
550   if (_source == StillImage) {
551     _filterThreadSemaphore.release();
552   }
553   updateKeypointsInViews();
554   _filterThread->start();
555 }
556 
557 void MainWindow::stop()
558 {
559   if (_filterThread) {
560     _filterThread->stop();
561     _filterThreadSemaphore.release();
562     _filterThread->wait();
563     _filterThread = 0;
564   }
565 }
566 
567 void MainWindow::onEndOfSource()
568 {
569   _startStopAction->setChecked(false);
570 }
571 
572 void MainWindow::onFilterThreadFinished()
573 {
574   delete sender();
575 }
576 
577 void MainWindow::onCommandParametersChanged()
578 {
579   if (_filterThread) {
580     _filterThread->setArguments(_commandParamsWidget->valueString());
581     if (_source == StillImage && _zeroFPS)
582       _filterThreadSemaphore.release();
583   }
584   updateKeypointsInViews();
585 }
586 
587 void MainWindow::onCommandParametersChangedFullScreen()
588 {
589   if (_filterThread) {
590     if (_displayMode == FullScreen) {
591       _filterThread->setArguments(_fullScreenWidget->commandParamsWidget()->valueString());
592       if (_source == StillImage && _zeroFPS)
593         _filterThreadSemaphore.release();
594     }
595   }
596   updateKeypointsInViews();
597 }
598 
599 void MainWindow::toggleFullScreenMode()
600 {
601   bool running = _filterThread && _filterThread->isRunning();
602   if (_displayMode == FullScreen) {
603     TreeWidgetPresetItem * item = dynamic_cast<TreeWidgetPresetItem *>(_fullScreenWidget->treeWidget()->currentItem());
604     _fullScreenWidget->close();
605     _displayMode = InWindow;
606     stop();
607     _fullScreenWidget->commandParamsWidget()->saveValuesInDOM();
608     _commandParamsWidget->build(_currentPresetNode);
609     if (item) {
610       _treeGPresets->setCurrentItem(findPresetItem(_treeGPresets, item->path()));
611     }
612   } else { // InWindow to FullScreen mode
613     TreeWidgetPresetItem * item = dynamic_cast<TreeWidgetPresetItem *>(_treeGPresets->currentItem());
614     _displayMode = FullScreen;
615     stop();
616     _commandParamsWidget->saveValuesInDOM();
617     _fullScreenWidget->imageView()->image() = _imageView->image();
618     _fullScreenWidget->imageView()->zoomFitBest();
619     _fullScreenWidget->commandParamsWidget()->build(_currentPresetNode);
620     _fullScreenWidget->showFullScreen();
621     if (item) {
622       QTreeWidget * tw = _fullScreenWidget->treeWidget();
623       tw->setCurrentItem(findPresetItem(tw, item->path()));
624     }
625   }
626   if (running) {
627     play();
628   }
629 }
630 
631 void MainWindow::onPlayAction(bool on)
632 {
633   if (!on && _filterThread) {
634     stop();
635     if (_source == Webcam) {
636       _webcam.stop();
637     }
638     changePlayButtonAppearence(false);
639     return;
640   }
641   if (on && !_filterThread) {
642     if ((_source == Video && _videoFile.filename().isEmpty()) || (_source == StillImage && _stillImage.filename().isEmpty())) {
643       QMessageBox::information(this, "Information", "No input file.\nPlease select one first.");
644       _tabParams->setCurrentIndex(0);
645       _startStopAction->setChecked(false);
646     } else {
647       if (_source == Webcam) {
648         _webcam.start();
649       }
650       play();
651       changePlayButtonAppearence(true);
652     }
653     return;
654   }
655 }
656 
657 void MainWindow::onComboSourceChanged(int i)
658 {
659   static bool firstRun = true;
660   bool running = _filterThread && _filterThread->isRunning();
661   if (running) {
662     stop();
663   }
664   _source = static_cast<Source>(_comboSource->itemData(i).toInt());
665   switch (_source) {
666   case Webcam:
667     _currentSource = &_webcam;
668     break;
669   case StillImage:
670     _currentSource = &_stillImage;
671     _webcam.stop();
672     break;
673   case Video:
674     _currentSource = &_videoFile;
675     _webcam.stop();
676     break;
677   }
678   _webcamParamsWidget->setVisible(_source == Webcam);
679   _imageParamsWidget->setVisible(_source == StillImage);
680   _videoParamsWidget->setVisible(_source == Video);
681   updateWindowTitle();
682   if (_source == StillImage && _stillImage.filename().isEmpty() && (!firstRun || WebcamSource::getCachedWebcamList().size())) {
683     onOpenImageFile();
684   }
685   if (_source == Video && _videoFile.filename().isEmpty())
686     onOpenVideoFile();
687   if (running) {
688     if (_source == Webcam) {
689       _webcam.start();
690     }
691     play();
692   } else {
693     showOneSourceImage();
694   }
695   firstRun = false;
696 }
697 
698 void MainWindow::setInputImage(QString filepath)
699 {
700   _stillImage.loadImage(filepath);
701 }
702 
703 void MainWindow::setInputVideo(QString filepath)
704 {
705   _videoFile.loadVideoFile(filepath);
706 }
707 
708 void MainWindow::onOpenImageFile()
709 {
710   QString filename;
711   filename = QFileDialog::getOpenFileName(this, "Select an image file", _stillImage.filePath().isEmpty() ? _videoFile.filePath() : _stillImage.filePath(),
712                                           "Image files (*.bmp *.gif *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm *.svg)");
713   if (filename.isEmpty()) {
714     return;
715   }
716   if (_source == StillImage && _filterThread) {
717     stop();
718     if (_stillImage.loadImage(filename)) {
719       updateWindowTitle();
720     }
721     play();
722   } else {
723     if (_stillImage.loadImage(filename)) {
724       updateWindowTitle();
725       showOneSourceImage();
726     }
727   }
728 }
729 
730 void MainWindow::onOpenVideoFile()
731 {
732   QString filename;
733   filename = QFileDialog::getOpenFileName(this, "Select a video file", _videoFile.filePath().isEmpty() ? _stillImage.filePath() : _videoFile.filePath(), "Video files (*.avi *.mpg)");
734   if (filename.isEmpty())
735     return;
736   if (_source == Video && _filterThread) {
737     stop();
738     if (_videoFile.loadVideoFile(filename)) {
739       updateWindowTitle();
740       play();
741     }
742   } else {
743     if (_videoFile.loadVideoFile(filename)) {
744       updateWindowTitle();
745       showOneSourceImage();
746     }
747   }
748 }
749 
750 void MainWindow::updateWindowTitle()
751 {
752   QString name;
753   switch (_source) {
754   case Webcam:
755     setWindowTitle(QString("ZArt %1 (Webcam %2x%3)").arg(ZART_VERSION_STRING).arg(_currentSource->width()).arg(_currentSource->height()));
756     break;
757   case StillImage:
758     name = QFileInfo(_stillImage.filename()).fileName();
759     if (name.isEmpty())
760       setWindowTitle(QString("ZArt %1 (No input file)").arg(ZART_VERSION_STRING));
761     else
762       setWindowTitle(QString("ZArt %1 (%2 %3x%4)").arg(ZART_VERSION_STRING).arg(name).arg(_currentSource->width()).arg(_currentSource->height()));
763     break;
764   case Video:
765     name = QFileInfo(_videoFile.filename()).fileName();
766     if (name.isEmpty())
767       setWindowTitle(QString("ZArt %1 (No input file)").arg(ZART_VERSION_STRING));
768     else
769       setWindowTitle(QString("ZArt %1 (%2 %3x%4)").arg(ZART_VERSION_STRING).arg(name).arg(_currentSource->width()).arg(_currentSource->height()));
770     break;
771   }
772 }
773 
774 void MainWindow::onVideoFileLoop(bool on)
775 {
776   _videoFile.setLoop(on);
777 }
778 
779 void MainWindow::changePlayButtonAppearence(bool on)
780 {
781   if (on) {
782 #if QT_VERSION >= 0x040600
783     _startStopAction->setIcon(QIcon::fromTheme("media-playback-stop", QIcon(":/images/media-playback-stop.png")));
784 #else
785     _startStopAction->setIcon(QIcon(":/images/media-playback-stop.png"));
786 #endif
787     _startStopAction->setToolTip("Stop processing (Ctrl+P)");
788   } else {
789 #if QT_VERSION >= 0x040600
790     _startStopAction->setIcon(QIcon::fromTheme("media-playback-start", QIcon(":/images/media-playback-start.png")));
791 #else
792     _startStopAction->setIcon(QIcon(":/images/media-playback-start.png"));
793 #endif
794     _startStopAction->setToolTip("Launch processing (Ctrl+P)");
795   }
796 }
797 
798 void MainWindow::imageViewMouseEvent(QMouseEvent * event)
799 {
800   int buttons = 0;
801   if (event->buttons() & Qt::LeftButton) {
802     buttons |= 1;
803   }
804   if (event->buttons() & Qt::RightButton) {
805     buttons |= 2;
806   }
807   if (event->buttons() & Qt::MidButton) {
808     buttons |= 4;
809   }
810   if (_filterThread) {
811     _filterThread->setMousePosition(event->x(), event->y(), buttons);
812   }
813   if (_source == StillImage && _zeroFPS) {
814     _filterThreadSemaphore.release();
815   }
816 }
817 
818 void MainWindow::imageViewResized(QSize size)
819 {
820   if (!_outputWindow || !_outputWindow->isVisible()) {
821     if (_filterThread) {
822       _filterThread->setViewSize(size);
823     }
824   }
825 }
826 
827 void MainWindow::outputWindowImageViewResized(QSize size)
828 {
829   if (size.isValid() && _filterThread) {
830     _filterThread->setViewSize(size);
831   }
832 }
833 
834 void MainWindow::fullScreenImageViewResized(QSize size)
835 {
836   if (size.isValid() && _filterThread) {
837     _filterThread->setViewSize(size);
838   }
839 }
840 
841 void MainWindow::commandModified()
842 {
843   if (_filterThread && _filterThread->isRunning()) {
844     stop();
845     play();
846   }
847 }
848 
849 void MainWindow::presetClicked(QTreeWidgetItem * item, int)
850 {
851   TreeWidgetPresetItem * presetItem = dynamic_cast<TreeWidgetPresetItem *>(item);
852   if (!presetItem) {
853     return;
854   }
855   if (presetItem->node().isNull()) { // A "folder" has been clicked, not a preset
856     _tbAddFave->setEnabled(false);
857     return;
858   }
859   _tbAddFave->setEnabled(true);
860   setCurrentPreset(presetItem->node());
861   _tabParams->setCurrentIndex(1);
862   int previewMode = _cbPreviewMode->itemData(_cbPreviewMode->currentIndex()).toInt();
863   if (previewMode == FilterThread::Original) {
864     _cbPreviewMode->setCurrentIndex(0);
865     onPreviewModeChanged(0);
866   }
867   commandModified();
868 }
869 
870 void MainWindow::snapshot()
871 {
872   if (_filterThread)
873     _startStopAction->setChecked(false);
874   QString filename = QFileDialog::getSaveFileName(this, "Save image as...", _currentDir, _imageFilters, 0, 0);
875   if (!filename.isEmpty()) {
876     QFileInfo info(filename);
877     _currentDir = info.filePath();
878     QImageWriter writer(filename);
879     _imageView->imageMutex().lock();
880     writer.write(_imageView->image());
881     _imageView->imageMutex().unlock();
882   }
883 }
884 
885 void MainWindow::setWebcamSkipFrames(int i)
886 {
887   _sliderWebcamSkipFrames->setToolTip(QString("%1").arg(i));
888   QToolTip::showText(_sliderWebcamSkipFrames->mapToGlobal(QPoint(0, 0)), QString("%1").arg(i), _sliderWebcamSkipFrames);
889   if (_filterThread) {
890     _filterThread->setFrameSkip(i);
891   }
892 }
893 
894 void MainWindow::setVideoSkipFrames(int i)
895 {
896   _sliderVideoSkipFrames->setToolTip(QString("%1").arg(i));
897   QToolTip::showText(_sliderVideoSkipFrames->mapToGlobal(QPoint(0, 0)), QString("%1").arg(i), _sliderVideoSkipFrames);
898   if (_filterThread) {
899     _filterThread->setFrameSkip(i);
900   }
901 }
902 
903 void MainWindow::setImageFPS(int fps)
904 {
905   _sliderImageFPS->setToolTip(QString("%1 fps").arg(fps));
906   QToolTip::showText(_sliderImageFPS->mapToGlobal(QPoint(0, 0)), QString("%1 fps").arg(fps), _sliderImageFPS);
907   if (_filterThread) {
908     _filterThread->setFPS(fps);
909   }
910   if (_source == StillImage && _zeroFPS) {
911     _filterThreadSemaphore.release();
912   }
913   _zeroFPS = !fps;
914 }
915 
916 void MainWindow::setVideoFPS(int fps)
917 {
918   _sliderVideoFPS->setToolTip(QString("%1 fps").arg(fps));
919   QToolTip::showText(_sliderVideoFPS->mapToGlobal(QPoint(0, 0)), QString("%1 fps").arg(fps), _sliderVideoFPS);
920   if (_filterThread) {
921     _filterThread->setFPS(fps);
922   }
923 }
924 
925 void MainWindow::onWebcamComboChanged(int index)
926 {
927   index = _comboWebcam->itemData(index).toInt();
928   if (_source == Webcam && _filterThread && _filterThread->isRunning()) {
929     stop();
930     _webcam.stop();
931     updateCameraResolutionCombo();
932     _webcam.setCaptureSize(CURRENTDATA(_comboCamResolution).toSize());
933     _webcam.setCameraIndex(index);
934     _webcam.start();
935     play();
936   } else {
937     updateCameraResolutionCombo();
938     _webcam.setCaptureSize(CURRENTDATA(_comboCamResolution).toSize());
939     _webcam.setCameraIndex(index);
940   }
941 }
942 
943 void MainWindow::onWebcamResolutionComboChanged(int i)
944 {
945   int currentCam = _comboWebcam->currentIndex();
946   _cameraDefaultResolutionsIndexes[currentCam] = i;
947   QSize resolution = CURRENTDATA(_comboCamResolution).toSize();
948   if (_source == Webcam && _filterThread && _filterThread->isRunning()) {
949     stop();
950     _webcam.setCaptureSize(resolution);
951     play();
952   } else {
953     _webcam.setCaptureSize(resolution);
954     // Update actual source capture size
955     _webcam.start();
956     _webcam.stop();
957   }
958   updateWindowTitle();
959 }
960 
961 void MainWindow::setPresets(const QDomElement & domE)
962 {
963   _treeGPresets->clear();
964   _presetsCount = 0;
965   addPresets(domE, 0);
966 
967   QString label;
968   label.sprintf("Presets (%d)", _presetsCount);
969   _treeGPresets->setHeaderLabel(label);
970   _fullScreenWidget->treeWidget()->setHeaderLabel(label);
971 
972   _fullScreenWidget->treeWidget()->clear();
973   int count = _treeGPresets->topLevelItemCount();
974   for (int i = 0; i < count; ++i) {
975     _fullScreenWidget->treeWidget()->addTopLevelItem(_treeGPresets->topLevelItem(i)->clone());
976   }
977 }
978 
979 void MainWindow::addPresets(const QDomElement & domE, TreeWidgetPresetItem * parent)
980 {
981   for (QDomNode node = domE.firstChild(); !node.isNull(); node = node.nextSibling()) {
982     QString name = node.attributes().namedItem("name").nodeValue();
983     if (node.nodeName() == QString("preset")) {
984       QStringList strList;
985       strList << name;
986       if (!parent) {
987         _treeGPresets->addTopLevelItem(new TreeWidgetPresetItem(strList, node));
988       } else {
989         new TreeWidgetPresetItem(parent, strList, node);
990       }
991       ++_presetsCount;
992     } else if (node.nodeName() == QString("preset_group")) {
993       TreeWidgetPresetItem * folder = new TreeWidgetPresetItem(QStringList() << name);
994       _treeGPresets->addTopLevelItem(folder);
995       addPresets(node.toElement(), folder);
996     }
997   }
998 }
999 
1000 void MainWindow::setPresetsFile(const QString & file)
1001 {
1002   QString filename = file;
1003   if (filename.isEmpty()) {
1004     QSettings settings;
1005     QString s = settings.value("PresetsFile").toString();
1006     QString dir = ".";
1007     if (QFileInfo(s).exists())
1008       dir = QFileInfo(s).absolutePath();
1009     filename = QFileDialog::getOpenFileName(this, "Open a presets file", dir, "Preset files (*.xml)");
1010   }
1011   if (!filename.isEmpty()) {
1012     QSettings settings;
1013     settings.setValue("PresetsFile", filename);
1014     settings.setValue("Presets", "File");
1015 
1016     QFile presetsTreeFile(filename);
1017     QString error;
1018     presetsTreeFile.open(QIODevice::ReadOnly);
1019     _presets.setContent(&presetsTreeFile, false, &error);
1020     presetsTreeFile.close();
1021     setPresets(_presets.elementsByTagName("document").at(0).toElement());
1022   } else {
1023     _builtInPresetsAction->setChecked(true);
1024   }
1025 }
1026 
1027 void MainWindow::onUseBuiltinPresets(bool on)
1028 {
1029   if (on) {
1030     QFile presetsTreeFile(":/presets.xml");
1031     QString error;
1032     presetsTreeFile.open(QIODevice::ReadOnly);
1033     _presets.setContent(&presetsTreeFile, false, &error);
1034     presetsTreeFile.close();
1035     setPresets(_presets.elementsByTagName("document").at(0).toElement());
1036     QSettings().setValue("Presets", "Built-in");
1037   }
1038 }
1039 
1040 void MainWindow::onReloadPresets()
1041 {
1042   if (_builtInPresetsAction->isChecked()) {
1043     onUseBuiltinPresets(true);
1044     return;
1045   }
1046   QSettings settings;
1047   QString filename = settings.value("PresetsFile").toString();
1048   setPresetsFile(filename);
1049 }
1050 
1051 void MainWindow::savePresetsFile()
1052 {
1053   QString filename = QFileDialog::getSaveFileName(this, "Save presets file", ".", "Preset files (*.xml)");
1054   if (!filename.isEmpty()) {
1055     QFile presetsFile(filename);
1056     presetsFile.open(QIODevice::WriteOnly);
1057     presetsFile.write(_presets.toByteArray());
1058     presetsFile.close();
1059   }
1060 }
1061 
1062 void MainWindow::onPreviewModeChanged(int index)
1063 {
1064   int mode = _cbPreviewMode->itemData(index).toInt();
1065   if (_filterThread)
1066     _filterThread->setPreviewMode(static_cast<FilterThread::PreviewMode>(mode));
1067 }
1068 
1069 void MainWindow::onRightPanel(bool on)
1070 {
1071   if (on && !_rightPanel->isVisible()) {
1072     _rightPanel->show();
1073     QSettings().setValue("showRightPanel", true);
1074     return;
1075   }
1076   if (!on && _rightPanel->isVisible()) {
1077     _rightPanel->hide();
1078     QSettings().setValue("showRightPanel", false);
1079     return;
1080   }
1081 }
1082 
1083 void MainWindow::updateCameraResolutionCombo()
1084 {
1085   disconnect(_comboCamResolution, SIGNAL(currentIndexChanged(int)), this, 0);
1086   int index = _comboWebcam->currentIndex();
1087   _comboCamResolution->clear();
1088   const QList<QSize> & resolutions = WebcamSource::webcamResolutions(index);
1089   QList<QSize>::const_iterator it = resolutions.begin();
1090   while (it != resolutions.end()) {
1091     _comboCamResolution->addItem(QString("%1 x %2").arg(it->width()).arg(it->height()), *it);
1092     ++it;
1093   }
1094   const int defaultResIndex = index >= 0 ? _cameraDefaultResolutionsIndexes[index] : -1;
1095   if (defaultResIndex >= 0) {
1096     _comboCamResolution->setCurrentIndex(defaultResIndex);
1097   }
1098   connect(_comboCamResolution, SIGNAL(currentIndexChanged(int)), this, SLOT(onWebcamResolutionComboChanged(int)));
1099 }
1100 
1101 TreeWidgetPresetItem * MainWindow::findPresetItem(QTreeWidget * tree, const QString & folder, const QString & name)
1102 {
1103   if (folder.isEmpty()) {
1104     QTreeWidgetItemIterator it(tree);
1105     while (*it) {
1106       if ((*it)->text(0) == name) {
1107         return dynamic_cast<TreeWidgetPresetItem *>(*it);
1108       }
1109       ++it;
1110     }
1111   } else {
1112     QTreeWidgetItemIterator it(tree);
1113     while (*it) {
1114       if ((*it)->text(0) == folder) {
1115         int count = (*it)->childCount();
1116         for (int i = 0; i < count; ++i) {
1117           QTreeWidgetItem * item = (*it)->child(i);
1118           if (item->text(0) == name) {
1119             return dynamic_cast<TreeWidgetPresetItem *>(item);
1120           }
1121         }
1122       }
1123       ++it;
1124     }
1125   }
1126   return 0;
1127 }
1128 
1129 TreeWidgetPresetItem * MainWindow::findPresetItem(QTreeWidget * tree, const QStringList & path)
1130 {
1131   switch (path.size()) {
1132   case 1:
1133     return findPresetItem(tree, QString(), path[0]);
1134     break;
1135   case 2:
1136     return findPresetItem(tree, path[0], path[1]);
1137     break;
1138   default:
1139     return 0;
1140   }
1141 }
1142 
1143 QString MainWindow::faveUniqueName(const QString & name)
1144 {
1145   int count = _cbFaves->count();
1146   int n = 1;
1147   for (int i = 0; i < count; ++i) {
1148     QString s = _cbFaves->itemText(i);
1149     s.replace(QRegExp(" \\(\\d*\\)"), "");
1150     if (s == name) {
1151       ++n;
1152     }
1153   }
1154   if (n > 1) {
1155     return QString("%1 (%2)").arg(name).arg(n);
1156   } else {
1157     return name;
1158   }
1159 }
1160 
1161 void MainWindow::onRefreshCameraResolutions()
1162 {
1163   WebcamSource::clearSavedSettings();
1164   onDetectCameras();
1165 }
1166 
1167 void MainWindow::onDetectCameras()
1168 {
1169   if (_source == Webcam && _filterThread) {
1170     stop();
1171     _webcam.stop();
1172   }
1173   centralWidget()->setEnabled(false);
1174   statusBar()->showMessage("Updating camera resolutions list...");
1175   menuBar()->setEnabled(false);
1176   QList<int> camList = WebcamSource::getWebcamList();
1177   int firstUnused = WebcamSource::getFirstUnusedWebcam();
1178   WebcamSource::retrieveWebcamResolutions(camList, 0, statusBar());
1179   initGUIFromCameraList(camList, firstUnused);
1180   statusBar()->showMessage(QString());
1181   centralWidget()->setEnabled(true);
1182   menuBar()->setEnabled(true);
1183 }
1184 
1185 void MainWindow::initGUIFromCameraList(const QList<int> & camList, int firstUnused)
1186 {
1187   disconnect(_comboWebcam, SIGNAL(currentIndexChanged(int)), this, 0);
1188   disconnect(_comboSource, SIGNAL(currentIndexChanged(int)), this, 0);
1189 
1190   QSettings settings;
1191   _comboSource->clear();
1192   _comboWebcam->clear();
1193 
1194   if (camList.size() == 0) {
1195     _tabParams->setCurrentIndex(0);
1196     _comboSource->addItem("Image", QVariant(StillImage));
1197     _comboSource->addItem("Video file", QVariant(Video));
1198 #if QT_VERSION >= 0x040600
1199     _comboSource->setItemIcon(0, QIcon::fromTheme("image-x-generic"));
1200     _comboSource->setItemIcon(1, QIcon::fromTheme("video-x-generic"));
1201 #endif
1202     if (_source == Webcam) {
1203       _source = StillImage;
1204       _currentSource = &_stillImage;
1205     }
1206   } else {
1207     _tabParams->setCurrentIndex(0);
1208     _comboSource->addItem("Webcam", QVariant(Webcam));
1209     _comboSource->addItem("Image", QVariant(StillImage));
1210     _comboSource->addItem("Video file", QVariant(Video));
1211 #if QT_VERSION >= 0x040600
1212     _comboSource->setItemIcon(0, QIcon::fromTheme("camera-web"));
1213     _comboSource->setItemIcon(1, QIcon::fromTheme("image-x-generic"));
1214     _comboSource->setItemIcon(2, QIcon::fromTheme("video-x-generic"));
1215 #endif
1216     _comboWebcam->setEnabled(camList.size() > 1);
1217     _cameraDefaultResolutionsIndexes.clear();
1218     for (int iCam = 0; iCam < camList.size(); ++iCam) {
1219       if (WebcamSource::webcamResolutions(iCam).isEmpty()) {
1220         _cameraDefaultResolutionsIndexes.push_back(-1);
1221         continue;
1222       }
1223       _comboWebcam->addItem(QString("Webcam %1").arg(camList[iCam]), QVariant(camList[iCam]));
1224       QSize size = settings.value(QString("WebcamSource/DefaultResolutionCam%1").arg(iCam), QSize()).toSize();
1225       if (size.isValid() && WebcamSource::webcamResolutions(iCam).contains(size)) {
1226         _cameraDefaultResolutionsIndexes.push_back(WebcamSource::webcamResolutions(iCam).indexOf(size));
1227       } else {
1228         _cameraDefaultResolutionsIndexes.push_back(WebcamSource::webcamResolutions(iCam).size() - 1);
1229       }
1230     }
1231     _comboWebcam->setCurrentIndex((firstUnused == -1) ? 0 : firstUnused);
1232     connect(_comboWebcam, SIGNAL(currentIndexChanged(int)), this, SLOT(onWebcamComboChanged(int)));
1233     onWebcamComboChanged(0);
1234   }
1235   connect(_comboSource, SIGNAL(currentIndexChanged(int)), this, SLOT(onComboSourceChanged(int)));
1236   onComboSourceChanged(0);
1237 }
1238 
1239 void MainWindow::onOutputWindow(bool on)
1240 {
1241   if (on) {
1242     if (!_outputWindow) {
1243       _outputWindow = new OutputWindow(this);
1244       connect(_outputWindow, SIGNAL(aboutToClose()), this, SLOT(onOutputWindowClosing()));
1245       connect(_outputWindow->imageView(), SIGNAL(keypointPositionsChanged()), this, SLOT(onOutputWindowKeypointsEvent()));
1246       connect(_outputWindow->imageView(), SIGNAL(resized(QSize)), this, SLOT(outputWindowImageViewResized(QSize)));
1247     }
1248     if (!_outputWindow->isVisible()) {
1249       bool running = _filterThread && _filterThread->isRunning();
1250       stop();
1251       _outputWindow->show();
1252       if (running) {
1253         play();
1254       }
1255     }
1256   }
1257   if (!on && _outputWindow && _outputWindow->isVisible()) {
1258     _outputWindow->onShowFullscreen(false);
1259     _outputWindow->close();
1260     imageViewResized(_imageView->size());
1261   }
1262 }
1263 
1264 void MainWindow::onOutputWindowClosing()
1265 {
1266   _outputWindowAction->setChecked(false);
1267 }
1268 
1269 void MainWindow::onAddFave()
1270 {
1271   TreeWidgetPresetItem * item = dynamic_cast<TreeWidgetPresetItem *>(_treeGPresets->currentItem());
1272   QStringList faveData;
1273   if (item && (!item->node().isNull())) {
1274     // "Display Name", "Folder", "Filter Name", "Parameter0", "Parameter1", "Parameter3", etc.
1275     faveData.append(faveUniqueName(item->text(0)));
1276     QTreeWidgetItem * parent = item->parent();
1277     if (parent) {
1278       faveData.append(parent->text(0));
1279     } else {
1280       faveData.append("");
1281     }
1282     faveData.append(item->text(0));
1283     faveData.append(_commandParamsWidget->valueStringList());
1284     _cbFaves->addItem(faveData[0], faveData);
1285     _cbFaves->setCurrentIndex(_cbFaves->count() - 1);
1286     _cbFaves->setEnabled(true);
1287     _fullScreenWidget->cbFaves()->setEnabled(true);
1288     _cbFaves->model()->sort(0, Qt::AscendingOrder);
1289     _tbRemoveFave->setEnabled(true);
1290     _tbRenameFave->setEnabled(true);
1291   }
1292 }
1293 
1294 void MainWindow::onRemoveFave()
1295 {
1296   _cbFaves->removeItem(_cbFaves->currentIndex());
1297   _cbFaves->setEnabled(_cbFaves->count());
1298   _fullScreenWidget->cbFaves()->setEnabled(_cbFaves->count());
1299   _tbRemoveFave->setEnabled(_cbFaves->count());
1300   _tbRenameFave->setEnabled(_cbFaves->count());
1301 }
1302 
1303 void MainWindow::onFaveSelected(int index)
1304 {
1305   _tbRemoveFave->setEnabled(true);
1306   _tbRenameFave->setEnabled(true);
1307   QStringList list = _cbFaves->itemData(index).toStringList();
1308   QString folder = list[1];
1309   QString name = list[2];
1310   TreeWidgetPresetItem * item = findPresetItem(_treeGPresets, folder, name);
1311   if (item) {
1312     list.pop_front();
1313     list.pop_front();
1314     list.pop_front();
1315     _treeGPresets->setCurrentItem(item);
1316     QTreeWidget * tree = _fullScreenWidget->treeWidget();
1317     TreeWidgetPresetItem * itemFS = findPresetItem(tree, folder, name);
1318     tree->setCurrentItem(itemFS);
1319     presetClicked(item, 0);
1320     _commandParamsWidget->setValues(list);
1321   }
1322 }
1323 
1324 void MainWindow::onRenameFave()
1325 {
1326   int index = _cbFaves->currentIndex();
1327   QStringList list = CURRENTDATA(_cbFaves).toStringList();
1328   QString newName = QInputDialog::getText(this, "Rename a fave", "Enter a new name", QLineEdit::Normal, list[0], 0);
1329   if (!newName.isNull()) {
1330     _cbFaves->setItemText(index, newName);
1331     list[0] = newName;
1332     _cbFaves->setItemData(index, list);
1333   }
1334 }
1335 
1336 void MainWindow::closeEvent(QCloseEvent * event)
1337 {
1338   if (_outputWindow && _outputWindow->isVisible()) {
1339     _outputWindow->close();
1340   }
1341   if (_fullScreenWidget && _displayMode == FullScreen) {
1342     toggleFullScreenMode();
1343   }
1344   event->accept();
1345 }
1346 
1347 void MainWindow::onImageViewKeypointsEvent()
1348 {
1349   _commandParamsWidget->setKeypoints(_imageView->keypoints(), true);
1350 }
1351 
1352 void MainWindow::onOutputWindowKeypointsEvent()
1353 {
1354   _commandParamsWidget->setKeypoints(_outputWindow->imageView()->keypoints(), true);
1355 }
1356 
1357 void MainWindow::onFullScreenKeypointsEvent()
1358 {
1359   _fullScreenWidget->commandParamsWidget()->setKeypoints(_fullScreenWidget->imageView()->keypoints(), true);
1360 }
1361