Tomilov Anatoliy
Tomilov Anatoliy

Reputation: 16711

Dynamically change Images when current locale changed

The interface of simple video player, which consists of Video and Image items, blended by Blend from QtGraphicalEffects. Image has associated MouseArea and translucent graphical buttons with "hardcoded" text in English. Default images provided by QQuickImageProvider from following locations:

import QtQml 2.2

import QtQuick 2.9
import QtMultimedia 5.9

import QtGraphicalEffects 1.0

Blend {
    id: blender

    mode: "normal"

    property alias playbackState: video.playbackState

    onVisibleChanged: {
        if (!visible) {
            video.playlist.clear()
        }
    }

    source: Video {
        id: video

        visible: false

        width: blender.width
        height: blender.height

        fillMode: VideoOutput.Stretch

        playlist: Playlist {
            onErrorChanged: {
                if (error() !== Playlist.NoError) {
                    console.log("Error: %1 (%2)".arg(error()).arg(errorString()))
                    video.stop()
                }
            }

            function nextInLoop() {
                currentIndex = (currentIndex + itemCount + 1) % itemCount
            }

            function previousInLoop() {
                currentIndex = (currentIndex + itemCount - 1) % itemCount
            }
        }

        Connections {
            target: videoPlayerSingleton
            onSetPlaylist: {
                if (video.playlist.clear()) {
                    if (video.playlist.addItems(playlist)) {
                        if (index < 0) {
                            video.playlist.playbackMode = Playlist.Random
                        } else {
                            video.playlist.currentIndex = index
                            video.playlist.playbackMode = Playlist.Sequential
                        }
                        video.play()
                    }
                }
            }
        }

        autoPlay: true
        loops: MediaPlayer.Infinite
    }

    foregroundSource: Image {
        visible: false

        width: blender.width
        height: blender.height

        sourceSize: Qt.size(width, height)
        source: {
            switch (playbackState) {
            case MediaPlayer.PlayingState : return "image://videoplayer/pause"
            case MediaPlayer.PausedState : return "image://videoplayer/play"
            case MediaPlayer.StoppedState : return "image://videoplayer/logo"
            }
        }
        smooth: true
    }

    MouseArea {
        anchors.fill: parent

        readonly property rect previousButton: Qt.rect((130 - 40) / 1920, (505 - 40) / 1080, 80 / 1920, 80 / 1080)
        readonly property rect nextButton: Qt.rect((1800 - 40) / 1920, (505 - 40) / 1080, 80 / 1920, 80 / 1080)
        readonly property rect playPauseButton: Qt.rect((950 - 40) / 1920, (980 - 40) / 1080, 80 / 1920, 80 / 1080)
        readonly property rect backToCatalogButton: Qt.rect((1510 - 40) / 1920, (980 - 40) / 1080, 80 / 1920, 80 / 1080)
        readonly property rect openCollectionButton: Qt.rect((1710 - 40) / 1920, (980 - 40) / 1080, 80 / 1920, 80 / 1080)

        onClicked: {
            var hit = Qt.point(mouse.x / width, mouse.y / height)
            if (videoPlayerSingleton.contains(previousButton, hit)) {
                video.playlist.previousInLoop()
            } else if (videoPlayerSingleton.contains(nextButton, hit)) {
                video.playlist.nextInLoop()
            } else if (videoPlayerSingleton.contains(playPauseButton, hit)) {
                if (playbackState === MediaPlayer.PlayingState) {
                    video.pause()
                } else {
                    video.play()
                }
            } else if (videoPlayerSingleton.contains(backToCatalogButton, hit)) {
                videoPlayerSingleton.backToCatalog(video.playlist.currentIndex)
            } else if (videoPlayerSingleton.contains(openCollectionButton, hit)) {
                videoPlayerSingleton.openCollection(video.playlist.currentIndex)
            }
        }
    }
}

There is also corresponding images in +ru_RU/ subfolder into the resources.

C++ QObject singletone from global context may have signal void languageChanged(QLocale locale);, which emitted, when all the QTranslators are replaced in QCoreApplication::removeTranslator/installTranslator for each of loaded resource.

// i18n.hpp:

#pragma once

#include <QtCore>

Q_DECLARE_LOGGING_CATEGORY(i18nCategory)

#ifndef PROJECT_DEFAULT_LOCALE
#define PROJECT_DEFAULT_LOCALE "ru_RU"
#endif

class Internationalization
        : public QObject
{

    Q_OBJECT

    static QMutex mutex;
    static Internationalization * self;

    explicit Internationalization(QString projectName = QStringLiteral(PROJECT_NAME));

public :

    static
    Internationalization * instance()
    {
        Q_ASSERT(qApp);
        QMutexLocker lock{&mutex};
        if (!self) {
            self = ::new Internationalization;
        }
        return self;
    }

    void setDependencies(QStringList dependencies);

public Q_SLOTS :

    void load(QLocale locale = QStringLiteral(PROJECT_DEFAULT_LOCALE));

Q_SIGNALS :

    void aboutToLanguageChanged();
    void languageChanged(QLocale locale);

private :

    QStringList translations;
    QList< QPointer< QTranslator > > translators;

};

#define i18n Internationalization::instance()

// i18n.cpp:

#include "i18n.hpp"

Q_LOGGING_CATEGORY(i18nCategory, "internationalization")

QMutex Internationalization::mutex;
Internationalization * Internationalization::self = Q_NULLPTR;

Internationalization::Internationalization(QString projectName)
    : QObject{qApp}
{
    translations.prepend(projectName);
    translations.prepend(PROJECT_NAME);
}

void Internationalization::setDependencies(QStringList dependencies)
{
    translations << dependencies;
}

void Internationalization::load(QLocale locale)
{
    // get_target_property(QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)
    // qmake -query QT_INSTALL_TRANSLATIONS
    Q_ASSERT(!(translators.size() > translations.size()));
    while (translators.size() < translations.size()) {
        translators << ::new QTranslator{qApp};
    }
    Q_EMIT aboutToLanguageChanged();
    QLocale::setDefault(locale);
    QMutableListIterator translator{translators};
    for (const auto translation : translations) {
        Q_ASSERT(translator.hasNext());
        const auto t = translator.next();
        if (!QCoreApplication::removeTranslator(t)) {
            if (!t->isEmpty()) {
                qCDebug(i18nCategory).noquote()
                        << tr("Unable to remove translation from project %1")
                           .arg(translation);
            }
        }
        if (t->load(locale, translation, ".", ":/i18n")) {
            if (!QCoreApplication::installTranslator(t)) {
                qCDebug(i18nCategory).noquote()
                        << tr("Unable to install translation for %1 locale from project %2")
                           .arg(locale.name(), translation);
            }
        } else {
            qCDebug(i18nCategory).noquote()
                    << tr("Unable to load translation for %1 locale from project %2")
                       .arg(locale.name(), translation);
        }
    }
    Q_EMIT languageChanged(locale);
}

I want to change locations of images on signal which emitted along with locale changing. Is it possible to change selected files at runtime (i.e. w/o recreating of the parent Item, which contains VideoPlayer)?

Connected question: what is affected by file selectors first: QImageReader's filepaths used internally into QQuickImageProvider or source: of Quick's Image? The latter are derived from the first. Or maybe both?

Upvotes: 1

Views: 1664

Answers (1)

Mark Ch
Mark Ch

Reputation: 3030

The approach is to assign your locale to a QML property, and then use that and the video playbackState enum property in a binding expression to the source property of the Image item.

When the button is clicked, the QML locale property is changed, and the binding expression on the source of the Image item is automatically re-evaluated, causing the QQuickImageProvider to serve a new image.

main.qml

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import QtMultimedia 5.9

ApplicationWindow {
    id: applicationWindow
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Video {
        id: video
    }

    Column {
        id: contents

        property bool toggle: false

        function toggleLocale() {
            if (toggle) {
                // set QML locale
                applicationWindow.locale = Qt.locale("en_GB")
            } else {
                // set QML locale
                applicationWindow.locale = Qt.locale("ru_RU")
            }

            toggle = !toggle
        }

        Text { text: "locale %1".arg(applicationWindow.locale.name) }
        Text { text: "playbackState %1".arg(video.playbackState) }

        Button { text: "toggle locale"; onClicked: contents.toggleLocale() }

        Image {
            source: "image://colors/%1/%2".arg(applicationWindow.locale.name).arg(video.playbackState)
        }
    }
}

imageprovider.h

#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H

#include <QDebug>
#include <QImage>
#include <QMediaPlayer>
#include <QQuickImageProvider>

class ImageProvider : public QQuickImageProvider
{
public:
    ImageProvider()
        : QQuickImageProvider(QQuickImageProvider::Image)
    {
    }

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
    {
        Q_UNUSED(requestedSize)

        QString localePath = id.split("/").at(0);
        QString fileName;

        int playbackState = id.split("/").at(1).toInt();
        switch (playbackState) {
        case QMediaPlayer::StoppedState:
            fileName = "logo.png";
            break;
        case QMediaPlayer::PlayingState:
            fileName = "play.png";
            break;
        case QMediaPlayer::PausedState:
            fileName = "pause.png";
            break;
        default:
            fileName = "logo.png";
            break;
        }

        // the following images are located under resources:
        // en_GB/logo.png
        // en_GB/play.png
        // en_GB/pause.png
        // ru_RU/logo.png
        // ru_RU/play.png
        // ru_RU/pause.png
        // which is why imagePath starts with : colon
        // but they could also be on the filesystem
        QString imagePath = QString(":%1/%2").arg(localePath).arg(fileName);
        qDebug() << "return Image file:" << imagePath;

        QImage image(imagePath);
        *size = image.size();

        // QImage supports copy, so can return like this
        // copy is returned, original on the stack is destroyed
        return image;
    }
};

#endif // IMAGEPROVIDER_H

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "imageprovider.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    engine.addImageProvider(QLatin1String("colors"), new ImageProvider());

    engine.load(QUrl(QLatin1String("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

Upvotes: 1

Related Questions