silvansky programming stuff

23Nov/100

QML WhealArea

источник: https://github.com/explicitcall/QMLbox
что: QML-элемент WheelArea, дополнение к стандартному MouseArea. реагирует на mouse wheel.
чтобы юзать, регистрируем и импортируем стандартным образом.

#ifndef WHEELAREA_H
#define WHEELAREA_H

#include <QDeclarativeItem>
#include <QGraphicsSceneWheelEvent>

class WheelArea : public QDeclarativeItem
{
    Q_OBJECT

public:
    explicit WheelArea(QDeclarativeItem *parent = 0) : QDeclarativeItem(parent) {}

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) {
        switch(event->orientation())
        {
            case Qt::Horizontal:
                emit horizontalWheel(event->delta());
                break;
            case Qt::Vertical:
                emit verticalWheel(event->delta());
                break;
            default:
                event->ignore();
                break;
        }
    }

signals:
    void verticalWheel(int delta);
    void horizontalWheel(int delta);
};

#endif // WHEELAREA_H
Tagged as: , , , No Comments
16Nov/100

qml & multiline elide

иногда нужно обрезать длинную строку и поставить в конце многоточие... для этого в QML у текста есть свойство elide. чтобы его задействовать нужно задать ширину текста.

а что если мы хотим вывести текст в 2 строки? и при этом естественно обрезать лишнее и поставить многоточие?...
тогда elide бессилен.

немного результатов моих мучений с данным вопросом: рабочая функция многострочного илайда!

сразу перейдём к коду.

Text {
    id: tempText
    visible: false
    width: parent.width
    wrapMode: Text.WordWrap
    function elideMultiline(text, font, linesCount) {
        tempText.font = font
        var l = text.length
        var s = "";
        for (var i = 0; i < linesCount; i++) {
            s += "W";
            if (i < linesCount - 1)
                s += "\n"
        }
        tempText.text = s
        var maxHeight = tempText.paintedHeight
        tempText.text = text
        while (tempText.paintedHeight > maxHeight) {
            tempText.text = text.substring(0, --l) + "..."
        }
        return tempText.text
    }
}
Text {
    id: longText
    width: parent.width
    wrapMode: Text.WordWrap
    font.pointSize: 16
    Component.onCompleted: {
        text = tempText.elideMultiline("my very-very-very-very and very long text with a lot of words in it and which i want to be correctly elided", font, 2)
    }
}

как видно из кода, создаётся дополнительный элемент Text и делается невидимым. он используется для определения размеров текста на экране. для этого элемента определяем функцию elideMultiline, в неё передаём текст, шрифт и нужное нам количество строк текста.

далее всё просто, простой линейный перебор (сокращаем строку на 1 символ, дописываем "...", проверяем, опять сокращаем.......)

в принципе всё работает, но когда нужно вызвать эту функцию много раз, получаются тормоза. как дойдут руки, переделаю из линейного поиска в метод золотого сечения.

Замечание. Почему-то не хочет работать text: elideMultiline(), поэтому используется событие готовности компонента и в нём делается присвоение. Если сделать всё же первый вариант, то всё будет тормозить и выдавать кучу предупреждений "QML Text: Binding loop detected for property "text""

Tagged as: , , , , No Comments
16Nov/100

<замазано>


тестирую тег span в HTML... не нашёл лучшего места, чем данный жж.
кстати, интересно наверное будут выглядеть посты в таком стиле. неудобочитаемые.

а если серьёзно, то нашёл интересный класс в QtDeclarative, называется QDeclarativeImageProvider. его надо наследовать, реализовать функции requestImage/requestPixmap, зарегистрировать наш провайдер в энджине (QDeclarativeEngine::addImageProvider()) и юзать из QML вот так:

Image {
    source: "image://providerName/imageName.png"
}

теперь можно динамически вставлять картинки в qml, передавая туда лишь строку с "image://...".

Tagged as: , , , No Comments
15Nov/100

qml + qt model/view

итак, теперь серьёзно о QML. загрузить в QML список элементов и отобразить их там - это конечно хорошо, но мало. предположим, у нас есть уже модель данных, сделанная на наследнике QAbstractItemModel и мы хотим отобразить элементы этой модели. причём перед отображением неплохо бы отфильтровать и отсортировать нашу модель.

C++-way: делаем наследника QAbstractItemView, делаем наследника QAbstractItemDelegate (для кастомной отрисовки), делаем наследника QSortFilterProxyModel (для фильтрации и сортировки).

QML-way: делаем наследника QSortFilterProxyModel (для фильтрации и сортировки), делаем в QML ListView (для моделей типа список), регистрируем наш фильтр в QML, назначаем list.model.

всё по прежнему остаётся просто! в принципе, надо лишь правильно создать и заполнить свою модель и прокси-модель.

Tagged as: , , , , No Comments
11Nov/100

qt + qml

итак, я немного разобрался с использованием QML. в кратце состав моего тестового проекта:

А. просто в QML:

1. создать скроллабл лист из итемов с кастомным делегатом отрисовки и списком итемов для отображения
2. реализовать выделение итемов по лику мыши и активацию их по повторному клику или нажатию на enter

Б. C++/QML

1. встроить объект из кода C++ в контекст QML
2. вызвать нужный метод этого объекта из QML
3. использовать сигналы этого объекта в QML
4. использовать в списке итемы, сгенерированные кодом на C++
5. встроить QDeclarativeView в окно программы (т.е., в существующий пользовательский интерфейс) и реализовать их взаимодействие

итак, код.

main.qml - реализация на QML

import Qt 4.7
import silvansky 1.0

Rectangle {
    width: 360
    height: 600
    gradient: Gradient {
        GradientStop {
            position: 0
            color: "#ffffff"
        }

        GradientStop {
            position: 0.48
            color: "#ffffff"
        }

        GradientStop {
            position: 1
            color: "#88e9ec"
        }
    }
    Component {
        id: contactDelegate
        Item {
            function activated(ci) {
                console.log("item #" + list1.currentIndex + " activated! (" + cl1.contacts[ci].name + " the " + cl1.contacts[ci].type + ")")
            }

            MouseArea {
                id: ma1
                width: 360
                height: 60
                onClicked: {
                    var ci = list1.indexAt(parent.x + mouse.x, parent.y + mouse.y)
                    if (list1.currentIndex == ci) {
                        activated(ci)
                    }
                    else {
                        list1.currentIndex = ci
                    }
                }
            }
            Keys.onSelectPressed: {
                activated(list1.currentIndex)
            }
            Keys.onEnterPressed: {
                activated(list1.currentIndex)
            }
            Keys.onReturnPressed: {
                activated(list1.currentIndex)
            }
            Keys.onEscapePressed: {
                console.log("ESCAPE!!!!!! AAAA!!!")
                interactor.escapePressed()
            }
            width: parent.width
            height: 60
            Column {
                Text {
                    height: 60
                    text: name + " (" + type + ")"
                    color: (type == "admin") ? "red" : ((type == "boss") ? "green" : "blue")
                    font.pointSize: 12
                    verticalAlignment: Text.AlignVCenter
                }
            }
        }
    }
    ListView {
        id: list1
        anchors.fill: parent
        //model: ContactsModel {}
        model: cl1.contacts
        delegate: contactDelegate
        highlight: Rectangle { color: "#00DDDD"; radius: 5 }
        highlightMoveDuration: 400
        highlightMoveSpeed: -1
        focus: true
    }
    Connections {
        target: interactor
        onMoveUp: {
            list1.currentIndex--
        }
        onMoveDown: {
            list1.currentIndex++
        }
    }
}

немного пояснений. сначала мы импортируем модуль Qt, затем мой модуль.

основа всего - главный объект (root object), который в данном случае просто прямоугольник размерами 360х600 (размер я выбирал такой чтоб на мобильнике можно было тестить) и с заливкой в виде градиента.

у нашего прямоуголльника есть дочерние объекты. первый из них - делегат для отрисовки итема в списке. в нём есть функция, которая вызывается при активировании итема, MouseArea для обработки нажатий мышью и обработчики нажатий на Enter/Return/Select/Escape. высота итема фиксирована (60px), ширина берётся как у родителя. далее мы просто выводим текст (предполагается, что у элемента списка есть два свойства: name и type), выделяя некоторые итемы цветом.

второй дочерний объект - собственно список (ListView). у него выставляем id, говорим, что будем заполнять родителя собой, устанавливаем модель и делегат, устанавливаем параметры подсветки элементов.

тут заметим, что модель мы берём из объекта cl1, который не объявлен. если раскомментить предыдущую строку, то будем брать модель из файла ContactsModel.qml, который содержит примерно следующее:

import Qt 4.7

ListModel {
    id: lm1
    ListElement {
        name: "Good Guy"
        type: "admin"
    }
    ListElement {
        name: "Vlad Pukin"
        type: "ex-boss"
    }
    ListElement {
        name: "Ramonitos"
        type: "boss"
    }
    ListElement {
        name: "J. Doolitle"
        type: "ex-boss"
    }
    ListElement {
        name: "Borak Ohama"
        type: "boss"
    }
    ListElement {
        name: "Vasya Pupkin"
        type: "lamer"
    }
    ListElement {
        name: "Utilkin"
        type: "user"
    }
    // and so on...
}

но мы не будем его использовать.

итак, следующий объект - Connections, в данном случае он используется для подключения сигналов из объектов C++ к функциям в QML. здесь interactor - объект, который будет объявлен в коде на C++.

итак, с QML разобрались. фух... теперь пойдёт интересное. создаём программу на C++/Qt, создаём там классы Contact, ContactList и QmlInteractor, создаём форму TestQmlView (наследуем от виджета, например). в форму запихиваем QDeclarativeView и тулбар, на который ставим две кнопки - Up и Down, которые будут перемещать выделение в списке.

вот примерно что должно получиться:

testqmlview.h

#ifndef TESTQMLVIEW_H
#define TESTQMLVIEW_H

#include <QWidget>
#include <QDeclarativeView>
#include <QToolBar>

namespace Ui
{
    class TestQmlView;
}

class TestQmlView : public QWidget
{
    Q_OBJECT

public:
    explicit TestQmlView(QWidget *parent = 0);
    ~TestQmlView();
    void setQml(const QUrl & url);
    QDeclarativeView * declarativeView() const
    {
        return dview;
    }

protected:
    void changeEvent(QEvent *e);
signals:
    void goUp();
    void goDown();

private:
    Ui::TestQmlView *ui;
    QDeclarativeView * dview;
    QToolBar * toolbar;
    QAction * up;
    QAction * down;
};

#endif // TESTQMLVIEW_H

contact.h

#ifndef CONTACT_H
#define CONTACT_H

#include <QObject>

class Contact : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged)
public:
    Contact();

    // props

    // name
    QString name() const
    {
        return _name;
    }
    void setName(const QString newName)
    {
        _name = newName;
    }
    // type
    QString type() const
    {
        return _type;
    }
    void setType(const QString newType)
    {
        _type = newType;
    }
signals:
    void nameChanged(const QString newName);
    void typeChanged(const QString newType);

public slots:
private:
    QString _name;
    QString _type;
};

#endif // CONTACT_H

contactlist.h

#ifndef CONTACTLIST_H
#define CONTACTLIST_H

#include <QObject>
#include "contact.h"
#include <QList>
#include <QDeclarativeListProperty>

class ContactList : public QObject
{
    Q_OBJECT
public:
    ContactList();

    Q_PROPERTY(QDeclarativeListProperty<Contact> contacts READ contacts CONSTANT)
    QDeclarativeListProperty<Contact> contacts()
    {
        return QDeclarativeListProperty<Contact>(this, _contacts);
    }
signals:

public slots:
private:
    QList<Contact*> _contacts;
};

#endif // CONTACTLIST_H

qmlinteractor.h

#ifndef QMLINTERACTOR_H
#define QMLINTERACTOR_H

#include <QObject>

class QmlInteractor : public QObject
{
    Q_OBJECT
public:
    QmlInteractor();
    Q_INVOKABLE void escapePressed();

signals:
    void moveUp();
    void moveDown();

public slots:

};

#endif // QMLINTERACTOR_H

в принципе тут ничего сложного. просто объекты со свойствами, объявленными через Q_PROPERTY. для свойств типа QList нужно использовать шаблон-враппер QDeclarativeListProperty<>, использование показано в коде.

осталось в конструкторе ContactList'а заполнить как-либо список _contacts. у меня это сделано так:

#include "contactlist.h"
#include <QDebug>
#include <time.h>

ContactList::ContactList() :
    QObject(0)
{
    qDebug() << "ContactList created!";
    QList<QString> names;
    QList<QString> types;
    names << "Name1" << "Vasya" << "petya" << "good guy" << "bad guy" << "youmil" << "rutta" << "hooker";
    types << "admin" << "user" << "boss" << "ex-boss" << "lamer";
    qsrand(time(NULL));
    for (int i = 0; i < 50; i++)
    {
        Contact * c = new Contact;
        c->setName(names.at(qrand() % names.count()));
        c->setType(types.at(qrand() % types.count()));
        _contacts.append(c);
    }
}

далее пошло самое интересное. итак, встречайте! файл main.cpp!

#include <QtGui/QApplication>
#include <QtDeclarative>
#include <QDebug>
#include "testqmlview.h"
#include "qmlinteractor.h"
#include "contact.h"
#include "contactlist.h"

void registerQmlTypes()
{
    static const char * uri = "silvansky";
    qmlRegisterType<QmlInteractor>(uri, 1, 0, "QmlInteractor");
    qmlRegisterType<Contact>(uri, 1, 0, "Contact");
    qmlRegisterType<ContactList>(uri, 1, 0, "ContactList");
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    TestQmlView view;
    registerQmlTypes();
    ContactList * cl = new ContactList;
    QmlInteractor * interactor = new QmlInteractor;
    view.declarativeView()->rootContext()->setContextProperty("cl1", cl);
    view.declarativeView()->rootContext()->setContextProperty("interactor", interactor);
    QObject::connect(&view, SIGNAL(goUp()), interactor, SIGNAL(moveUp()));
    QObject::connect(&view, SIGNAL(goDown()), interactor, SIGNAL(moveDown()));
    view.setQml(QUrl("qml/qtquicktest/main.qml"));
#if defined(Q_OS_SYMBIAN) || defined(Q_OS_SIMULATOR)
    view.showFullScreen();
#else
    view.show();
    view.adjustSize();
#endif
    return app.exec();
}

разбираем по порядку.

первая функция регистрирует три типа для использования в QML - Contact, ContactList и QmlInteractor

теперь читаем внимательно, ибо порядок строк важен ;)
создаём view (у него в конструкторе создаётся QDeclarativeView), затем создаём контактлист и интерактор.
а теперь фокус-покус! у QDeclarativeView берём rootContext и у ему делаем setContextProperty()! этот финт ушами я долго пытался найти. что это даёт? да то, что теперь наши объекты cl и interactor доступны в QML под id cl1 и interactor и наш QML-код из main.qml перестаёт быть бессмысленным.

после коннектов (не буду их пояснять) мы в наш QDeclarativeView загружаем наш файл main.qml и показываем наше окошко.

всё, компилим, запускаем, наслаждаемся!
главное не забыть в .pro файле написать следующие строчки:

QT += declarative
symbian: {
    qmlfiles.sources = qml/qtquicktest/*.qml
    qmlfiles.path = ./qml/qtquicktest
    DEPLOYMENT += qmlfiles
}

послесловие или "зачем это надо?"

вообще, перенести часть мобильного проекта на QML мне захотелось после попытки сделать кинетик скролл для QListView. разумеется, я не стал писать его с нуля, а воспользовался готовым классом FlickCharm из Anomaly demo, немного его подточив. но при этом всё стало плохо - тормоза, глюки, трудно нажать на элемент, ну и т.д.... особенно сильные тормоза были при натягивании дизайна через QSS.

а вот в QML элемент ListView сам по себе умеет кинетик скроллинг делать. быстро, красиво и удобно. и много разных других фишек (например, плавное перемещение фокуса). потестив немного это всё я пришёл к выводу, что на QML всё должно работать быстрее. пока что остановился только на переводе списков на QML, потом подумаю над тем чтобы весь UI на него перевести.

Tagged as: , , , No Comments