//------------------------------------------------------------------------
//  Copyright 2008-2009 (c) Jeff Brown <spadix@users.sourceforge.net>
//
//  This file is part of the ZBar Bar Code Reader.
//
//  The ZBar Bar Code Reader is free software; you can redistribute it
//  and/or modify it under the terms of the GNU Lesser Public License as
//  published by the Free Software Foundation; either version 2.1 of
//  the License, or (at your option) any later version.
//
//  The ZBar Bar Code Reader is distributed in the hope that it will be
//  useful, but WITHOUT ANY WARRANTY; without even the implied warranty
//  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser Public License for more details.
//
//  You should have received a copy of the GNU Lesser Public License
//  along with the ZBar Bar Code Reader; if not, write to the Free
//  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
//  Boston, MA  02110-1301  USA
//
//  http://sourceforge.net/projects/zbar
//------------------------------------------------------------------------

#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCommandLineParser>
#include <QFileDialog>
#include <QImage>
#include <QLayout>
#include <QPushButton>
#include <QTextEdit>
#include <QWidget>
#include <QtGlobal>
#include <config.h>
#include <zbar.h>
#include <zbar/QZBar.h>

#define TEST_IMAGE_FORMATS                                                  \
    "Image Files (*.png *.jpg *.jpeg *.bmp *.gif *.ppm *.pgm *.pbm *.tiff " \
    "*.xpm *.xbm)"

#define SYM_GROUP   "Symbology"
#define CAM_GROUP   "Camera"
#define DBUS_NAME   "D-Bus"
#define OPTION_BAR  "option_bar.enable"
#define CONTROL_BAR "control_bar.enable"

extern "C" {
int scan_video(void *add_device, void *userdata, const char *default_device);
}

struct configs_s {
    QString name;
    zbar::zbar_symbol_type_t sym;
};

static const struct configs_s configs[] = {
    { "Composite codes", zbar::ZBAR_COMPOSITE },
    { "Image Scanner", zbar::ZBAR_PARTIAL },
#if ENABLE_CODABAR == 1
    { "Codabar", zbar::ZBAR_CODABAR },
#endif
#if ENABLE_CODE128 == 1
    { "Code-128", zbar::ZBAR_CODE128 },
#endif
#if ENABLE_I25 == 1
    { "Code 2 of 5 interlaced", zbar::ZBAR_I25 },
#endif
#if ENABLE_CODE39 == 1
    { "Code-39", zbar::ZBAR_CODE39 },
#endif
#if ENABLE_CODE93 == 1
    { "Code-93", zbar::ZBAR_CODE93 },
#endif
#if ENABLE_DATABAR == 1
    { "DataBar", zbar::ZBAR_DATABAR },
    { "DataBar expanded", zbar::ZBAR_DATABAR_EXP },
#endif
#if ENABLE_EAN == 1
    { "EAN-2", zbar::ZBAR_EAN2 },
    { "EAN-5", zbar::ZBAR_EAN5 },
    { "EAN-8", zbar::ZBAR_EAN8 },
    { "EAN-13", zbar::ZBAR_EAN13 },
    { "ISBN-10", zbar::ZBAR_ISBN10 },
    { "ISBN-13", zbar::ZBAR_ISBN13 },
    { "UPC-A", zbar::ZBAR_UPCA },
    { "UPC-E", zbar::ZBAR_UPCE },
#endif
#if ENABLE_PDF417 == 1
    { "PDF417", zbar::ZBAR_PDF417 },
#endif
#if ENABLE_QRCODE == 1
    { "QR code", zbar::ZBAR_QRCODE },
#endif
#if ENABLE_SQCODE == 1
    { "SQ code", zbar::ZBAR_SQCODE },
#endif
};

#define CONFIGS_SIZE (sizeof(configs) / sizeof(*configs))

struct settings_s {
    QString name;
    zbar::zbar_config_t ctrl;
    bool is_bool;
};

static const struct settings_s settings[] = {
    { "x-density", zbar::ZBAR_CFG_Y_DENSITY, false },
    { "y-density", zbar::ZBAR_CFG_Y_DENSITY, false },
    { "min-length", zbar::ZBAR_CFG_MIN_LEN, false },
    { "max-length", zbar::ZBAR_CFG_MAX_LEN, false },
    { "uncertainty", zbar::ZBAR_CFG_UNCERTAINTY, false },
    { "ascii", zbar::ZBAR_CFG_ASCII, true },
    { "binary", zbar::ZBAR_CFG_BINARY, true },
    { "add-check", zbar::ZBAR_CFG_ADD_CHECK, true },
    { "emit-check", zbar::ZBAR_CFG_EMIT_CHECK, true },
    { "position", zbar::ZBAR_CFG_POSITION, true },
    { "test-inverted", zbar::ZBAR_CFG_TEST_INVERTED, true },
};
#define SETTINGS_SIZE (sizeof(settings) / sizeof(*settings))

// Represents an integer control

class IntegerControl : public QSpinBox
{
    Q_OBJECT

private:
    char *name;
    zbar::QZBar *zbar;

private slots:
    void updateControl(int value);

public:
    IntegerControl(QGroupBox *parent, zbar::QZBar *_zbar, char *_name, int min,
		   int max, int def, int step)
	: QSpinBox(parent)
    {
	int val;

	zbar = _zbar;
	name = _name;

	setRange(min, max);
	setSingleStep(step);
	if (!zbar->get_control(name, &val))
	    setValue(val);
	else
	    setValue(def);

	connect(this, SIGNAL(valueChanged(int)), this,
		SLOT(updateControl(int)));
    }
};

void IntegerControl::updateControl(int value)
{
    zbar->set_control(name, value);
}

// Represents a menu control
class MenuControl : public QComboBox
{
    Q_OBJECT

private:
    char *name;
    zbar::QZBar *zbar;
    QVector<QPair<int, QString> > vector;

private slots:
    void updateControl(int value);

public:
    MenuControl(QGroupBox *parent, zbar::QZBar *_zbar, char *_name,
		QVector<QPair<int, QString> > _vector)
	: QComboBox(parent)
    {
	int val;

	zbar   = _zbar;
	name   = _name;
	vector = _vector;

	if (zbar->get_control(name, &val))
	    val = 0;
	for (int i = 0; i < vector.size(); ++i) {
	    QPair<int, QString> pair = vector.at(i);
	    addItem(pair.second, pair.first);

	    if (val == pair.first)
		setCurrentIndex(i);
	}
	connect(this, SIGNAL(currentIndexChanged(int)), this,
		SLOT(updateControl(int)));
    }
};

void MenuControl::updateControl(int index)
{
    zbar->set_control(name, vector.at(index).first);
}

class IntegerSetting : public QSpinBox
{
    Q_OBJECT

public:
    QString name;

    IntegerSetting(QString _name, int val = 0) : name(_name)
    {
	setValue(val);
    }
};

class SettingsDialog : public QDialog
{
    Q_OBJECT

private:
    QVector<int> val;
    zbar::QZBar *zbar;
    zbar::zbar_symbol_type_t sym;

private slots:

    void accept()
    {
	for (unsigned i = 0; i < SETTINGS_SIZE; i++)
	    zbar->set_config(sym, settings[i].ctrl, val[i]);
	QDialog::accept();
    };
    void reject()
    {
	QDialog::reject();
    };
    void clicked()
    {
	QCheckBox *button = qobject_cast<QCheckBox *>(sender());
	if (!button)
	    return;

	QString name = button->text();

	for (unsigned i = 0; i < SETTINGS_SIZE; i++) {
	    if (settings[i].name == name) {
		val[i] = button->isChecked();
		return;
	    }
	}
	// ERROR!
    };
    void update(int value)
    {
	IntegerSetting *setting = qobject_cast<IntegerSetting *>(sender());
	if (!setting)
	    return;

	for (unsigned i = 0; i < SETTINGS_SIZE; i++) {
	    if (settings[i].name == setting->name) {
		val[i] = value;
		return;
	    }
	}
	// ERROR!
    };

public:
    SettingsDialog(zbar::QZBar *_zbar, QString &name,
		   zbar::zbar_symbol_type_t _sym)
	: zbar(_zbar), sym(_sym)
    {
	val = QVector<int>(SETTINGS_SIZE);

	QGridLayout *layout = new QGridLayout(this);

	this->setWindowTitle(name);

	for (unsigned i = 0; i < SETTINGS_SIZE; i++) {
	    int value = 0;

	    if (zbar->get_config(sym, settings[i].ctrl, value))
		continue;
	    val[i] = value;

	    if (settings[i].is_bool) {
		QCheckBox *button = new QCheckBox(settings[i].name, this);

		button->setChecked(value);

		layout->addWidget(button, i, 0, 1, 2,
				  Qt::AlignTop | Qt::AlignLeft);
		connect(button, SIGNAL(clicked()), this, SLOT(clicked()));
	    } else {
		QLabel *label = new QLabel(settings[i].name);

		layout->addWidget(label, i, 0, 1, 1,
				  Qt::AlignTop | Qt::AlignLeft);
		IntegerSetting *spin =
		    new IntegerSetting(settings[i].name, value);
		layout->addWidget(spin, i, 1, 1, 1,
				  Qt::AlignTop | Qt::AlignLeft);
		connect(spin, SIGNAL(valueChanged(int)), this,
			SLOT(update(int)));
	    }
	}
	QDialogButtonBox *buttonBox = new QDialogButtonBox(
	    QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
	connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
	connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
	layout->addWidget(buttonBox);
    }
};

class SettingsButton : public QPushButton
{
    Q_OBJECT

private:
    QString name;
    zbar::QZBar *zbar;
    zbar::zbar_symbol_type_t sym;

public:
    SettingsButton(zbar::QZBar *_zbar, const QIcon &_icon, QString _name,
		   zbar::zbar_symbol_type_t _sym)
	: QPushButton(_icon, ""), name(_name), zbar(_zbar), sym(_sym)
    {
	int size = font().pointSize();

	if (size < 0)
	    size = font().pixelSize();

	if (size > 0)
	    setIconSize(QSize(size, size));
    };

public Q_SLOTS:
    void button_clicked()
    {
	SettingsButton *button = qobject_cast<SettingsButton *>(sender());
	if (!button)
	    return;

	QString name = button->name;

	SettingsDialog *dialog = new SettingsDialog(zbar, name, sym);
	dialog->setModal(true);
	dialog->show();
    }
};

struct CamRes {
    unsigned width;
    unsigned height;
    float max_fps;
};

class ZbarcamQZBar : public QWidget
{
    Q_OBJECT

protected:
    static void add_device(QComboBox *list, const char *device)
    {
	list->addItem(QString(device));
    }

public Q_SLOTS:
    void turn_show_options()
    {
	QPushButton *button = qobject_cast<QPushButton *>(sender());
	if (!button)
	    return;

	show_options = !show_options;
	if (show_options) {
	    button->setText("Hide Options");
	    optionsGroup->show();
	} else {
	    button->setText("Show Options");
	    optionsGroup->hide();
	}
    }

    void turn_show_controls()
    {
	QPushButton *button = qobject_cast<QPushButton *>(sender());
	if (!button)
	    return;

	show_controls = !show_controls;
	if (show_controls) {
	    button->setText("Hide Controls");
	    controlGroup->show();
	} else {
	    button->setText("Show Controls");
	    controlGroup->hide();
	}
    }

public:
    ~ZbarcamQZBar()
    {
	saveSettings();
    }
    ZbarcamQZBar(const QStringList *names, int verbose = 0) : resolutions(NULL)
    {
	// drop-down list of video devices
	QComboBox *videoList = new QComboBox;

	// toggle button to disable/enable video
	statusButton = new QPushButton;

	QStyle *style	 = QApplication::style();
	QIcon statusIcon = style->standardIcon(QStyle::SP_DialogNoButton);
	QIcon yesIcon	 = style->standardIcon(QStyle::SP_DialogYesButton);
	statusIcon.addPixmap(yesIcon.pixmap(QSize(128, 128), QIcon::Normal,
					    QIcon::On),
			     QIcon::Normal, QIcon::On);

	statusButton->setIcon(statusIcon);
	statusButton->setText("&Enable");
	statusButton->setCheckable(true);
	statusButton->setEnabled(false);
	statusButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

	// command button to open image files for scanning
	QPushButton *openButton = new QPushButton("&Open");
	QIcon openIcon = style->standardIcon(QStyle::SP_DialogOpenButton);
	openButton->setIcon(openIcon);
	openButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

	// collect video list and buttons horizontally
	ZBarMenu = new QHBoxLayout;
	ZBarMenu->setAlignment(Qt::AlignLeft);
	ZBarMenu->addWidget(videoList, 5);
	ZBarMenu->addWidget(statusButton, 1);
	ZBarMenu->addWidget(openButton, 1);

	// video barcode scanner
	zbar = new zbar::QZBar(NULL, verbose);
	zbar->setAcceptDrops(true);

	// text box for results
	QTextEdit *results = new QTextEdit;
	results->setReadOnly(true);

	QGridLayout *grid = new QGridLayout;
	grid->addLayout(ZBarMenu, 0, 0, 1, -1);
	grid->addWidget(zbar, 1, 0, 1, 1);
	grid->addWidget(results, 2, 0, 1, 1);

	// Group box where controls will be added
	optionsGroup		      = new QGroupBox(tr("Options"), this);
	QGridLayout *optionsBoxLayout = new QGridLayout(optionsGroup);
	optionsGroup->setAlignment(Qt::AlignHCenter);
	optionsBoxLayout->setContentsMargins(0, 0, 16, 0);
	grid->addWidget(optionsGroup, 1, 1, -1, 1, Qt::AlignTop);

	controlGroup	 = new QGroupBox(this);
	controlBoxLayout = new QGridLayout(controlGroup);
	controlBoxLayout->setContentsMargins(0, 0, 0, 0);
	grid->addWidget(controlGroup, 1, 2, -1, 1, Qt::AlignTop);

	loadSettings();
	zbar->request_size(curWidth, curHeight, false);

	int pos = 0;

#ifdef HAVE_DBUS
	QCheckBox *button = new QCheckBox(DBUS_NAME, this);
	button->setChecked(dbus_enabled);
	optionsBoxLayout->addWidget(button, ++pos, 0, 1, 1,
				    Qt::AlignTop | Qt::AlignLeft);
	connect(button, SIGNAL(clicked()), this, SLOT(code_clicked()));
	zbar->request_dbus(0);
#endif

	for (unsigned i = 0; i < CONFIGS_SIZE; i++) {
	    int val = 0;

	    if (configs[i].sym == zbar::ZBAR_PARTIAL) {
		QLabel *label = new QLabel(configs[i].name, this);
		optionsBoxLayout->addWidget(label, ++pos, 0, 1, 1,
					    Qt::AlignTop | Qt::AlignLeft);
	    } else {
		QCheckBox *button = new QCheckBox(configs[i].name, this);

		zbar->get_config(configs[i].sym, zbar::ZBAR_CFG_ENABLE, val);

		button->setChecked(val);
		optionsBoxLayout->addWidget(button, ++pos, 0, 1, 1,
					    Qt::AlignTop | Qt::AlignLeft);
		connect(button, SIGNAL(clicked()), this, SLOT(code_clicked()));
	    }

	    /* Composite doesn't have configuration */
	    if (configs[i].sym == zbar::ZBAR_COMPOSITE)
		continue;

	    QIcon icon = QIcon::fromTheme(QLatin1String("configure-toolbars"));
	    SettingsButton *settings =
		new SettingsButton(zbar, icon, configs[i].name, configs[i].sym);
	    optionsBoxLayout->addWidget(settings, pos, 1, 1, 1,
					Qt::AlignTop | Qt::AlignLeft);

	    connect(settings, &SettingsButton::clicked, settings,
		    &SettingsButton::button_clicked);
	}

	// Allow showing/hiding options/controls menus
	QPushButton *showOptionsButton, *showControlsButton;

	if (show_options) {
	    showOptionsButton = new QPushButton("Hide Options");
	    optionsGroup->show();
	} else {
	    showOptionsButton = new QPushButton("Show Options");
	    optionsGroup->hide();
	}
	showOptionsButton->setSizePolicy(QSizePolicy::Fixed,
					 QSizePolicy::Fixed);
	ZBarMenu->addWidget(showOptionsButton);
	connect(showOptionsButton, SIGNAL(clicked()), this,
		SLOT(turn_show_options()));

	if (show_controls) {
	    showControlsButton = new QPushButton("Hide Controls");
	    controlGroup->show();
	} else {
	    showControlsButton = new QPushButton("Show Controls");
	    controlGroup->hide();
	}
	showControlsButton->setSizePolicy(QSizePolicy::Fixed,
					  QSizePolicy::Fixed);
	ZBarMenu->addWidget(showControlsButton);
	connect(showControlsButton, SIGNAL(clicked()), this,
		SLOT(turn_show_controls()));

	if (!geometry.isEmpty())
	    restoreGeometry(geometry);

	setLayout(grid);

	videoList->addItem("");

	int active = 0;
	for (int i = 0; i < names->size(); i++)
	    active += scan_video((void *)add_device, videoList,
				 names->at(i).toUtf8());

	if (names->isEmpty())
	    active += scan_video((void *)add_device, videoList, NULL);

	// directly connect combo box change signal to scanner video open
	connect(videoList, SIGNAL(currentIndexChanged(const QString &)), zbar,
		SLOT(setVideoDevice(const QString &)));

	// directly connect status button state to video enabled state
	connect(statusButton, SIGNAL(toggled(bool)), zbar,
		SLOT(setVideoEnabled(bool)));

	// also update status button state when video is opened/closed
	connect(zbar, SIGNAL(videoOpened(bool)), this, SLOT(setEnabled(bool)));

	// prompt for image file to scan when openButton is clicked
	connect(openButton, SIGNAL(clicked()), SLOT(openImage()));

	// directly connect video scanner decode result to display in text box
	connect(zbar, SIGNAL(decodedText(const QString &)), results,
		SLOT(append(const QString &)));

	if (active >= 0)
	    videoList->setCurrentIndex(active);
    }

public Q_SLOTS:
    void openImage()
    {
	file = QFileDialog::getOpenFileName(this, "Open Image", file,
					    TEST_IMAGE_FORMATS);
	if (!file.isEmpty())
	    zbar->scanImage(QImage(file));
    }

    void control_clicked()
    {
	QCheckBox *button = qobject_cast<QCheckBox *>(sender());
	if (!button)
	    return;

	QString name = button->text();
	bool val     = button->isChecked();

	zbar->set_control(name.toUtf8().data(), val);
    }

    void code_clicked()
    {
	QCheckBox *button = qobject_cast<QCheckBox *>(sender());
	if (!button)
	    return;

	QString name = button->text();
	bool val     = button->isChecked();

	if (name == DBUS_NAME) {
	    zbar->request_dbus(val);
	    dbus_enabled = val;
	    return;
	}

	for (unsigned i = 0; i < CONFIGS_SIZE; i++) {
	    if (configs[i].name == name) {
		zbar->set_config(configs[i].sym, zbar::ZBAR_CFG_ENABLE, val);
		return;
	    }
	}
    }

    void clearLayout(QLayout *layout)
    {
	QLayoutItem *item;
	while ((item = layout->takeAt(0))) {
	    if (item->layout()) {
		clearLayout(item->layout());
		delete item->layout();
	    }
	    if (item->widget()) {
		delete item->widget();
	    }
	    delete item;
	}
    }

    void setVideoResolution(int index)
    {
	struct CamRes *cur_res;

	if (index < 0 || res.isEmpty())
	    return;

	cur_res = &res[index];

	unsigned width	= zbar->videoWidth();
	unsigned height = zbar->videoHeight();

	if (width == cur_res->width && height == cur_res->height)
	    return;

	zbar->request_size(cur_res->width, cur_res->height);

	curWidth  = cur_res->width;
	curHeight = cur_res->height;
    }

    void setEnabled(bool videoEnabled)
    {
	zbar->setVideoEnabled(videoEnabled);

	// Update the status button
	statusButton->setEnabled(videoEnabled);
	statusButton->setChecked(videoEnabled);

	// Delete items before creating a new set of controls
	clearLayout(controlBoxLayout);

	if (!videoEnabled)
	    return;

	// get_controls
	loadSettings(false);

	// FIXME: clear a previous resolutions box

	bool isNewResolutions = false;

	if (!resolutions) {
	    resolutions = new QComboBox;
	    resolutions->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	    isNewResolutions = true;
	}

	resolutions->blockSignals(true);
	res.clear();
	resolutions->clear();

	for (int i = 0;; i++) {
	    QString new_res, fps;
	    struct CamRes cur_res;

	    if (!zbar->get_resolution(i, cur_res.width, cur_res.height,
				      cur_res.max_fps))
		break;

	    fps.setNum(cur_res.max_fps, 'f', 2);
	    new_res = QString("%1x%2 - %3 fps (max)")
			  .arg(cur_res.width)
			  .arg(cur_res.height)
			  .arg(fps);

	    resolutions->addItem(new_res);
	    res.append(cur_res);

	    if (curWidth == cur_res.width && curHeight == cur_res.height)
		resolutions->setCurrentIndex(i);
	}

	if (isNewResolutions) {
	    ZBarMenu->addWidget(resolutions);
	    connect(resolutions, SIGNAL(currentIndexChanged(int)), this,
		    SLOT(setVideoResolution(int)));
	}
	resolutions->blockSignals(false);

	// Restore saved resolution
	unsigned width	= zbar->videoWidth();
	unsigned height = zbar->videoHeight();

	if (width != curWidth || height != curHeight) {
	    for (int i = 0; i < res.size(); i++) {
		if (res[i].width == curWidth && res[i].height == curHeight) {
		    resolutions->setCurrentIndex(i);
		    break;
		}
	    }
	}

	int pos		 = 0;
	QString oldGroup = "";
	for (int i = 0;; i++) {
	    char *name, *group;
	    enum zbar::QZBar::ControlType type;
	    int min, max, def, step;

	    int ret = zbar->get_controls(i, &name, &group, &type, &min, &max,
					 &def, &step);
	    if (!ret)
		break;

	    QString newGroup =
		"<strong>" + QString::fromUtf8(group) + " Controls</strong>";

	    if (newGroup != oldGroup) {
		if (oldGroup != "")
		    controlBoxLayout->addItem(new QSpacerItem(0, 12), pos++, 2,
					      1, 2, Qt::AlignLeft);
		QLabel *label = new QLabel(newGroup);
		controlBoxLayout->addWidget(label, pos++, 0, 1, 2,
					    Qt::AlignTop | Qt::AlignHCenter);
		pos++;
		oldGroup = newGroup;
	    }

	    switch (type) {
	    case zbar::QZBar::Button:
	    case zbar::QZBar::Boolean: {
		bool val;

		QCheckBox *button = new QCheckBox(name, controlGroup);
		controlBoxLayout->addWidget(button, pos++, 0, 1, 2,
					    Qt::AlignLeft);

		if (!zbar->get_control(name, &val))
		    button->setChecked(val);
		else
		    button->setChecked(def);
		connect(button, SIGNAL(clicked()), this,
			SLOT(control_clicked()));
		break;
	    }
	    case zbar::QZBar::Integer: {
		IntegerControl *ctrl;

		QLabel *label = new QLabel(QString::fromUtf8(name));
		ctrl = new IntegerControl(controlGroup, zbar, name, min, max,
					  def, step);

		controlBoxLayout->addWidget(label, pos, 0, Qt::AlignLeft);
		controlBoxLayout->addWidget(ctrl, pos++, 1, Qt::AlignLeft);
		break;
	    }
	    case zbar::QZBar::Menu: {
		MenuControl *ctrl;

		QLabel *label = new QLabel(QString::fromUtf8(name));

		QVector<QPair<int, QString> > vector;
		vector = zbar->get_menu(i);
		ctrl   = new MenuControl(controlGroup, zbar, name, vector);

		controlBoxLayout->addWidget(label, pos, 0, Qt::AlignLeft);
		controlBoxLayout->addWidget(ctrl, pos++, 1, Qt::AlignLeft);
		break;
	    }
	    default:
		// Just ignore other types
		break;
	    }
	}
    }

private:
    QString file;
    zbar::QZBar *zbar;
    QHBoxLayout *ZBarMenu;
    QPushButton *statusButton;
    QGroupBox *controlGroup, *optionsGroup;
    QComboBox *resolutions;
    QGridLayout *controlBoxLayout;
    QSignalMapper *signalMapper;
    bool dbus_enabled, show_options, show_controls;
    QByteArray geometry;
    QVector<struct CamRes> res;
    unsigned curWidth, curHeight;

    void loadSettings(bool getRes = true)
    {
	QSettings qSettings(QCoreApplication::organizationName(),
			    QCoreApplication::applicationName());
	QString key;
	QVariant qVal;

	geometry = qSettings.value("geometry").toByteArray();

	key	     = OPTION_BAR;
	qVal	     = qSettings.value(key, true);
	show_options = qVal.toBool();

	key	      = CONTROL_BAR;
	qVal	      = qSettings.value(key, true);
	show_controls = qVal.toBool();

	if (getRes) {
	    qVal      = qSettings.value("width");
	    curWidth  = qVal.toUInt();
	    qVal      = qSettings.value("height");
	    curHeight = qVal.toUInt();
	}

#ifdef HAVE_DBUS
	key	     = DBUS_NAME ".enable";
	qVal	     = qSettings.value(key, false);
	dbus_enabled = qVal.toBool();
#endif

	qSettings.beginGroup(SYM_GROUP);

	for (unsigned i = 0; i < CONFIGS_SIZE; i++) {
	    int val = 0;
	    if (zbar->get_config(configs[i].sym, zbar::ZBAR_CFG_ENABLE, val))
		continue;
	    key = QString(configs[i].name) + QString(".enable");
	    key.replace(" ", "_");
	    qVal = qSettings.value(key, val);
	    zbar->set_config(configs[i].sym, zbar::ZBAR_CFG_ENABLE,
			     qVal.toInt());

	    if (configs[i].sym == zbar::ZBAR_COMPOSITE)
		continue;

	    for (unsigned j = 0; j < SETTINGS_SIZE; j++) {
		int val = 0;

		if (zbar->get_config(configs[i].sym, settings[j].ctrl, val))
		    continue;
		key = QString(configs[i].name) + QString(".") +
		      QString(settings[j].name);
		key.replace(" ", "_");

		qVal = qSettings.value(key, val);
		zbar->set_config(configs[i].sym, settings[j].ctrl,
				 qVal.toInt());
	    }
	}
	qSettings.endGroup();

	qSettings.beginGroup(CAM_GROUP);
	for (unsigned i = 0;; i++) {
	    char *name, *group;
	    enum zbar::QZBar::ControlType type;
	    int min, max, def, step, val;

	    int ret = zbar->get_controls(i, &name, &group, &type, &min, &max,
					 &def, &step);
	    if (!ret)
		break;

	    switch (type) {
	    case zbar::QZBar::Button:
	    case zbar::QZBar::Boolean:
	    case zbar::QZBar::Menu:
	    case zbar::QZBar::Integer: {
		key = QString::fromUtf8(name);

		if (zbar->get_control(name, &val))
		    continue;

		key.replace(QRegularExpression("[^\\w\\d]+"), "_");
		key.replace(QRegularExpression("_$"), "");

		qVal = qSettings.value(key, val);
		zbar->set_control(name, qVal.toInt());
		break;
	    }
	    default:
		// Just ignore other types
		break;
	    }
	}
	qSettings.endGroup();
    }

    void saveSettings()
    {
	QSettings qSettings(QCoreApplication::organizationName(),
			    QCoreApplication::applicationName());
	QString key;
	unsigned int i;

	qSettings.setValue("geometry", saveGeometry());

	key = OPTION_BAR;
	qSettings.setValue(key, show_options);

	key = CONTROL_BAR;
	qSettings.setValue(key, show_controls);

	curWidth  = zbar->videoWidth();
	curHeight = zbar->videoHeight();
	qSettings.setValue("width", curWidth);
	qSettings.setValue("height", curHeight);

#ifdef HAVE_DBUS
	// FIXME: track dbus enable-disable and store last state
	key = DBUS_NAME ".enable";
	qSettings.setValue(key, dbus_enabled);
#endif

	qSettings.beginGroup(SYM_GROUP);
	for (i = 0; i < CONFIGS_SIZE; i++) {
	    int val = 0;

	    if (zbar->get_config(configs[i].sym, zbar::ZBAR_CFG_ENABLE, val))
		continue;
	    key = QString(configs[i].name) + QString(".enable");
	    key.replace(" ", "_");
	    qSettings.setValue(key, val);

	    if (configs[i].sym == zbar::ZBAR_COMPOSITE)
		continue;

	    for (unsigned j = 0; j < SETTINGS_SIZE; j++) {
		int val = 0;

		if (zbar->get_config(configs[i].sym, settings[j].ctrl, val))
		    continue;
		key = QString(configs[i].name) + QString(".") +
		      QString(settings[j].name);
		key.replace(" ", "_");
		qSettings.setValue(key, val);
	    }
	}
	qSettings.endGroup();

	for (i = 0;; i++) {
	    char *name, *group;
	    enum zbar::QZBar::ControlType type;
	    int min, max, def, step, val;

	    int ret = zbar->get_controls(i, &name, &group, &type, &min, &max,
					 &def, &step);
	    if (!ret)
		break;

	    if (i == 0)
		qSettings.beginGroup(CAM_GROUP);

	    switch (type) {
	    case zbar::QZBar::Button:
	    case zbar::QZBar::Boolean:
	    case zbar::QZBar::Menu:
	    case zbar::QZBar::Integer: {
		key = QString::fromUtf8(name);

		if (zbar->get_control(name, &val))
		    continue;

		key.replace(QRegularExpression("[^\\w\\d]+"), "_");
		key.replace(QRegularExpression("_$"), "");
		qSettings.setValue(key, val);
		break;
	    }
	    default:
		// Just ignore other types
		break;
	    }
	}
	if (i > 0)
	    qSettings.endGroup();
    }
};

#include "moc_zbarcam_qt.h"

int main(int argc, char *argv[])
{
    int verbose = 0;
    QApplication app(argc, argv);
    app.setApplicationName("zbarcam_qt");
    app.setOrganizationName("LinuxTV");
    app.setOrganizationDomain("linuxtv.org");
    app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);

    QCommandLineParser parser;
    parser.setApplicationDescription("ZBar bar code reader Qt application");
    parser.addHelpOption();

    parser.addPositionalArgument("name", QObject::tr("device or file name"));

    QCommandLineOption debugOption(QStringList() << "d"
						 << "debug",
				   QObject::tr("Enable debug mode."));
    parser.addOption(debugOption);

    QCommandLineOption verboseOption(QStringList() << "v"
						   << "verbosity",
				     QObject::tr("Verbosity level."),
				     QObject::tr("value"));
    parser.addOption(verboseOption);

    parser.process(app);

    if (parser.isSet(verboseOption))
	verbose = parser.value(verboseOption).toInt();

    if (parser.isSet(debugOption))
	verbose = 127;

    const QStringList args = parser.positionalArguments();

    ZbarcamQZBar window(&args, verbose);
    window.show();
    return (app.exec());
}
