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

Laurent Pinchart laurent.pinchart at ideasonboard.com
Fri Feb 7 11:13:19 CET 2020


Hi Kieran,

On Fri, Feb 07, 2020 at 10:02:23AM +0000, Kieran Bingham wrote:
> On 06/02/2020 23:28, Laurent Pinchart wrote:
> > 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.
> 
> Which context menu?
> 
> I'm not sure I understand the need. I mean, I don't care, I can add the
> name - I just can't see /why/ a toolbar needs a name :-)
> 
> Ugh ... I see right clicking on the toolbar lets you hide it and then
> you can't get it back again. So /that's/ the context menu ...
> 
> Should the toolbar be 'permanant'? Or removable - and if removable, how
> would we expect to get it back. Keyboard shortcut?

If you can make it permanent I think that would be best.

> Perhaps removable is useful to be able to simplify the view - but as
> this is just a test tool - I don't see the benefit yet.
> 
> I'll try to look at disabling the context menu or making the main
> toolbar permanent.
> 
> >> +
> >> +	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.
> 
> I would actually like this entry to show the current camera in the toolbar.
> 
> And yes the duplicated entry is annoying.
> 
> But I haven't got as far as dealing with such UX issues.
> I was focussing on getting the core code to work.
> 
> I'll work through your code and see what how it integrates.
> 
> 
> Hrm ... one part I don't like about the below is selecting a camera by
> index. That seems quite fragile once we have hotplug support ?

Agreed, but once we have hotplug support we'll have to remove and add
entries from the combo box or popup menu, so refactoring will be needed
anyway. I think hotplug support will also require stable names/IDs, so
we should then have the tool we need to do the job.

> > 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;
> 
> I'll remove this debug print and print the camera name if it fails to
> acquire.
> 
> >  	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.
> 
> Yes, I thought that was needed to make sure they remain in scope. And as
> they are shared_ptr that should be fine right?

I think it's OK, I just wanted to point it out as lambda capture can
sometimes be confusing.

> The alternative is to pass the QAction, and get the camera name from
> there, or pass the cam->name() itself and then get the camera by name.
> 
> I thought as we already have the 'Camera' this would be better - but I
> can change if it's a problem.
> 
> I see in your implementation you move to an index, but I fear this would
> cause problems with hotplug support. But maybe that will be painful
> anyway, and we'll have to reconstruct the camera list to update anytime
> the camera list changes regardless.

The reason I move to an index is becase the activated signal only gives
an index. There's a way to associate a QVariant to a combo box entry,
maybe we can store the shared_ptr in the QVariant, but I haven't tried
that.

> >> +	}
> >> +
> >> +	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.
> 
> Ah indeed, I started out with this as a void, I must have changed it to
> return int when I pulled over the code with the -EBUSY.
> 
> But we can simply ignore that return value indeed, no action will occur.
> 
> Later it would be nice if we had a status bar to report specific
> messages through the UI. But that's a 'later' task.
> 
> Or maybe a workshop style activity ... ;-)
> 
> >> +{
> >> +	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().
> 
> Aha - that's much better and clearly obvious :)
> 
> I'll update to do so.
> 
> >> +
> >> +	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