4

Using KI18n with Rust and Qml

 2 years ago
source link: https://dev.to/ayush1325/using-ki18n-with-rust-and-qml-ja7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Introduction

Now that we have a working QML application start point, I would like to start working on my application. I will be using KDE Kirigami to create my application. While using Kirigami doesn't seem to require any extra configuration(at least not initially), the KI18n framework used for localization in KDE does need some work.

This post can serve as a guide to using C++ from Rust and creating stuff to use qt from Rust.

Create a Libray

I just created a library create using cargo:

cargo new ki18n-rs --lib
Enter fullscreen modeExit fullscreen mode

Rust has the convention to name the minimal wrappers of C libraries as *-sys. However, this doesn't seem to be a convention in C++ libraries. Also, this crate isn't a minimal wrapper in any way.

Also, check that everything is working by running the test:

cargo test
Enter fullscreen modeExit fullscreen mode

Add Dependencies

Normal Dependencies

We will need to add a few dependencies to Cargo.toml before starting:

[dependencies]
cpp = "0.5"
qttypes = "0.2"
qmetaobject = "0.2"
Enter fullscreen modeExit fullscreen mode

We need qmetaobject as a dependency since we will need QObject and QmlEngine later, which are not defined in qttypes. Honestly, we probably can avoid specifying qttypes, but I just left it there for now.

Build Dependencies

We also need a few dependencies for our build.rs. Here is documentation covering the basics of build scripts. If I am being honest, I still don't completely understand everything about linking and other things which seem to be needed when interfacing with C/C++.

[build-dependencies]
cpp_build = "0.5"
semver = "1.0"
Enter fullscreen modeExit fullscreen mode

Writing build.rs

Start point

This is probably the portion that I found the most difficult. The README of qmetaobject-rs gives us a basic idea of the build script, so I started with that. Here is my starting script

use semver::Version;

fn main() {
    eprintln!("cargo:warning={:?}", std::env::vars().collect::<Vec<_>>());

    let qt_include_path = std::env::var("DEP_QT_INCLUDE_PATH").unwrap();
    let qt_library_path = std::env::var("DEP_QT_LIBRARY_PATH").unwrap();
    let qt_version = std::env::var("DEP_QT_VERSION")
        .unwrap()
        .parse::<Version>()
        .expect("Parsing Qt version failed");

    let mut config = cpp_build::Config::new();
    if cfg!(target_os = "macos") {
        config.flag("-F");
        config.flag(&qt_library_path);
    }
    if qt_version >= Version::new(6, 0, 0) {
        config.flag_if_supported("-std=c++17");
        config.flag_if_supported("/std:c++17");
        config.flag_if_supported("/Zc:__cplusplus");
    }
    config.include(&qt_include_path).build("src/lib.rs");

    for minor in 7..=15 {
        if qt_version >= Version::new(5, minor, 0) {
            println!("cargo:rustc-cfg=qt_{}_{}", 5, minor);
        }
    }
    let mut minor = 0;
    while qt_version >= Version::new(6, minor, 0) {
        println!("cargo:rustc-cfg=qt_{}_{}", 6, minor);
        minor += 1;
    }
}
Enter fullscreen modeExit fullscreen mode

Setting up KI18n

For linking KI18n, I decided to create a new function. This was my first time writing a build script, so honestly, I was pretty clueless. I currently have the include path hardcoded because I couldn't find a better way to locate the header files. If anyone has suggestions, they are welcome to open issue at github or they can comment a solution.

fn ki18n_setup(config: &mut cpp_build::Config) {
    let kf5_i18n_path = "/usr/include/KF5/KI18n";
    config.include(kf5_i18n_path);
    println!("cargo:rustc-link-lib=KF5I18n");
}
Enter fullscreen modeExit fullscreen mode

Tidying up Qt section

I also extracted the Qt section to it's seperate function to keep things tidy. Here is the new function:

fn qt_setup(config: &mut cpp_build::Config) -> Version {
    let qt_include_path = std::env::var("DEP_QT_INCLUDE_PATH").unwrap();
    let qt_library_path = std::env::var("DEP_QT_LIBRARY_PATH").unwrap();
    let qt_version = std::env::var("DEP_QT_VERSION")
        .unwrap()
        .parse::<Version>()
        .expect("Parsing Qt version failed");

    if cfg!(target_os = "macos") {
        config.flag("-F");
        config.flag(&qt_library_path);
    }

    if qt_version >= Version::new(6, 0, 0) {
        config.flag_if_supported("-std=c++17");
        config.flag_if_supported("/std:c++17");
        config.flag_if_supported("/Zc:__cplusplus");
    }

    config.include(&qt_include_path);

    // Include qtcore
    config.include(&format!("{}/{}", qt_include_path, "QtCore"));

    qt_version
}
Enter fullscreen modeExit fullscreen mode

I had to include QtCore separately, and I don't currently have any solution for this. It seems the KLocalized header file imports QObject directly rather than using a relative path like include <QtCore/QObject>. So this is probably a quick and dirty fix for now.

Here is my full build script:

use semver::Version;

fn main() {
    eprintln!("cargo:warning={:?}", std::env::vars().collect::<Vec<_>>());

    let mut config = cpp_build::Config::new();

    let qt_version = qt_setup(&mut config);
    ki18n_setup(&mut config);

    config.build("src/lib.rs");

    for minor in 7..=15 {
        if qt_version >= Version::new(5, minor, 0) {
            println!("cargo:rustc-cfg=qt_{}_{}", 5, minor);
        }
    }
    let mut minor = 0;
    while qt_version >= Version::new(6, minor, 0) {
        println!("cargo:rustc-cfg=qt_{}_{}", 6, minor);
        minor += 1;
    }
}

fn qt_setup(config: &mut cpp_build::Config) -> Version {
    let qt_include_path = std::env::var("DEP_QT_INCLUDE_PATH").unwrap();
    let qt_library_path = std::env::var("DEP_QT_LIBRARY_PATH").unwrap();
    let qt_version = std::env::var("DEP_QT_VERSION")
        .unwrap()
        .parse::<Version>()
        .expect("Parsing Qt version failed");

    if cfg!(target_os = "macos") {
        config.flag("-F");
        config.flag(&qt_library_path);
    }

    if qt_version >= Version::new(6, 0, 0) {
        config.flag_if_supported("-std=c++17");
        config.flag_if_supported("/std:c++17");
        config.flag_if_supported("/Zc:__cplusplus");
    }

    config.include(&qt_include_path);

    // Include qtcore
    config.include(&format!("{}/{}", qt_include_path, "QtCore"));

    qt_version
}

fn ki18n_setup(config: &mut cpp_build::Config) {
    let kf5_i18n_path = "/usr/include/KF5/KI18n";

    config.include(kf5_i18n_path);

    println!("cargo:rustc-link-lib=KF5I18n");
}
Enter fullscreen modeExit fullscreen mode

Writing the Library

Now we can finally work on using KI18n from Rust. The cpp documentation is pretty great, and I would advise everyone to go through it if they are doing anything with C++ and Rust.

Creating Wrapper for KLocalizedContext

We cannot directly use KLocalizedContext from Rust since it is not a relocatable struct. Thus we need to create a wrapper struct which contains a unique_ptr to our actual KLocalizedContext. At least that's how qmetaobject-rs seems to get around the problem. Here's how the wrapper looks like:

cpp! {{
    #include <KLocalizedContext>
    #include <QtCore/QObject>
    #include <QtQml/QQmlEngine>
    #include <QtQuick/QtQuick>

    struct KLocalizedContextHolder {
        std::unique_ptr<KLocalizedContext> klocalized;

        KLocalizedContextHolder(QObject *parent) : klocalized(new KLocalizedContext(parent)) {}
    };
}}
Enter fullscreen modeExit fullscreen mode

We use the cpp! macro to include all the headers and define the struct.

Define Rust Struct

We now need to define a rust struct for KLocalizedContext, which refers to our Holder struct in C++. We use the cpp_class! macro for this:

cpp_class!(pub unsafe struct KLocalizedContext as "KLocalizedContextHolder");
Enter fullscreen modeExit fullscreen mode

The as "datatype" part represents the C++ type our rust type/data refers to.

Implement members

Finally we can now implement the methods we want on this struct. Currently, I just want to register KLocalizedContext so that I can use the methods like i18n from QML. So the implementation is given below:

impl KLocalizedContext {
    pub fn init_from_engine(engine: &QmlEngine) {
        let engine_ptr = engine.cpp_ptr();
        cpp!(unsafe [engine_ptr as "QQmlEngine*"] {
            engine_ptr->rootContext()->setContextObject(new KLocalizedContext(engine_ptr));
        });
    }
}
Enter fullscreen modeExit fullscreen mode

We have to use a closure in the cpp! macro to execute the instructions we want to perform.

Example Usage

Using this is pretty straightforward. We need to initialize KLocalizedContext after creating the engine. Here is an example:

use cstr::cstr;
use qmetaobject::prelude::*;
use ki18n_rs::KLocalizedContext;

fn main() {
  let mut engine = QmlEngine::new();
  KLocalizedContext::init_from_engine(&engine);
  engine.load_data(r#"
    import QtQuick 2.6
    import QtQuick.Controls 2.0 as Controls
    import QtQuick.Layouts 1.2
    import org.kde.kirigami 2.13 as Kirigami

    // Base element, provides basic features needed for all kirigami applications
    Kirigami.ApplicationWindow {
        // ID provides unique identifier to reference this element
        id: root

        // Window title
        // i18nc is useful for adding context for translators, also lets strings be changed for different languages
        title: i18nc("@title:window", "Hello World")

        // Initial page to be loaded on app load
        pageStack.initialPage: Kirigami.Page {

            Controls.Label {
                // Center label horizontally and vertically within parent element
                anchors.centerIn: parent
                text: i18n("Hello World!")
            }
        }
    }
  "#.into());
  engine.exec();
}
Enter fullscreen modeExit fullscreen mode

Conclusion

I have publised this crate on crates.io as ki18n-rs. I will be exposing more of the C++ API when I get the time. However, since I haven't used KI18n in the past, pull requests and issues on Github will be extremely valuable. If possible, I would like to make the usage of KI18n from Rust as painless as possible.


Recommend

  • 36
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    Qml 快速使用

    点击上方蓝字可直接关注!方便下次阅读。如果对你有帮助,可以点个在看 或点个赞,感谢~ 这周简单的了解了下Qt的 qml 。个人对它的定位就是可以方便快速地绘制一些精美的 UI ,对快速...

  • 11
    • doc.qt.io 3 years ago
    • Cache

    Integrating QML and C++

    Integrating QML and C++ QML applications often need to handle more advanced and performance-intensive tasks in C++. The most common and quickest way to do this is to expose the C++ class to the QML runtime, provided th...

  • 3
    • jaredtao.github.io 3 years ago
    • Cache

    玩转Qml(17)-树组件的定制

    最近遇到一些需求,要在Qt/Qml中开发树结构,并能够导入、导出json格式。 于是我写了一个简易的Demo,并做了一些性能测试。 在这里将源码、实现原理、以及性能测试都记录、分享出来,算是抛砖引玉吧,希望有更多人来讨论、交流。 TreeEdi...

  • 5
    • jaredtao.github.io 3 years ago
    • Cache

    玩转Qml(18)-用户向导

    玩转Qml(18)-用户向导 很多现代化的软件,都会有向导功能,相信大家并不陌生。 “用户向导”的作用,可以帮助新用...

  • 5

    How to create and use C ++ objects in QML Javascript advertisements My app uses both c++ and QML. I've defined several objects in C++ p...

  • 7
    • www.mycode.net.cn 3 years ago
    • Cache

    Qt Quick QML MouseArea 事件穿透

    MouseArea 是 QML 中一个不可见的鼠标操作区域,可响应所有鼠标事件。一般情况下在自定义按钮、自定义需要鼠标交互的区域时使用。有时你只需要它的 hover 通知来做一些事情,而另外的点击等操作需要传递给其下层的控件,这时你就需要忽略其自身的...

  • 4

    Introduction I have been a KDE user for a long time and have been getting into contributing to KDE. I also love Rust Programming Language. With the release of GTK4, the Rust bindings for GTK have become official and are prett...

  • 2

    Open Source QML Web Browser CANONIC Downloading & Compiling (65%) ...

  • 6
    • embeddeduse.com 2 years ago
    • Cache

    Developing a QML Keypad with TDD

    Although Qt comes with a unit-test framework QuickTest for QML code, hardly any QML GUIs are unit-tested, let alone developed with TDD (test-driven development). One reason is that many developers regard unit tests for GUI code as a waste of...

  • 7

    基于 PyQt5/PySide2 和 QML 的跨平台 GUI 程序开发 范叶亮 / 2018-05-27 分类: 编程 / 标签: Qt, PyQt...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK