silvansky programming stuff

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
19Apr/100

c++ ext

было бы неплохо иметь расширение C++ in:

int i = getI();
if (i in (1, 5, 78, 455))
{
    process(i);
}
else
{
    discard(i);
}

Tagged as: No Comments
17Mar/100

инструкция: как выучить C++ за 21 день

http://abstrusegoose.com/249

12Mar/100

caps lock

как программно определить, включен ли caps lock?

решение платформозависимо.

Windows:


#include <windows.h>

int i = GetKeyState(VK_CAPITAL);
bool caps_on = (i == 1);

что угодно, но с иксами (X11), будь то Linux, Unix, etc...:
(тянет за собой libx11-dev, требует -lX11)

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>

Display * Dpy = XOpenDisplay((char*)0);
bool caps_on = false;
if (Dpy)
{
    unsigned n;
    XkbGetIndicatorState(Dpy, XkbUseCoreKbd, &n);
    caps_on = (n & 0x01) == 1;
}

UPD: как показал опыт, в иксах включать достаточно XKBLib.h. при этом если это юзать в QT, то возникают глюки использования QEvent (см. X11/X.h):

#define KeyPress                2
#define KeyRelease              3
#define ButtonPress             4
#define ButtonRelease           5
#define MotionNotify            6
#define EnterNotify             7
#define LeaveNotify             8
#define FocusIn                 9
#define FocusOut                10
#define KeymapNotify            11
#define Expose                  12
#define GraphicsExpose          13
#define NoExpose                14
#define VisibilityNotify        15
#define CreateNotify            16
#define DestroyNotify           17
#define UnmapNotify             18
#define MapNotify               19
#define MapRequest              20
#define ReparentNotify          21
#define ConfigureNotify         22
#define ConfigureRequest        23
#define GravityNotify           24
#define ResizeRequest           25
#define CirculateNotify         26
#define CirculateRequest        27
#define PropertyNotify          28
#define SelectionClear          29
#define SelectionRequest        30
#define SelectionNotify         31
#define ColormapNotify          32
#define ClientMessage           33
#define MappingNotify           34
#define LASTEvent               35      /* must be bigger than any event # */

это конфликтует с этим (см. qcoreevent.h):

enum Type {
        /*
          If you get a strange compiler error on the line with None,
          it's probably because you're also including X11 headers,
          which #define the symbol None. Put the X11 includes after
          the Qt includes to solve this problem.
        */
        None = 0,                               // invalid event
        Timer = 1,                              // timer event
        MouseButtonPress = 2,                   // mouse button pressed
        MouseButtonRelease = 3,                 // mouse button released
        MouseButtonDblClick = 4,                // mouse button double click
        MouseMove = 5,                          // mouse move
        KeyPress = 6,                           // key pressed
        KeyRelease = 7,                         // key released
        FocusIn = 8,                            // keyboard focus received
        FocusOut = 9,                           // keyboard focus lost
        Enter = 10,                             // mouse enters widget
        Leave = 11,                             // mouse leaves widget
        Paint = 12,                             // paint widget
        Move = 13,                              // move widget
        Resize = 14,                            // resize widget
        Create = 15,                            // after widget creation
        Destroy = 16,                           // during widget destruction
        Show = 17,                              // widget is shown
        Hide = 18,                              // widget is hidden
        Close = 19,                             // request to close widget
        Quit = 20,                              // request to quit application
        ParentChange = 21,                      // widget has been reparented
        ParentAboutToChange = 131,              // sent just before the parent change is done
// ... etc ...

в частности в пунктах FocusIn, FocusOut, KeyPress...
так что если юзать указанный мной подход, надо делать #undef для конфликтующих имён.

#include <X11/XKBlib.h>
#undef KeyPress
#undef FocusIn
// ... etc ...

так что этот подход лишает нас возможности юзать события иксов в QT. что ж, не очень-то и хотелось ))

UPD 2: а вообще лучше это вынести в отдельный файл, тогда конфликтов не будет.