diff --git a/linphone-desktop/CMakeLists.txt b/linphone-desktop/CMakeLists.txt index afc831eab..6498a3ab9 100644 --- a/linphone-desktop/CMakeLists.txt +++ b/linphone-desktop/CMakeLists.txt @@ -78,6 +78,7 @@ endif() set(SOURCES src/app/App.cpp + src/app/AsyncObjectBuilder.cpp src/app/AvatarProvider.cpp src/app/DefaultTranslator.cpp src/app/Logger.cpp @@ -111,6 +112,7 @@ set(SOURCES set(HEADERS src/app/App.hpp + src/app/AsyncObjectBuilder.hpp src/app/AvatarProvider.hpp src/app/DefaultTranslator.hpp src/app/Logger.hpp diff --git a/linphone-desktop/src/app/App.cpp b/linphone-desktop/src/app/App.cpp index d59f4f5f1..0c1a2efab 100644 --- a/linphone-desktop/src/app/App.cpp +++ b/linphone-desktop/src/app/App.cpp @@ -157,27 +157,7 @@ void App::initContentApp () { if (m_parser.isSet("selftest")) QObject::connect(core, &CoreManager::linphoneCoreCreated, this, &App::quit); else - QObject::connect( - core, &CoreManager::linphoneCoreCreated, this, [core, this]() { - tryToUsePreferredLocale(); - - qInfo() << QStringLiteral("Linphone core created."); - core->enableHandlers(); - - #ifndef __APPLE__ - // Enable TrayIconSystem. - if (!QSystemTrayIcon::isSystemTrayAvailable()) - qWarning("System tray not found on this system."); - else - setTrayIcon(); - - if (!m_parser.isSet("iconified")) - getMainWindow()->showNormal(); - #else - getMainWindow()->showNormal(); - #endif // ifndef __APPLE__ - } - ); + QObject::connect(core, &CoreManager::linphoneCoreCreated, this, &App::openAppAfterInit); QObject::connect( this, &App::receivedMessage, this, [this](int, QByteArray message) { @@ -238,11 +218,8 @@ void App::tryToUsePreferredLocale () { // ----------------------------------------------------------------------------- -QQuickWindow *App::getCallsWindow () { - if (!m_calls_window) - m_calls_window = createSubWindow(this, QML_VIEW_CALLS_WINDOW); - - return m_calls_window; +QQuickWindow *App::getCallsWindow () const { + return qobject_cast(m_calls_window.getObject()); } QQuickWindow *App::getMainWindow () const { @@ -251,28 +228,14 @@ QQuickWindow *App::getMainWindow () const { ); } -QQuickWindow *App::getSettingsWindow () { - if (!m_settings_window) { - m_settings_window = createSubWindow(this, QML_VIEW_SETTINGS_WINDOW); - - QObject::connect( - m_settings_window, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) { - if (visibility == QWindow::Hidden) { - qInfo() << "Update nat policy."; - shared_ptr core = CoreManager::getInstance()->getCore(); - core->setNatPolicy(core->getNatPolicy()); - } - } - ); - } - - return m_settings_window; +QQuickWindow *App::getSettingsWindow () const { + return qobject_cast(m_settings_window.getObject()); } // ----------------------------------------------------------------------------- bool App::hasFocus () const { - return getMainWindow()->isActive() || (m_calls_window && m_calls_window->isActive()); + return getMainWindow()->isActive() || (m_calls_window.isCreated() && getCallsWindow()->isActive()); } // ----------------------------------------------------------------------------- @@ -398,6 +361,45 @@ QString App::getLocale () const { // ----------------------------------------------------------------------------- +void App::openAppAfterInit () { + tryToUsePreferredLocale(); + + qInfo() << QStringLiteral("Linphone core created."); + CoreManager::getInstance()->enableHandlers(); + + #ifndef __APPLE__ + // Enable TrayIconSystem. + if (!QSystemTrayIcon::isSystemTrayAvailable()) + qWarning("System tray not found on this system."); + else + setTrayIcon(); + + if (!m_parser.isSet("iconified")) + getMainWindow()->showNormal(); + #else + getMainWindow()->showNormal(); + #endif // ifndef __APPLE__ + + m_calls_window.createObject(&m_engine, QML_VIEW_CALLS_WINDOW); + + m_settings_window.createObject( + &m_engine, QML_VIEW_SETTINGS_WINDOW, [this](QObject *object) { + QQuickWindow *window = qobject_cast(object); + QObject::connect( + window, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) { + if (visibility == QWindow::Hidden) { + qInfo() << "Update nat policy."; + shared_ptr core = CoreManager::getInstance()->getCore(); + core->setNatPolicy(core->getNatPolicy()); + } + } + ); + } + ); +} + +// ----------------------------------------------------------------------------- + void App::quit () { if (m_parser.isSet("selftest")) cout << tr("selftestResult").toStdString() << endl; diff --git a/linphone-desktop/src/app/App.hpp b/linphone-desktop/src/app/App.hpp index 40d3505f2..ce9b23984 100644 --- a/linphone-desktop/src/app/App.hpp +++ b/linphone-desktop/src/app/App.hpp @@ -25,6 +25,7 @@ #include "../components/notifier/Notifier.hpp" #include "../externals/single-application/SingleApplication.hpp" +#include "AsyncObjectBuilder.hpp" #include #include @@ -58,12 +59,12 @@ public: return m_notifier; } - QQuickWindow *getCallsWindow (); + QQuickWindow *getCallsWindow () const; QQuickWindow *getMainWindow () const; bool hasFocus () const; - Q_INVOKABLE QQuickWindow *getSettingsWindow (); + Q_INVOKABLE QQuickWindow *getSettingsWindow () const; static App *getInstance () { return static_cast(QApplication::instance()); @@ -88,6 +89,8 @@ private: return m_available_locales; } + void openAppAfterInit (); + QCommandLineParser m_parser; QVariantList m_available_locales; @@ -98,8 +101,8 @@ private: DefaultTranslator *m_translator = nullptr; Notifier *m_notifier = nullptr; - QQuickWindow *m_calls_window = nullptr; - QQuickWindow *m_settings_window = nullptr; + AsyncObjectBuilder m_calls_window; + AsyncObjectBuilder m_settings_window; }; #endif // APP_H_ diff --git a/linphone-desktop/src/app/AsyncObjectBuilder.cpp b/linphone-desktop/src/app/AsyncObjectBuilder.cpp new file mode 100644 index 000000000..c26b4e8ca --- /dev/null +++ b/linphone-desktop/src/app/AsyncObjectBuilder.cpp @@ -0,0 +1,123 @@ +/* + * AsyncObjectBuilder.cpp + * Copyright (C) 2017 Belledonne Communications, Grenoble, France + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Created on: March 27, 2017 + * Author: Ronan Abhamon + */ + +#include +#include +#include + +#include "AsyncObjectBuilder.hpp" + +using namespace std; + +// ============================================================================= + +class AsyncObjectBuilder::ObjectIncubator : public QQmlIncubator { +public: + // FIXME: At this moment, asynchronous loading is unstable. + // Use `IncubationMode::Synchronous` instead in Qt 5.9. + // + // See: https://bugreports.qt.io/browse/QTBUG-49416 and + // https://bugreports.qt.io/browse/QTBUG-50992 + ObjectIncubator (AsyncObjectBuilder *builder) : QQmlIncubator(IncubationMode::Synchronous) { + m_builder = builder; + } + +protected: + void statusChanged (Status status) override { + if (status == Error) { + qWarning() << "ObjectIncubator failed to build component:" << errors(); + abort(); + } + + if (status == Ready) { + QObject *object = QQmlIncubator::object(); + + QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); + object->setParent(m_builder); + + m_builder->m_is_created = true; + + // Call user decorator. + if (m_builder->m_decorator) + m_builder->m_decorator(object); + + qInfo() << QStringLiteral("Creation of component instance is successful:") << m_builder->m_component; + + m_builder->m_object = object; + emit m_builder->objectCreated(object); + + // Optimization: Delete unused component now. + m_builder->m_component->deleteLater(); + + // Optimization: Delete unused incubator. + m_builder->m_incubator = nullptr; + delete this; // Very courageous but works. + } + } + +private: + AsyncObjectBuilder *m_builder; +}; + +// ----------------------------------------------------------------------------- + +AsyncObjectBuilder::AsyncObjectBuilder (QObject *parent) : QObject(parent) {} + +AsyncObjectBuilder::~AsyncObjectBuilder () { + delete m_incubator; +} + +void AsyncObjectBuilder::createObject (QQmlEngine *engine, const char *path, Decorator decorator) { + Q_ASSERT(!m_block_creation); + #ifdef QT_DEBUG + m_block_creation = true; + #endif // ifdef QT_DEBUG + + m_component = new QQmlComponent(engine, QUrl(path), QQmlComponent::Asynchronous, this); + m_decorator = decorator; + + qInfo() << QStringLiteral("Start async creation of: `%1`. Component:").arg(path) << m_component; + + QObject::connect(m_component, &QQmlComponent::statusChanged, this, &AsyncObjectBuilder::handleComponentCreation); +} + +QObject *AsyncObjectBuilder::getObject () const { + while (!m_object) + QCoreApplication::processEvents(QEventLoop::AllEvents, 50); + + return m_object; +} + +void AsyncObjectBuilder::handleComponentCreation (QQmlComponent::Status status) { + if (status == QQmlComponent::Ready) { + qInfo() << QStringLiteral("Component built:") << m_component; + + m_incubator = new ObjectIncubator(this); + + qInfo() << QStringLiteral("Start creation of component instance:") << m_component; + + m_component->create(*m_incubator); + } else if (status == QQmlComponent::Error) { + qWarning() << "AsyncObjectBuilder failed to build component:" << m_component->errors(); + abort(); + } +} diff --git a/linphone-desktop/src/app/AsyncObjectBuilder.hpp b/linphone-desktop/src/app/AsyncObjectBuilder.hpp new file mode 100644 index 000000000..337db9cd9 --- /dev/null +++ b/linphone-desktop/src/app/AsyncObjectBuilder.hpp @@ -0,0 +1,71 @@ +/* + * AsyncObjectBuilder.hpp + * Copyright (C) 2017 Belledonne Communications, Grenoble, France + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Created on: March 27, 2017 + * Author: Ronan Abhamon + */ + +#ifndef ASYNC_OBJECT_BUILDER_H_ +#define ASYNC_OBJECT_BUILDER_H_ + +#include + +#include +#include + +// ============================================================================= + +class AsyncObjectBuilder : public QObject { + Q_OBJECT; + + class ObjectIncubator; + + typedef std::function Decorator; + +public: + AsyncObjectBuilder (QObject *parent = Q_NULLPTR); + ~AsyncObjectBuilder (); + + void createObject (QQmlEngine *engine, const char *path, Decorator decorator = nullptr); + QObject *getObject () const; + + bool isCreated () const { + return m_is_created; + } + +signals: + void objectCreated (QObject *object); + +private: + void handleComponentCreation (QQmlComponent::Status status); + + ObjectIncubator *m_incubator = nullptr; + QQmlComponent *m_component = nullptr; + + Decorator m_decorator; + + QObject *m_object = nullptr; + + bool m_is_created = false; + + #ifdef QT_DEBUG + bool m_block_creation = false; + #endif // ifdef QT_DEBUG +}; + +#endif // ASYNC_OBJECT_BUILDER_H_