[libcamera-devel] [PATCH 03/11] qcam: Add GUI way to change control values

Utkarsh Tiwari utkarsh02t at gmail.com
Fri Jul 15 21:13:52 CEST 2022


Introduce GUI ability to change control values currently only for
boolean control types.

Each of the ControlFrame first identifies the type of control it is and
then accordingly constructs the widget that would help the user to
interact with it.

The current model of connecting the controls change info to the
MaindWindow is as follows.

            +-----------+
            |Main Window|
            +-----^-----+
                  |
                  |
          +-------+-------+
          |Settings Dialog|
          +--^----^------^+
             |    |      |
      +------+    |      +-----+
      |           |            |
+-----+---+  +----+----+  +----+----+
|Various  |  | Tabs    |  |   ...   |
++---+----+  +---+---+-+  ++--+--+--+
 |   |           |   |     |  |  |
 |   |           |   |     |  |  |
 |   |           |   |     |  |  |

Implement a controlLatch mechanism to update thec ontrolList. As the
Settings QDialog and ControlFrame processing the change in control lie
in the same thread there would be no race to access the ControlList.

This allows us to use to shared_ptr to the ControlList
to help us the same function from MaindWindow::queueRequest()
and the controlListChanged() signal.

We clear the controlList_ in MainWindow::queueRequest() and not in the
MainWindow::controlListLatch() because when in controlListLatch() we
need to clear it when we don't have script nor the alerted by the signal.
But what Can happen is that we have controlListLatch() is triggered by the
signal and then immediately called by the queueRequest() which would
clear the controlList and it would not end up getting set.

Signed-off-by: Utkarsh Tiwari <utkarsh02t at gmail.com>
---
 src/qcam/main_window.cpp            | 38 +++++++++++++--
 src/qcam/main_window.h              |  4 ++
 src/qcam/settings/control_frame.cpp | 75 +++++++++++++++++++++++++++--
 src/qcam/settings/control_frame.h   | 13 ++++-
 src/qcam/settings/controls_tab.cpp  | 19 +++++++-
 src/qcam/settings/controls_tab.h    | 10 ++++
 src/qcam/settings/settings_dialog.h |  5 ++
 7 files changed, 156 insertions(+), 8 deletions(-)

diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index f0916b8e..5d11ef98 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -98,7 +98,7 @@ private:
 
 MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
 	: saveRaw_(nullptr), options_(options), cm_(cm), allocator_(nullptr),
-	  isCapturing_(false), captureRaw_(false)
+	  isCapturing_(false), captureRaw_(false), controlList_(ControlList())
 {
 	int ret;
 
@@ -353,6 +353,8 @@ void MainWindow::openSettingsDialog()
 	}
 
 	settingsDialog_ = std::make_unique<SettingsDialog>(camera_, this);
+	connect(settingsDialog_.get(), &SettingsDialog::controlListChanged,
+		this, &MainWindow::controlListLatch);
 	settingsDialog_->show();
 }
 
@@ -891,10 +893,40 @@ void MainWindow::renderComplete(FrameBuffer *buffer)
 
 int MainWindow::queueRequest(Request *request)
 {
-	if (script_)
-		request->controls() = script_->frameControls(queueCount_);
+	/*
+	 * Call the controlListLatch with a nullptr to indicate that it
+	 * has been called by us and not by the signal.
+	 */
+
+	controlListLatch(nullptr);
+	request->controls() = controlList_;
+
+	/* Clear controlList_ to remove old controls that have been set.*/
+	if (controlList_.size())
+		controlList_.clear();
 
 	queueCount_++;
 
 	return camera_->queueRequest(request);
 }
+
+void MainWindow::controlListLatch(std::shared_ptr<const libcamera::ControlList> controlList)
+{
+	/*
+	 * If we have been given a non-null shared_ptr then it means we
+	 * have been alerted by the SettingsWindow::controlListChanged signal.
+	 */
+
+	if (controlList) {
+		controlList_ = *(controlList);
+
+		/* Shut down the capture script */
+		if (script_)
+			toggleScriptAction(true);
+
+		return;
+	}
+
+	if (script_)
+		controlList_ = script_->frameControls(queueCount_);
+}
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index e35f9029..05207023 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -71,6 +71,8 @@ private Q_SLOTS:
 
 	void renderComplete(libcamera::FrameBuffer *buffer);
 
+	void controlListLatch(std::shared_ptr<const libcamera::ControlList> controlList);
+
 private:
 	int createToolbars();
 
@@ -139,4 +141,6 @@ private:
 
 	std::vector<std::unique_ptr<libcamera::Request>> requests_;
 	std::unique_ptr<CaptureScript> script_;
+
+	libcamera::ControlList controlList_;
 };
diff --git a/src/qcam/settings/control_frame.cpp b/src/qcam/settings/control_frame.cpp
index 785e4040..e335d373 100644
--- a/src/qcam/settings/control_frame.cpp
+++ b/src/qcam/settings/control_frame.cpp
@@ -9,7 +9,9 @@
 
 #include <libcamera/controls.h>
 
+#include <QCheckBox>
 #include <QFrame>
+#include <QHBoxLayout>
 #include <QLabel>
 #include <QString>
 #include <QVBoxLayout>
@@ -27,11 +29,11 @@ ControlFrame::ControlFrame(const libcamera::ControlId *control,
 	frameVLayout->addWidget(new QLabel(QString::fromStdString(control_->name())));
 
 	/*
-	 * No need to pass parents to widgets,
-	 * as QVBoxLayout transfers ownership to
-	 * its parent widget.
+	 * No need to pass parents to widgets, as QVBoxLayout transfers
+	 * ownership to its parent widget.
 	 */
 	frameVLayout->addWidget(defaultValueLabel());
+	frameVLayout->addWidget(controlInteraction());
 
 	setFrameStyle(QFrame::StyledPanel);
 }
@@ -49,6 +51,73 @@ QLabel *ControlFrame::defaultValueLabel(QWidget *parent)
 	return defaultValLabel;
 }
 
+QWidget *ControlFrame::controlInteraction(QWidget *parent)
+{
+	QWidget *containerWidget = new QWidget(parent);
+
+	switch (control_->type()) {
+	case ControlTypeBool: {
+		QHBoxLayout *HCheckBoxLayout = new QHBoxLayout(containerWidget);
+
+		HCheckBoxLayout->addWidget(new QLabel("Enabled :"));
+
+		controlCheckBox_ = new QCheckBox;
+		/*
+		 * In the start we are not sure what is exactly the state of
+		 * the control. Do not assume. Set in partially checked state.
+		 */
+		controlCheckBox_->setCheckState(Qt::PartiallyChecked);
+
+		connect(controlCheckBox_, &QCheckBox::stateChanged,
+			this, &ControlFrame::notifyControlChange);
+
+		HCheckBoxLayout->addWidget(controlCheckBox_);
+
+		/* Align it with the name of the control. */
+		HCheckBoxLayout->setAlignment(Qt::AlignLeft);
+		HCheckBoxLayout->setMargin(0);
+		return containerWidget;
+	}
+	default:
+		return (new QLabel("Currently Unavailable"));
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * Qt Slots
+ */
+
+void ControlFrame::notifyControlChange()
+{
+	ControlValue controlValue = ControlValue();
+
+	switch (control_->type()) {
+	case ControlTypeBool:
+
+		/*
+		 * When clicked for the first time, the switch comes from a
+		 * partially checked state. Turn the triset off so we can have
+		 * only enabled and disabled states.
+		 */
+		controlCheckBox_->setTristate(false);
+		/*
+		 * When this function is invoked, the checkbox can only be in
+		 * the Checked or Unchecked State (after the first time).
+		 */
+		if (controlCheckBox_->checkState() == Qt::CheckState::Checked)
+			controlValue.set<bool>(true);
+		else
+			controlValue.set<bool>(false);
+
+		break;
+	default:
+		/* Nothing to emit so return */
+		return;
+	}
+
+	Q_EMIT controlChanged(control_, controlValue);
+}
+
 /* -----------------------------------------------------------------------------
  * Helpers
  */
diff --git a/src/qcam/settings/control_frame.h b/src/qcam/settings/control_frame.h
index 884feb6b..61005ea5 100644
--- a/src/qcam/settings/control_frame.h
+++ b/src/qcam/settings/control_frame.h
@@ -9,8 +9,9 @@
 
 #include <libcamera/controls.h>
 
-#include <QLabel>
+#include <QCheckBox>
 #include <QFrame>
+#include <QLabel>
 #include <QWidget>
 
 class ControlFrame : public QFrame
@@ -23,13 +24,23 @@ public:
 		     QWidget *parent);
 	~ControlFrame() = default;
 
+Q_SIGNALS:
+	void controlChanged(const libcamera::ControlId *controlId,
+			    const libcamera::ControlValue);
+
+private Q_SLOTS:
+	void notifyControlChange();
+
 private:
 	const libcamera::ControlId *control_;
 	const libcamera::ControlInfo &controlInfo_;
 
 	/* Widgets */
+	QWidget *controlInteraction(QWidget *parent = nullptr);
 	QLabel *defaultValueLabel(QWidget *parent = nullptr);
 
+	QCheckBox *controlCheckBox_;
+
 	/* Helper Hunctions */
 	QString getDefaultValueQStr();
 };
diff --git a/src/qcam/settings/controls_tab.cpp b/src/qcam/settings/controls_tab.cpp
index adc24326..0777d708 100644
--- a/src/qcam/settings/controls_tab.cpp
+++ b/src/qcam/settings/controls_tab.cpp
@@ -17,9 +17,11 @@
 
 #include "control_frame.h"
 
+using namespace libcamera;
+
 ControlsTab::ControlsTab(std::shared_ptr<libcamera::Camera> camera_,
 			 QWidget *parent)
-	: QWidget(parent)
+	: QWidget(parent), controlList_(std::make_shared<ControlList>())
 {
 	/* Main Layout for the tab */
 	QGridLayout *controlGLayout = new QGridLayout(this);
@@ -27,6 +29,8 @@ ControlsTab::ControlsTab(std::shared_ptr<libcamera::Camera> camera_,
 	int controlCount = 0;
 	for (auto &[control, info] : camera_->controls()) {
 		ControlFrame *controlFrame = new ControlFrame(control, info, this);
+		connect(controlFrame, &ControlFrame::controlChanged,
+			this, &ControlsTab::controlChanged);
 
 		controlGLayout->addWidget(controlFrame, controlCount / 2,
 					  controlCount % 2);
@@ -36,3 +40,16 @@ ControlsTab::ControlsTab(std::shared_ptr<libcamera::Camera> camera_,
 	if (controlCount == 0)
 		controlGLayout->addWidget(new QLabel("No controls available"));
 }
+
+/* -----------------------------------------------------------------------------
+ * Qt Slots
+ */
+
+void ControlsTab::controlChanged(const libcamera::ControlId *controlId,
+				 const libcamera::ControlValue controlValue)
+{
+	controlList_->clear();
+
+	controlList_->set(controlId->id(), controlValue);
+	Q_EMIT controlListChanged(controlList_);
+}
diff --git a/src/qcam/settings/controls_tab.h b/src/qcam/settings/controls_tab.h
index 6a63f334..5bac7d48 100644
--- a/src/qcam/settings/controls_tab.h
+++ b/src/qcam/settings/controls_tab.h
@@ -21,4 +21,14 @@ class ControlsTab : public QWidget
 public:
 	ControlsTab(std::shared_ptr<libcamera::Camera> camera_, QWidget *parent);
 	~ControlsTab() = default;
+
+Q_SIGNALS:
+	void controlListChanged(const std::shared_ptr<const libcamera::ControlList>);
+
+public Q_SLOTS:
+	void controlChanged(const libcamera::ControlId *controlId,
+			    const libcamera::ControlValue controlValue);
+
+private:
+	std::shared_ptr<libcamera::ControlList> controlList_;
 };
diff --git a/src/qcam/settings/settings_dialog.h b/src/qcam/settings/settings_dialog.h
index c2fa61ea..e6efd876 100644
--- a/src/qcam/settings/settings_dialog.h
+++ b/src/qcam/settings/settings_dialog.h
@@ -32,8 +32,13 @@ public:
 
 		ControlsTab *controlsTab = new ControlsTab(camera, this);
 		settingTabWidget->addTab(controlsTab, "Controls");
+		connect(controlsTab, &ControlsTab::controlListChanged,
+			this, &SettingsDialog::controlListChanged);
 
 		setWindowTitle("Settings");
 	}
 	~SettingsDialog() = default;
+
+Q_SIGNALS:
+	void controlListChanged(std::shared_ptr<const libcamera::ControlList>);
 };
-- 
2.25.1



More information about the libcamera-devel mailing list