15.1.987. tablet_qt/widgets/cameraqml.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/


// #define DEBUG_CAMERA
// #define DEBUG_TEST_QML_ONLY

#define USE_FILE

#include "cameraqml.h"
#include <QFile>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QMimeType>
#include <QtQml/QQmlEngine>
#include <QtQuick/QQuickItem>
#include <QVBoxLayout>
#include "lib/uifunc.h"

#ifdef DEBUG_TEST_QML_ONLY
const QString TEST_ANIMATION_QML("camcops/camera_qml/test_animation.qml");
#else
const QString CAMERA_QML("camcops/camera_qml/camera.qml");
#endif

/*

For examples see
- https://doc.qt.io/qt-6.5/qtmultimedia-multimediawidgets-camera-example.html
- qt5/qtmultimedia/examples/multimediawidgets/camera/camera.cpp
- qt5/qtmultimedia/examples/multimedia/declarative-camera/...

The "declarative-camera" example is the QML one.
- It's very responsive. It runs on Android properly.

The "multimediawidgets/camera" one is plain CPP.
- Its viewfinder is laggy in the default configuration.
- Its viewfinder doesn't work on Android.

Yet presumably all the QML stuff uses the same underlying CPP code?

Or maybe not?
- https://forum.qt.io/topic/59394/declarative-camera-vs-widget-based-camera-qml-to-c-breakout
- https://doc.qt.io/qt-6.5/qtqml-cppintegration-interactqmlfromcpp.html
- http://lists.qt-project.org/pipermail/android-development/2015-September/000734.html
- https://stackoverflow.com/questions/40153156/qt-qcamera-not-working-on-android
- https://bugreports.qt.io/browse/QTBUG-38233
- https://bugreports.qt.io/browse/QTBUG-41467
- http://omg-it.works/how-to-grab-video-frames-directly-from-qcamera/
- https://forum.qt.io/topic/47330/android-qcamera-5-4-beta
- https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer

The actual error on Android is:
... warning: The video surface is not compatible with any format supported by the camera

*/


// ============================================================================
// Constructor/destructor
// ============================================================================

CameraQml::CameraQml(QWidget* parent) :
    OpenableWidget(parent)
{
#ifdef DEBUG_CAMERA
    qDebug() << Q_FUNC_INFO;
#endif
    /*

QWidget::createWindowContainer()
    - https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
    - https://doc.qt.io/qt-6.5/qquickview.html#details
    - http://blog.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/

BUT:
    - doesn't work on Android, even for the test animation.
    - When it doesn't work, but the declarative-camera example does:
      These errors come from both, so are not a problem:
      (a) camcops
      - D libGLESv2: DTS_GLAPI : DTS is not allowed for Package : org.camcops.camcops
      - E libGLESv1: HWUI Protection: wrong call from hwui context F:ES1-glDeleteTexturesSEC
      (b) declarative_camera
      - D libGLESv2: DTS_GLAPI : DTS is not allowed for Package : org.qtproject.example.declarative_camera
      ...
      - E libGLESv1: HWUI Protection: wrong call from hwui context F:ES1-glDeleteTexturesSEC

    - http://lists.qt-project.org/pipermail/interest/2015-November/019657.html

... use QQuickWidget instead.

    */
    m_qml_view = new QQuickWidget();
    m_qml_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
    connect(m_qml_view->engine(), &QQmlEngine::quit,
            this, &CameraQml::cancelled);
    // Just after calling setSource(), calling view->rootObject() can give a
    // nullptr, because it may be loading in the background. So:
    connect(m_qml_view, &QQuickWidget::statusChanged,
            this, &CameraQml::qmlStatusChanged);
    // ... and must set that signal before calling setSource().
#ifdef DEBUG_TEST_QML_ONLY
    m_qml_view->setSource(uifunc::resourceUrl(TEST_ANIMATION_QML));
    m_qml_view->resize(800, 480);
#else
    m_qml_view->setSource(uifunc::resourceUrl(CAMERA_QML));
#endif

    auto top_layout = new QVBoxLayout();
    top_layout->addWidget(m_qml_view);
    setLayout(top_layout);
}


// ============================================================================
// Public interface
// ============================================================================

void CameraQml::finish()
{
    emit finished();
}


// ============================================================================
// Internals
// ============================================================================

void CameraQml::qmlStatusChanged(const QQuickWidget::Status status)
{
#ifdef DEBUG_CAMERA
    qDebug() << Q_FUNC_INFO;
#endif
    if (status == QQuickWidget::Ready) {
        qmlFinishedLoading();
    } else {
        qWarning() << "QML status is unhappy:" << status;
    }
}


void CameraQml::qmlFinishedLoading()
{
#ifdef DEBUG_CAMERA
    qDebug() << Q_FUNC_INFO;
#endif
    Q_ASSERT(m_qml_view);
    QQuickItem* root = m_qml_view->rootObject();
    Q_ASSERT(root);
    // It's possible to connect to non-root objects, but it's much cleaner to
    // route from QML child objects up to the QML root object, and then to C++.
    connect(root, SIGNAL(imageCaptured(const QVariant&)),
            this, SLOT(copyPreviewImage(const QVariant&)));
    connect(root, SIGNAL(previewSaved()), this, SLOT(savePreviewImage()));
    connect(root, SIGNAL(fileNoLongerNeeded(const QString&)),
            this, SLOT(deleteSuperfluousFile(const QString&)));
    // ... we have to use SIGNAL() and SLOT() since C++ has no idea of the
    // provenance of the signal (and whether or not it exists) -- the macros
    // map signals via strings, so this works, but you'll get an error like
    // "QObject::connect: No such signal PhotoPreview_QMLTYPE_2::imageCaptured(const QString&)"
    // if you get the type wrong.
}


void CameraQml::copyPreviewImage(const QVariant& preview)
{
    m_preview = preview.value<QImage>();
}


void CameraQml::savePreviewImage()
{
    emit imageCaptured(m_preview);
}


void CameraQml::deleteFile(const QString& filename) const
{
#ifdef DEBUG_CAMERA
    qDebug() << Q_FUNC_INFO;
#endif
    if (!filename.isEmpty()) {
        bool success = QFile::remove(filename);
        qDebug() << "Deleting temporary camera file " << filename
                 << (success ? "... success" : "... FAILED!");
    }
}


void CameraQml::deleteSuperfluousFile(const QString& filename) const
{
#ifdef DEBUG_CAMERA
    qDebug() << Q_FUNC_INFO;
#endif
    deleteFile(filename);
}