Nmaster88
Nmaster88

Reputation: 1605

Dynamic translation of combobox qml

I've added translation to my qt/qml app using this tutorial

https://retifrav.github.io/blog/2017/01/04/translating-qml-app/ https://github.com/retifrav/translating-qml

And most seems to work well except that the values of the combobox dont get update with dynamic translate. Im using qt 5.11.2.

By a combobox i mean this:

ComboBox {
    textRole: "text"
    Layout.fillWidth: true
    model: ListModel {
        Component.onCompleted: {
            append({text: qsTr("None")})
            append({text: qsTr("Subpanel")})
            append({text: qsTr("All")})
        }
    }
}
ComboBox {
    textRole: "text"
    Layout.fillWidth: true
    model: ListModel {
            ListElement{text: qsTr("None")}
            ListElement{text: qsTr("Subpanel")}
            ListElement{text: qsTr("All")}
    }
}

None of them gets updated. I've done some research and found this on bug reports https://bugreports.qt.io/browse/QTBUG-68350 This seems to get fixed on 5.12, but for various reason we need to keep the same version, is there a way i can fix it for this version? (5.11.2)

EDIT: I dont find a way to translate comboBox. Is there another way of doing translations? even if it means to open a new instance of the app? Can someone point me to a link? Can't find a way to do this.

EDIT2: Is there a way to force the model of the combobox to be updated by javascript? when the changeLanguage() method is called?

note: As a complaint i'm finding the support / community to get answers for Qt problems terrible, really bad, but maybe its my problem.

Upvotes: 2

Views: 2153

Answers (4)

trin94
trin94

Reputation: 212

There now exists a small example in the Qt Documentation:

// Used as an example of a backend - this would usually be
// e.g. a C++ type exposed to QML.
QtObject {
    id: backend
    property int modifier
}
ComboBox {
    textRole: "text"
    valueRole: "value"

    model: [
        { value: Qt.NoModifier, text: qsTr("No modifier") },
        { value: Qt.ShiftModifier, text: qsTr("Shift") },
        { value: Qt.ControlModifier, text: qsTr("Control") }
    ]

    // When an item is selected, update the backend.
    onActivated: backend.modifier = currentValue

    // Set the initial currentIndex to the value stored in the backend.
    Component.onCompleted: currentIndex = indexOfValue(backend.modifier)
}

Upvotes: 0

Felix
Felix

Reputation: 7146

This is an addition to @Amfasis answer. It extends the very useful "baseEnum" model by adding the capability to detect and react to restranslation events


For the GUI to actually detect that the text was changed after a restranslation, the model must "notify" the gui that data has changed. However, to do that, the model must know when data changed. Thankfully, Qt has the LanguageChange event to do so. The following code catches that event and uses it to notify the gui of the data change.

// in the header file:

class baseEnum : public QAbstractListModel
{
    Q_OBJECT

public:
    // ... all the stuff from before

    bool event(QEvent *event);
};

// and in the cpp file:

bool baseEnum::event(QEvent *ev)
{
    if(ev) {
        switch(ev->type()) {
        case QEvent::LanguageChange:
            // notifiy models that the display data has changed for all rows
            emit dataChanged(index(0),
                             index(rowCount(QModelIndex{}) - 1),
                             {Qt::DisplayRole});
            break;
        }
    }

    return QAbstractListModel::event(ev);
}

Upvotes: 2

Andrii
Andrii

Reputation: 1906

Unfortunately, I can't test all the cases at the moment (and I don't have an access to the Qt 5.11.2), but I think this should work:

    ComboBox {
        textRole: "text"
        Layout.fillWidth: true

        displayText: qsTr(currentText)

        model: ListModel {
            ListElement{text: "None"}
            ListElement{text: "Subpanel"}
            ListElement{text: "All"}
        }
        delegate: Button {
            width: ListView.view.width
            text: qsTr(model.text)
        }
    }

Or, another way is to re-create a model when language is changed:

    ComboBox {
        id: combobox
        textRole: "text"
        Layout.fillWidth: true

        model: ListModel {
            dynamicRoles: true
        }

        Component.onCompleted: {
            reload()
        }

        Connections {
            target: trans // this is a translator from a git project you are referring to
            onLanguageChanged: {
                combobox.reload()
            }
        }

        function reload() {
            var i = combobox.currentIndex
            combobox.model = [
                        {text: qsTr("None")},
                        {text: qsTr("Subpanel")},
                        {text: qsTr("All")}
                    ]
            combobox.currentIndex = i
        }
    }

Upvotes: 1

Amfasis
Amfasis

Reputation: 4208

One option is to add a QAbstracstListModel which does the translation. I made myself a base class, which can be inherited. This also gives you a lot of flexibility for converting a selected item to a value (in this example I am using int, but you can make it anything), which is connected to a C++ backend (I used backend.selectedPanel for your example)

<< Edit: See answer of Felix for nice addition of dynamic translation >>

base header:

class baseEnum : public QAbstractListModel
{
    Q_OBJECT

public:
    virtual int rowCount(const QModelIndex &parent) const = 0;
    virtual QVariant data(const QModelIndex &index, int role) const = 0;
    QHash<int, QByteArray> roleNames() const;

    Q_INVOKABLE int getIndex(int value);
    Q_INVOKABLE int getValue(int index);
}

base cpp:

QHash<int, QByteArray> baseEnum::roleNames() const
{
    QHash<int, QByteArray> result;
    result.insert(Qt::DisplayRole, "text");
    result.insert(Qt::UserRole + 1, "value");
    return result;
}

int baseEnum::getIndex(int value)
{
    for(int i=0;i<rowCount(QModelIndex());++i)
        if(data(createIndex(i, 0, nullptr), Qt::UserRole + 1).toInt() == value)
            return i;

    return -1;
}

int baseEnum::getValue(int index)
{
    return data(createIndex(index, 0, nullptr), Qt::UserRole + 1).toInt();
}

derived header:

class FancyEnum : public baseEnum
{
    Q_OBJECT

public:
    int rowCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
};

derived cpp:

int FancyEnum::rowCount(const QModelIndex &parent) const
{
    if(!parent.isValid())
        return 5;

    return 0;
}

QVariant FancyEnum::data(const QModelIndex &index, int role) const
{
    switch(index.row())
    {
    case 0: return role == Qt::DisplayRole ? QVariant(tr("None"))     : QVariant(0);
    case 1: return role == Qt::DisplayRole ? QVariant(tr("Subpanel")) : QVariant(1);
    case 2: return role == Qt::DisplayRole ? QVariant(tr("All"))      : QVariant(2);
    }

    return role == Qt::DisplayRole ? QVariant(QString("<%1>").arg(index.row())) : QVariant(0);
}

Register it somewhere:

qmlRegisterType<FancyEnum>("your.path.here", 1, 0, "FancyEnum");

usage in QML:

ComboBox {
    model: FancyEnum { id: myEnum }
    textRole: "text"
    currentIndex: myEnum.getIndex(backend.selectedPanel) : 0
    onActivated: backend.selectedPanel = myEnum.getValue(index) }
}

Upvotes: 2

Related Questions