[libcamera-devel] [PATCH 3/6] qcam: Introduce a toolbar and camera switching

Laurent Pinchart laurent.pinchart at ideasonboard.com
Fri Feb 7 00:28:52 CET 2020


Hi Kieran,

Thank you for the patch.

On Thu, Feb 06, 2020 at 03:05:01PM +0000, Kieran Bingham wrote:
> Implement a quit button, and a list of cameras.
> 
> Selecting a different camera from the Toolbar will stop the current
> stream, and start streaming the chosen camera device if it can be
> acquired.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> ---
>  src/qcam/main_window.cpp | 60 ++++++++++++++++++++++++++++++++++++++++
>  src/qcam/main_window.h   |  4 +++
>  2 files changed, 64 insertions(+)
> 
> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
> index b51a16de199d..1c7260f32d0a 100644
> --- a/src/qcam/main_window.cpp
> +++ b/src/qcam/main_window.cpp
> @@ -13,6 +13,8 @@
>  #include <QCoreApplication>
>  #include <QInputDialog>
>  #include <QTimer>
> +#include <QToolBar>
> +#include <QToolButton>
>  
>  #include <libcamera/camera_manager.h>
>  #include <libcamera/version.h>
> @@ -27,6 +29,8 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
>  {
>  	int ret;
>  
> +	createToolbars(cm);
> +
>  	title_ = "QCam " + QString::fromStdString(CameraManager::version());
>  	setWindowTitle(title_);
>  	connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle()));
> @@ -53,6 +57,31 @@ MainWindow::~MainWindow()
>  	}
>  }
>  
> +int MainWindow::createToolbars(CameraManager *cm)
> +{
> +	QAction *action;
> +
> +	toolbar_ = addToolBar("");

You should give a name to the toolbar, otherwise the context menu will
have an empty entry. You can call it "Main" or anything similar to start
with.

> +
> +	action = toolbar_->addAction("Quit");
> +	connect(action, &QAction::triggered, this, &MainWindow::quit);
> +
> +	QAction *cameraAction = new QAction("&Cameras", this);
> +	toolbar_->addAction(cameraAction);
> +
> +	QToolButton *cameraButton = dynamic_cast<QToolButton *>(toolbar_->widgetForAction(cameraAction));
> +
> +	cameraButton->setPopupMode(QToolButton::InstantPopup);

Any way we could remove the "Camera" entry from the popup menu ? It
seems we may have to insert a manually-created QComboBox. My initial
expriment resulted in the following code. Feel free to fold it in,
modify it, or ignore it if you think it's not a good idea.

diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index 0e994b1e9197..f68b7abccda6 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -10,6 +10,7 @@
 #include <string>
 #include <sys/mman.h>

+#include <QComboBox>
 #include <QCoreApplication>
 #include <QFileDialog>
 #include <QIcon>
@@ -29,11 +30,11 @@
 using namespace libcamera;

 MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
-	: options_(options), allocator_(nullptr), isCapturing_(false)
+	: cm_(cm), options_(options), allocator_(nullptr), isCapturing_(false)
 {
 	int ret;

-	createToolbars(cm);
+	createToolbars();

 	title_ = "QCam " + QString::fromStdString(CameraManager::version());
 	setWindowTitle(title_);
@@ -43,7 +44,7 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
 	setCentralWidget(viewfinder_);
 	adjustSize();

-	ret = openCamera(cm);
+	ret = openCamera();
 	if (!ret) {
 		ret = startCapture();
 	}
@@ -61,7 +62,7 @@ MainWindow::~MainWindow()
 	}
 }

-int MainWindow::createToolbars(CameraManager *cm)
+int MainWindow::createToolbars()
 {
 	QAction *action;

@@ -70,18 +71,14 @@ int MainWindow::createToolbars(CameraManager *cm)
 	action = toolbar_->addAction(QIcon(":x-circle.svg"), "Quit");
 	connect(action, &QAction::triggered, this, &MainWindow::quit);

-	QAction *cameraAction = new QAction("&Cameras", this);
-	toolbar_->addAction(cameraAction);
+	QComboBox *cameraCombo = new QComboBox();
+	connect(cameraCombo, QOverload<int>::of(&QComboBox::activated),
+		this, &MainWindow::setCamera);

-	QToolButton *cameraButton = dynamic_cast<QToolButton *>(toolbar_->widgetForAction(cameraAction));
+	for (const std::shared_ptr<Camera> &cam : cm_->cameras())
+		cameraCombo->addItem(QString::fromStdString(cam->name()));

-	cameraButton->setPopupMode(QToolButton::InstantPopup);
-
-	for (const std::shared_ptr<Camera> &cam : cm->cameras()) {
-		action = new QAction(QString::fromStdString(cam->name()));
-		cameraButton->addAction(action);
-		connect(action, &QAction::triggered, this, [=]() { this->setCamera(cam); });
-	}
+	toolbar_->addWidget(cameraCombo);

 	action = toolbar_->addAction(QIcon(":play-circle.svg"), "start");
 	connect(action, &QAction::triggered, this, &MainWindow::startCapture);
@@ -117,13 +114,19 @@ void MainWindow::updateTitle()
 	setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
 }

-int MainWindow::setCamera(const std::shared_ptr<Camera> &cam)
+void MainWindow::setCamera(int index)
 {
+	const auto &cameras = cm_->cameras();
+	if (static_cast<unsigned int>(index) >= cameras.size())
+		return;
+
+	std::shared_ptr<Camera> cam = cameras[index];
+
 	std::cout << "Chose " << cam->name() << std::endl;

 	if (cam->acquire()) {
 		std::cout << "Failed to acquire camera" << std::endl;
-		return -EBUSY;
+		return;
 	}

 	std::cout << "Switching to camera " << cam->name() << std::endl;
@@ -144,19 +147,17 @@ int MainWindow::setCamera(const std::shared_ptr<Camera> &cam)
 	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);

 	startCapture();
-
-	return 0;
 }

-std::string MainWindow::chooseCamera(CameraManager *cm)
+std::string MainWindow::chooseCamera()
 {
 	QStringList cameras;
 	bool result;

-	if (cm->cameras().size() == 1)
-		return cm->cameras()[0]->name();
+	if (cm_->cameras().size() == 1)
+		return cm_->cameras()[0]->name();

-	for (const std::shared_ptr<Camera> &cam : cm->cameras())
+	for (const std::shared_ptr<Camera> &cam : cm_->cameras())
 		cameras.append(QString::fromStdString(cam->name()));

 	QString name = QInputDialog::getItem(this, "Select Camera",
@@ -168,19 +169,19 @@ std::string MainWindow::chooseCamera(CameraManager *cm)
 	return name.toStdString();
 }

-int MainWindow::openCamera(CameraManager *cm)
+int MainWindow::openCamera()
 {
 	std::string cameraName;

 	if (options_.isSet(OptCamera))
 		cameraName = static_cast<std::string>(options_[OptCamera]);
 	else
-		cameraName = chooseCamera(cm);
+		cameraName = chooseCamera();

 	if (cameraName == "")
 		return -EINVAL;

-	camera_ = cm->get(cameraName);
+	camera_ = cm_->get(cameraName);
 	if (!camera_) {
 		std::cout << "Camera " << cameraName << " not found"
 			  << std::endl;
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index fc85b6a46491..49af0f6ad934 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -44,19 +44,21 @@ private Q_SLOTS:
 	void quit();
 	void updateTitle();

-	int setCamera(const std::shared_ptr<Camera> &cam);
+	void setCamera(int index);

 	int startCapture();
 	void stopCapture();
 	void saveImage();

 private:
-	int createToolbars(CameraManager *cm);
-	std::string chooseCamera(CameraManager *cm);
-	int openCamera(CameraManager *cm);
+	int createToolbars();
+	std::string chooseCamera();
+	int openCamera();
 	void requestComplete(Request *request);
 	int display(FrameBuffer *buffer);

+	CameraManager *cm_;
+
 	QString title_;
 	QTimer titleTimer_;

> +
> +	for (const std::shared_ptr<Camera> &cam : cm->cameras()) {
> +		action = new QAction(QString::fromStdString(cam->name()));
> +		cameraButton->addAction(action);
> +		connect(action, &QAction::triggered, this, [=]() { this->setCamera(cam); });

Are you aware that this will create local copies of the
std::shared_ptr<Camera> for each camera ? Probably not a big deal.

> +	}
> +
> +	return 0;
> +}
> +
>  void MainWindow::quit()
>  {
>  	QTimer::singleShot(0, QCoreApplication::instance(),
> @@ -72,6 +101,37 @@ void MainWindow::updateTitle()
>  	setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
>  }
>  
> +int MainWindow::setCamera(const std::shared_ptr<Camera> &cam)

You can make this a void function, the return value of slots is ignored
when connected to signals.

> +{
> +	std::cout << "Chose " << cam->name() << std::endl;
> +
> +	if (cam->acquire()) {
> +		std::cout << "Failed to acquire camera" << std::endl;
> +		return -EBUSY;
> +	}
> +
> +	std::cout << "Switching to camera " << cam->name() << std::endl;
> +
> +	stopCapture();
> +	camera_->release();
> +
> +	/*
> +	 * If we don't disconnect this signal, it will persist (and get
> +	 * re-added and thus duplicated later if we ever switch back to an
> +	 * previously streamed camera). This causes all sorts of pain.
> +	 *
> +	 * Perhaps releasing a camera should disconnect all (public?) connected
> +	 * signals forcefully!

I'm not sure that would be a good idea, implicit actions are usually
confusing.

> +	 */
> +	camera_->requestCompleted.disconnect(this, &MainWindow::requestComplete);
> +	camera_ = cam;
> +	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);

How about connecting the signal in startCapture() and disconnecting it
in stopCapture() ? It will avoid the duplicated connect in openCamera().

> +
> +	startCapture();
> +
> +	return 0;
> +}
> +
>  std::string MainWindow::chooseCamera(CameraManager *cm)
>  {
>  	QStringList cameras;
> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
> index a11443b30b37..f7c96fdd5c30 100644
> --- a/src/qcam/main_window.h
> +++ b/src/qcam/main_window.h
> @@ -44,7 +44,10 @@ private Q_SLOTS:
>  	void quit();
>  	void updateTitle();
>  
> +	int setCamera(const std::shared_ptr<Camera> &cam);
> +
>  private:
> +	int createToolbars(CameraManager *cm);
>  	std::string chooseCamera(CameraManager *cm);
>  	int openCamera(CameraManager *cm);
>  
> @@ -71,6 +74,7 @@ private:
>  	uint32_t previousFrames_;
>  	uint32_t framesCaptured_;
>  
> +	QToolBar *toolbar_;
>  	ViewFinder *viewfinder_;
>  	std::map<int, std::pair<void *, unsigned int>> mappedBuffers_;
>  };

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list