This may seem like a dumb question, but i'm having lots of trouble to have internationalization working well in QT 5.12.
There are at least 2 things i would like to do:
The possibility to save the language that was selected by the user and the next time the user runs the app its selected (but problably that would require at least a file to save that), or run the app in the native language of the SO.
Having a dynamic translation working, by this i mean the language to be automatically changed while the application is running.
About the first point i know that to install the translation of the native language of the system something like Translator.load("qt_" + QLocale::system().name(),QLibraryInfo::location(QLibraryInfo::TranslationsPath)) can be used.
The second point, i've found a solution but it requires that i place a folder with the name translation(on this case) on the release/debug folder where i place the individual .qm files.
I'm going to provide a simple example of what i've found so far:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QFontDatabase>
#include <QFont>
#include <QtQml>
#include "trans.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QTranslator Translator;
Translator.load(":/translations/translating-qml_ru.qm");
app.installTranslator(&Translator);
QQmlApplicationEngine engine;
// object of our class with "magic" property for translation
Trans trans(&engine);
// make this object available from QML side
engine.rootContext()->setContextProperty("trans", &trans);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
ApplicationWindow {
id: root
width: 800
minimumWidth: 500
height: 600
minimumHeight: 600
visible: true
title: "Translating QML application"
Column {
width: parent.width * 0.95
spacing: 15
padding: 15
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
Button {
Layout.preferredWidth: 76
Layout.preferredHeight: 53
text:"EN"
onClicked: {
onClicked: trans.selectLanguage("en");
}
}
Rectangle
{
Layout.preferredWidth: 2
Layout.fillHeight: true
Layout.margins: 10
color: "black"
}
Button {
text: "RU"
Layout.preferredWidth: 76
Layout.preferredHeight: 53
onClicked: {
onClicked: trans.selectLanguage("ru");
}
}
Rectangle
{
Layout.preferredWidth: 2
Layout.fillHeight: true
Layout.margins: 10
color: "black"
}
Button {
text: "NO"
Layout.preferredWidth: 76
Layout.preferredHeight: 53
onClicked: {
onClicked: trans.selectLanguage("no");
}
}
Rectangle
{
Layout.preferredWidth: 2
Layout.fillHeight: true
Layout.margins: 10
color: "black"
}
Button {
text: "DE"
Layout.preferredWidth: 76
Layout.preferredHeight: 53
onClicked: {
onClicked: trans.selectLanguage("de");
}
}
}
Label {
font.pixelSize: 16
text: qsTr("I woke up after midnight and realised - <b>IT DOES</b>!<br/>"
+ "Everything goes according to the plan.")
}
}
}
trans.cpp
#include "trans.h"
Trans::Trans(QQmlEngine *engine)
{
_translator = new QTranslator(this);
_engine = engine;
}
void Trans::selectLanguage(QString language)
{
QString languagesArray[] = { "en", "pt", "es", "br", "de", "dk", "fi", "fr", "it", "lt", "no", "ro", "tr", "hu" };
QDir dir = QDir(qApp->applicationDirPath()).absolutePath();
for(int i=0;i<languagesArray->length();i++){
if(languagesArray[i] != language){
_translator->load(QString("Lang-%1").arg(languagesArray[i]),QString("%1/translation").arg(dir.path()));
qApp->removeTranslator(_translator);
}
}
if (!_translator->load(
QString("translating-qml_%1").arg(language),
// look for the file in translations folder within working directory
QString("%1/translations").arg(dir.path())
)
)
{
qDebug() << "Failed to load translation file, falling back to English";
}
// it's a global thing, we can use it anywhere (after #including <QGuiApplication>)
qApp->installTranslator(_translator);
_engine->retranslate();
emit languageChanged();
}
trans.h
#ifndef TRANS_H
#define TRANS_H
#include <QObject>
#include <QTranslator>
#include <QDebug>
#include <QGuiApplication>
#include <QDir>
#include <QQmlEngine>
class Trans : public QObject
{
Q_OBJECT
public:
Trans(QQmlEngine *engine);
Q_INVOKABLE void selectLanguage(QString language);
signals:
void languageChanged();
private:
QTranslator *_translator;
QQmlEngine *_engine;
};
#endif // TRANS_H
I want to know step by step what i have to do to have this system fully working, because the info i've found the web (including qt docs) is confuse to me.
For the first point it is only necessary to save the information that identifies the language on the hard disk through QSettings. When the application is started, the QSettings should be read and, accordingly, the translation should be done and when the language is modified it should be saved.
Your second point is not very clear, but I suppose you want what are the steps to use Qt Internationalization, there are several methods since some tasks can be done manually and others can be automated.
First add the following instruction to the .pro:
TRANSLATIONS = /path/of/some_name1.ts \
/path/of/some_name2.ts \
/path/of/some_name3.ts
In my example I use the following structure:
TARGET = AppTranslations
# ...
TRANSLATIONS = i18n/$${TARGET}_en.ts \
i18n/$${TARGET}_de.ts \
i18n/$${TARGET}_no.ts \
i18n/$${TARGET}_ru.ts
Then you must open the terminal or CMD in the folder where your .pro is and execute the following command:
lupdate your_project.pro
This generates the .ts where you indicated, then you have to use Qt Linguist to edit that file doing the translations.
Then you convert the .ts to .qm with:
lrelease your_project.pro
Then you can add the .qm to a qresource embedding in the application, but in my case I prefer that it is in a folder on the side of the executable so the executable does not weigh much and could add more translations without recompiling the project, and for it to be automated the next command to be copied to the side of the executable.
COPY_CONFIG = $$files(i18n/*.qm, true)
copy_cmd.input = COPY_CONFIG
copy_cmd.output = i18n/${QMAKE_FILE_IN_BASE}${QMAKE_FILE_EXT}
copy_cmd.commands = $$QMAKE_COPY_DIR ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT}
copy_cmd.CONFIG += no_link_no_clean
copy_cmd.variable_out = PRE_TARGETDEPS
QMAKE_EXTRA_COMPILERS += copy_cmd
So in the end the build folder will have the following structure:
├── AppTranslations
├── i18n
│ ├── AppTranslations_de.qm
│ ├── AppTranslations_en.qm
│ ├── AppTranslations_no.qm
│ └── AppTranslations_ru.qm
│ ...
Now that you have the .qm, the logic of using it in the application is implemented. Before Qt 5.10 you had to do the trick of adding an empty string for the translation to work but the latest versions do not require it.
On the other hand in my case I implement a logic to get the .qm and so the languages available since I use a default format:
{Name_Of_Application}_{lang}.qm
The other part of the logic is similar to yours so I will not go into much detail and I will show you the code:
translator.h
#ifndef TRANSLATOR_H
#define TRANSLATOR_H
#include <QDir>
#include <QObject>
#include <QQmlEngine>
#include <QTranslator>
class Translator : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList languages READ languages NOTIFY languagesChanged)
Q_PROPERTY(QString currentLanguage READ currentLanguage NOTIFY currentLanguageChanged)
public:
explicit Translator(QQmlEngine *engine, QObject *parent = nullptr);
Q_INVOKABLE void selectLanguage(const QString & language);
QStringList languages() const;
QString currentLanguage() const;
Q_INVOKABLE static QString languageByCode(const QString & code);
signals:
void languageChanged();
void languagesChanged();
void currentLanguageChanged();
private:
const QString extension = ".qm";
QQmlEngine *m_engine;
QTranslator *m_translator;
QStringList m_languages;
QString m_currentLanguage;
QDir m_dir;
};
#endif // TRANSLATOR_H
translator.cpp
#include "translator.h"
#include <QGuiApplication>
#include <QDirIterator>
#include <QSettings>
Translator::Translator(QQmlEngine *engine, QObject *parent) :
QObject(parent),
m_engine(engine)
{
m_translator = new QTranslator(this);
m_dir = QDir(QGuiApplication::applicationDirPath(),
"*"+extension,
QDir::Name|QDir::IgnoreCase,
QDir::Files);
m_dir.cd("i18n");
m_languages.clear();
for(QString entry: m_dir.entryList()){
entry.remove(0, QGuiApplication::applicationName().length()+1);
entry.chop(extension.length());
m_languages.append(entry);
}
emit languagesChanged();
QSettings settings;
QString lang =settings.value("Language/current", QLocale::system().bcp47Name()).toString();
selectLanguage(lang);
}
QStringList Translator::languages() const
{
return m_languages;
}
QString Translator::currentLanguage() const
{
return m_currentLanguage;
}
QString Translator::languageByCode(const QString &code)
{
QLocale lo(code);
return QLocale::languageToString(lo.language());
}
void Translator::selectLanguage(const QString &language)
{
qApp->removeTranslator(m_translator);
if(m_languages.contains(language)){
QString file = QString("%1_%2%3").arg(QGuiApplication::applicationName()).arg(language).arg(extension);
if(m_translator->load(m_dir.absoluteFilePath(file))){
m_currentLanguage = language;
QSettings settings;
settings.setValue("Language/current", language);
emit currentLanguageChanged();
}
}
qApp->installTranslator(m_translator);
m_engine->retranslate();
emit languageChanged();
}
And then it applies to your project:
main.cpp
#include "translator.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setOrganizationName("Translations INC");
QCoreApplication::setOrganizationDomain("translations.com");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Translator trans(&engine);
engine.rootContext()->setContextProperty("trans", &trans);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
ApplicationWindow {
id: root
width: 800
minimumWidth: 500
height: 600
minimumHeight: 600
visible: true
title: "Translating QML application"
Column {
width: parent.width * 0.95
spacing: 15
padding: 15
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
Repeater{
model: trans.languages
Button{
id: btn
property string code: modelData
text: trans.languageByCode(code)
onClicked: trans.selectLanguage(btn.code)
Layout.preferredWidth: 100
Layout.preferredHeight: 50
highlighted: code == trans.currentLanguage
}
}
}
Label {
font.pixelSize: 16
text: qsTr("I woke up after midnight and realised - <b>IT DOES</b>!<br/>"
+ "Everything goes according to the plan.")
}
}
}
The complete example you find here.
Related
Based on Qt documentation, whenever a QObject pointer type is passed from C++ code to QML, via a Q_INVOKABLE method, there is a set of rules that determine who is responsible for the lifetime of that pointer. Should the QObject be parentless, implicitly the QML engine is responsible for taking ownership of the pointer.
In my scenario, I want my frontend UI to represent a list model which is generated/provided by the backend C++ code. My assumption is that the pointer will stay alive as long as there is a reference to it by the QML code. The code below shows the trimmed down test case:
Main.cpp
#include <QAbstractItemModel>
#include <QDebug>
#include <QGuiApplication>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStringListModel>
class MyStringListModel : public QStringListModel
{
Q_OBJECT
public:
explicit MyStringListModel(const QStringList &strings, QObject* parent=nullptr) : QStringListModel(strings, parent)
{
qDebug() << "Creation";
}
virtual ~MyStringListModel() override
{
qDebug() << "Destruction";
}
};
class Backend : public QObject
{
Q_OBJECT
public:
Backend(QObject* parent=nullptr) : QObject(parent)
{
}
Q_INVOKABLE QAbstractItemModel* createModel() const
{
static const QStringList months = {
tr("January"),
tr("February"),
tr("March"),
tr("April"),
tr("May"),
tr("June"),
tr("July"),
tr("August"),
tr("September"),
tr("October"),
tr("November"),
tr("December"),
};
return new MyStringListModel(months);
}
};
int main(int argc, char* argv[])
{
QGuiApplication application(argc, argv);
qmlRegisterType<QAbstractItemModel>();
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
engine.load("qrc:///ui/main.qml");
return application.exec();
}
#include "main.moc"
Main.qml
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1
ApplicationWindow {
id: window
width: 200
height: 250
visible: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: backend.createModel()
delegate: Text {
anchors.horizontalCenter: parent.horizontalCenter
text: model.display
}
}
Button {
Layout.alignment: Qt.AlignCenter
text: qsTr("Garbage Collect")
onClicked: gc()
}
}
}
This is a screenshot of the program:
The moment the user clicks on the button, the garbage collector runs and destroys the model ptr (destruction is evident by the "Creation" and "Destruction" output in the stdout).
I'm curious to know why the pointer was destroyed? I've noticed that it didn't set the ListView as its parent, which is fair enough, I thought that the QML engine would have used some form of reference pointer to try keep track of who still holds a reference to it. Is there a document which gives greater insight into the way in which garbage collection / ownership is implemented.
Likewise, is there a better way of structuring this code while still meeting the demands of passing a parentless QObject back to QML.
It seems that the reason for the destruction is because the object is not being referenced in QML, for example if it is assigned to a property the garbage collector will not affect it:
ApplicationWindow {
id: window
width: 200
height: 250
visible: true
property var mymodel: backend.createModel()
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: mymodel
delegate: Text {
anchors.horizontalCenter: parent.horizontalCenter
text: display
}
}
Button {
Layout.alignment: Qt.AlignCenter
text: qsTr("Garbage Collect")
onClicked: gc()
}
}
}
I have a C++ plugin system, where a QQmlComponent is created and a qml file is loaded when the user requests a new plugin instance.
Currently I am using setContextProperty() to tell QML about a QObject that is needed for proper initialization.
mEngine->rootContext()->setContextProperty("controller", QVariant::fromValue(mController));
mComponent = new QQmlComponent(mEngine);
mComponent->loadUrl(QUrl{ "qrc:///MyPlugin.qml" });
The problem is, when instantiating a second plugin, both will use the controller of the second one because "controller" is global in QML.
Repeater {
model: controller.numEntries
Is there a way to set a context property locally (only for the current instance)?
I found solutions using setProperty() or QQmlIncubator and setInitialState(), but they all seem to require an object that was already created from my component. But in my plugin I only define the component, which is loaded in the main application through a Loader item. So, when trying these approaches, I always ended up in setting the value in a copy of the item, but not the one being created in my backend.
How can I get access to a property of the component that is created in QML?
mComponent->findChild<QQuickItem*>("controller");
does not give me any results, even if I defined the property in MyPlugin.qml.
Maybe you can create a QObject based class and have a slot and instead of using property you can call slot to slot create a new property in c++ and return it to QML
ControllerCreator.h
#ifndef CONTROLLERCREATOR_H
#define CONTROLLERCREATOR_H
#include <QObject>
class ControllerCreator : public QObject {
Q_OBJECT
public:
explicit ControllerCreator(QObject *parent = nullptr);
signals:
public slots:
int propertyCreator();
private:
int m_example;
};
#endif // CONTROLLERCREATOR_H
ControllerCreator.cpp
#include "ControllerCreator.h"
ControllerCreator::ControllerCreator(QObject *parent)
: QObject(parent), m_example(0)
{
}
int ControllerCreator::propertyCreator()
{
m_example++;
return m_example;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ControllerCreator.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ControllerCreator controllerCreator;
engine.rootContext()->setContextProperty("creator", &controllerCreator);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Column{
anchors.fill: parent
Text{
text: creator.propertyCreator()
color: "blue"
}
Text{
text: creator.propertyCreator()
color: "red"
}
Text{
text: creator.propertyCreator()
color: "green"
}
}
}
I have an enum defined in a QObject with a few values, and I am registering the enum as QFlags as the Qt documentation specifies. I have registered the enum and the QObject as metatypes that I can access just fine from QML.
The problem is that once I have a C++ QObject slot defined that has the QFlags as an argument it doesn't get an error when it is called, but instead passes in the first defined value in the enum (ie. its value is that of the enum entry with the number 0).
It is hard to describe, so I created a small working example (using C++11/Qt 5.7). When you run it and click anywhere in the window that opens, QFlags<QMLThing::MyEnum>(VALA) is printed out, even though in main.qml I am calling thing.doThing(QMLThing.VALC).
I started by creating a "Qt Quick Application" in QtCreator. Then added a class called "QMLThing". Here is the source code for each file:
QMLThing.hpp
#ifndef QMLTHING_HPP
#define QMLTHING_HPP
#include <QObject>
class QMLThing : public QObject
{
Q_OBJECT
public:
enum MyEnum {
VALA = 0,
VALB = 1,
VALC = 2,
VALD = 4,
};
Q_ENUM(MyEnum)
Q_DECLARE_FLAGS(MyEnums, MyEnum)
public:
explicit QMLThing(QObject *parent = 0);
public slots:
void doThing(QMLThing::MyEnums val);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QMLThing::MyEnums)
Q_DECLARE_METATYPE(QMLThing::MyEnums)
#endif // QMLTHING_HPP
QMLThing.cpp
#include "QMLThing.hpp"
#include <QDebug>
QMLThing::QMLThing(QObject *parent) : QObject(parent)
{}
void QMLThing::doThing(QMLThing::MyEnums val)
{
qDebug() << val;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "QMLThing.hpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<QMLThing>("stuff", 1, 0, "QMLThing");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import stuff 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
thing.doThing(QMLThing.VALC)
}
}
Text {
text: qsTr("Click here and look in the terminal")
anchors.centerIn: parent
}
QMLThing {
id: thing
}
}
This seems a lot like a bug, but maybe I'm just missing something.
You're missing Q_FLAG(MyEnums):
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QObject>
class QMLThing : public QObject
{
Q_OBJECT
public:
enum MyEnum {
VALA = 0,
VALB = 1,
VALC = 2,
VALD = 4,
VALE = VALC | VALD
};
Q_DECLARE_FLAGS(MyEnums, MyEnum)
Q_FLAG(MyEnums)
public:
explicit QMLThing(QObject *parent = 0) :
QObject(parent)
{
}
public slots:
void doThing(QMLThing::MyEnums val)
{
qDebug() << val;
}
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QMLThing::MyEnums)
Q_DECLARE_METATYPE(QMLThing::MyEnums)
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<QMLThing>("stuff", 1, 0, "QMLThing");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.7
import QtQuick.Window 2.2
import stuff 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
thing.doThing(QMLThing.VALC)
thing.doThing(QMLThing.VALC | QMLThing.VALD)
}
}
Text {
text: qsTr("Click here and look in the terminal")
anchors.centerIn: parent
}
QMLThing {
id: thing
}
}
As mentioned here, you don't need to use Q_ENUM():
Note: The Q_FLAG macro takes care of registering individual flag
values with the meta-object system, so it is unnecessary to use
Q_ENUM() in addition to this macro.
Not sure exactly what is going on but first of all:
public:
enum MyEnum {
VALA,
VALB,
VALC,
VALD,
};
You need to remove the last coma.
I would also recommend to set at least the first enum, to a certain value, usually 0 so you know where you are going but no need to set the following enum items as they will be auto-incremented from the last one set.
Last, I'm not entirely sure about the QMLThing.ValC, shouldn't it be QMLThing::MyEnums::ValC ?
I have a Qt application and I'd like to show some log. I use a TextArea. However, if the log is large or the events come too fast the GUI can't draw Textarea fast enough.
I have analyzed this problem with Qt Creator (QML Profiler) and if the log is large it takes 300 ms to draw the GUI. I use this software on a Raspberry Pi2.
Any ideas how to solve this? Should I use other QML controls? Thanks.
QML code:
TextArea {
text: appHandler.rawCommunication
readOnly: true
}
C++ code:
Q_PROPERTY(QString rawCommunication READ rawCommunication WRITE setrawCommunication NOTIFY rawCommunicationChanged)
void setrawCommunication(QString val)
{
val.append("\n");
val.append(m_rawCommunication);
m_rawCommunication = val;
emit rawCommunicationChanged(m_rawCommunication);
}
Use a view, like ListView. They instantiate their delegates as needed, based on which data the view says it needs to show depending on the position the user is at in the list. This means that they perform much better for visualising large amounts of data than items like TextArea, which in your case is going to keep a massive, ever-growing string in memory.
Your delegate could then be a TextArea, so you'd have one editable block of text per log line. However, if you don't need styling, I'd recommend going with something a bit lighter, like TextEdit. Taking it one step further: if you don't need editable text, use plain old Text. Switching to these might not make much of a difference, but if you're still seeing slowness (and have lots of delegates visible at a time), it's worth a try.
I tried the ListView suggestion but it has several drawbacks:
No easy way to keep the view positioned at the bottom when new output is added
No selection across lines/delegates
So I ended up using a cached TextArea, updating once every second:
TextArea {
id: outputArea_text
wrapMode: TextArea.Wrap
readOnly: true
font.family: "Ubuntu Mono, times"
function appendText(text){
logCache += text + "\n";
update_timer.start();
}
property string logCache: ""
Timer {
id: update_timer
// Update every second
interval: 1000
running: false
repeat: false
onTriggered: {
outputArea_text.append(outputArea_text.logCache);
outputArea_text.logCache = "";
}
}
Component.onCompleted: {
my_signal.connect(outputArea_text.appendText)
}
}
try this approach:
create a c++ logger class
append all logs to this class
and print them using some action, example a button click
this will solve your performance issue
Example of code:
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QQmlContext>
#include <QObject>
#include <QStringList>
#include <QQmlEngine>
#include <QString>
#include <QtCore>
#include <QDebug>
class Logger : public QObject
{
Q_OBJECT
public:
explicit Logger(QObject *parent = 0);
~Logger();
Q_INVOKABLE QStringList *getLogStream();
Q_INVOKABLE void printLogStream();
Q_INVOKABLE void appendLog(QString log);
Q_INVOKABLE void log(QString log="");
Q_INVOKABLE void log(QString fileName, QString log);
signals:
public slots:
private:
QStringList* stringStream_;
};
#endif // LOGGER_H
Logger.cpp
#include "logger.h"
Logger::Logger(QObject *parent) :
QObject(parent),
stringStream_(new QStringList)
{
}
~Logger(){
if(stringStream_ != NULL)
{
delete stringStream_;
stringStream_ = NULL;
}
}
QStringList* Logger::getLogStream(){
return stringStream_;
}
void Logger::printLogStream()
{
QStringListIterator itr(*stringStream_);
while (itr.hasNext())
qDebug()<< itr.next()<<"\n";
}
void Logger::appendLog(QString log){
stringStream_->push_back(log) ;
}
void Logger::log(QString fileName,QString log)
{
#ifdef ENABLElogs
fileName.push_front(" [");
if(!fileName.contains(".qml"))
{
fileName.append(".qml]:");
}
qDebug()<<fileName<<log;
#else
Q_UNUSED(log);
Q_UNUSED(fileName);
#endif
}
void Logger::log(QString log)
{
#ifdef ENABLElogs
qDebug()<<log;
#else
Q_UNUSED(log);
#endif
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "logger.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer *viewer = new QtQuick2ApplicationViewer;
Logger* stream = new Logger;
viewer->rootContext()->setContextProperty("Stream",stream);
viewer->setMainQmlFile(QStringLiteral("qml/project/main.qml"));
viewer->showExpanded();
return app.exec();
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 800
height: 480
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
Component.onCompleted: Stream.appendLog("Text object is completed")
}
Column{
x:300
Button{
text:"append"
onClicked: {
Stream.appendLog("MouseArea object clicked")
}
Component.onCompleted: Stream.appendLog("Button object is completed")
}
Button{
text:"logger"
onClicked: {
Stream.printLogStream()
}
Component.onCompleted: Stream.appendLog("Button logger object is completed")
}
}
TextArea{
text:"blablabla"
Component.onCompleted: Stream.appendLog("TextArea object is completed")
}
Component.onCompleted: Stream.appendLog("the main object is completed")
}
project.pro
#add this line
# comment it, run qmake and recompile to disable logs
DEFINES += ENABLElogs
using this approch you can stop all logs with a single line change when you want to release your soft
However, I have included complete code, using "QAbstractListModel" for a Logging heavy data to QML
listmodel.h
#ifndef LISTMODEL_H
#define LISTMODEL_H
#include <QAbstractListModel>
class ListModel: public QAbstractListModel
{
Q_OBJECT
public:
ListModel();
// Q_PROPERTY(QStringList logs READ name WRITE nameChanged)
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
Q_INVOKABLE QVariant activate(int i);
private:
QStringList m_list;
};
#endif // LISTMODEL_H
listmodel.cpp
#include "listmodel.h"
#include <QFile>
#include <QHash>
ListModel::ListModel()
{
QFile file("/home/ashif/LogFile");
if(!file.open(QIODevice::ReadOnly))
{
qDebug( "Log file open failed" );
}
bool isContinue = true;
do
{
if(file.atEnd())
{
isContinue = false;
}
m_list.append(file.readLine());
}
while( isContinue);
}
int ListModel::rowCount(const QModelIndex & parent ) const
{
return m_list.count();
}
QVariant ListModel::data(const QModelIndex & index, int role ) const
{
if(!index.isValid()) {
return QVariant("temp");
}
return m_list.value(index.row());
}
QVariant ListModel::activate(int i)
{
return m_list[i];
}
main.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
ListView
{
width: 200; height: 250
anchors.centerIn: parent
model:mylistModel
delegate: Text
{
text:mylistModel.activate(index)
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "logger.h"
#include "listmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Logger myLogger;
ListModel listModel;
engine.rootContext()->setContextProperty("mylistModel", &listModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
I am developing an app for mobile to run in iOS and Android and I am facing some difficulties to access the image gallery of the devices with Qml.
I need to list the images from image gallery in a GridView.
I have tried to return the pictures folder using QStandardPaths but it just works for desktop computers. For smartphones running iOS and Android it returns a folder that is not the folder of the gallery.
Could someone help me to figure out how I can do that? My code is below:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "caminhoimagens.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<caminhoImagens>("PathImagens", 1, 0, "CaminhoImagens");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
import Qt.labs.folderlistmodel 2.1
import PathImagens 1.0
Window {
visible: true
width: 360
height: 640
maximumHeight: 640
minimumHeight: 640
maximumWidth: 360
minimumWidth: 360
title: "Acessar Galeria Test"
Rectangle {
id: principal
anchors.fill: parent
ListModel {
id: listModel
}
FolderListModel {
id: folderListModel
folder: "file://" + caminhoImagens.retornaCaminhoImagens()
nameFilters: "*.jpeg"
}
CaminhoImagens {
id: caminhoImagens
}
Item {
id: listaFotosDelegate
property Component delegateComponent: listaFotosDelegateComponent
Component {
id: listaFotosDelegateComponent
Image {
id: imagem
source: folderListModel.folder + "/" + fileName
width: principal.width / 4.2
height: principal.width / 4.2
fillMode: Image.PreserveAspectCrop
}
}
}
GridView {
id: listaFotosGridView
anchors.fill: parent
clip: true
model: folderListModel
delegate: listaFotosDelegate.delegateComponent
cellWidth: parent.width / 4
cellHeight: parent.width / 4
}
}
}
caminhoimagens.h
#ifndef CAMINHOIMAGENS_H
#define CAMINHOIMAGENS_H
#include <QObject>
#include <QStandardPaths>
class caminhoImagens : public QObject
{
Q_OBJECT
public slots:
QString retornaCaminhoImagens();
public:
caminhoImagens();
};
#endif // CAMINHOIMAGENS_H
caminhoimagens.cpp
#include "caminhoimagens.h"
caminhoImagens::caminhoImagens()
{
}
QString caminhoImagens::retornaCaminhoImagens()
{
return QStandardPaths::locate(QStandardPaths::PicturesLocation, QString(), QStandardPaths::LocateDirectory);
}
Answering my own question.
in iOS just create a FileDialog inside the QML file and set folder: shortcuts.pictures. It will call the iOS gallery.
In Android it is a harder job once it's needed to integrate java code.
I have done my code in Qt using QAndroidJniObject to write a equivalent java code.
caminhoimagens.h
#ifndef CAMINHOIMAGENS_H
#define CAMINHOIMAGENS_H
#include <QObject>
#include "imagepickerandroid.h"
#include <QDebug>
class caminhoImagens : public QObject
{
Q_OBJECT
Q_PROPERTY(QString imagemCaminho READ imagemCaminho NOTIFY imagemCaminhoChanged)
public slots:
void buscaImagem();
void retornaImagem(QString path);
public:
caminhoImagens();
QString imagemCaminho();
private:
QString m_imagemCaminho = "";
signals:
void imagemCaminhoChanged();
};
#endif //CAMINHOIMAGENS_H
caminhoimagens.cpp
#include "caminhoimagens.h"
caminhoImagens::caminhoImagens()
{
}
void caminhoImagens::buscaImagem()
{
imagePickerAndroid *imagePicker = new imagePickerAndroid();
connect(imagePicker, SIGNAL(imagemCaminhoSignal(QString)), this, SLOT(retornaImagem(QString)));
imagePicker->buscaImagem();
}
void caminhoImagens::retornaImagem(QString path)
{
qDebug() << path;
m_imagemCaminho = path;
emit imagemCaminhoChanged();
}
QString caminhoImagens::imagemCaminho()
{
return m_imagemCaminho;
}
imagepickerandroid.h
#ifndef IMAGEPICKERANDROID_H
#define IMAGEPICKERANDROID_H
#include <QObject>
#include <QtAndroidExtras>
#include <QDebug>
class imagePickerAndroid : public QObject, public QAndroidActivityResultReceiver
{
Q_OBJECT
public:
imagePickerAndroid();
void buscaImagem();
virtual void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject & data);
signals:
void imagemCaminhoSignal(QString);
};
#endif // IMAGEPICKERANDROID_H
imagepickerandroid.cpp
#include "imagepickerandroid.h"
imagePickerAndroid::imagePickerAndroid()
{
}
void imagePickerAndroid::buscaImagem()
{
QAndroidJniObject ACTION_PICK = QAndroidJniObject::fromString("android.intent.action.GET_CONTENT");
QAndroidJniObject intent("android/content/Intent");
if (ACTION_PICK.isValid() && intent.isValid())
{
intent.callObjectMethod("setAction", "(Ljava/lang/String;)Landroid/content/Intent;", ACTION_PICK.object<jstring>());
intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("image/*").object<jstring>());
QtAndroid::startActivity(intent.object<jobject>(), 101, this);
qDebug() << "OK";
}
else
{
qDebug() << "ERRO";
}
}
void imagePickerAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
{
qDebug() << "Trabalha com os dados";
jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
if (receiverRequestCode == 101 && resultCode == RESULT_OK) {
QString imagemCaminho = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
emit imagemCaminhoSignal(imagemCaminho);
qDebug() << imagemCaminho;
}
else
{
qDebug() << "Caminho errado";
}
}