silvansky programming stuff

12Nov/100

qt creator hint/hack

если в режиме дизайна форм в криейторе создать (кинуть на форму) некий виджет, который мы хотим использовать как контейнер, то нельзя ему сразу назначить компоновщик (layout), приходится это делать из кода.

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

всё, имеем пустой виджет с компоновщиком.

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
7Oct/100

сигнал о входящем/исходящем телефонном вызове в Qt под Symbian

в связи с тем, что из QtMobility 1.1 исключили модуль Telephony (анонсированный в 1.1-tp) я немного покопался в инете и нашёл таки способ как поймать начало и конец входящего и исходящего звонков. Осталось поймать смс-ки ;)

итак, средствами Qt этого не сделать. смиритесь. придётся написать немного кода на родном API симбиана.

итак, класс мы назовём CallWatcher. наследовать его мы будем от QObject (чтобы слать сигналы) и от CActive (это уже симбиан, технология Active Object).

  1. class CallWatcher : public QObject, public CActive
  2. {
  3.   Q_OBJECT
  4. public:
  5.   CallWatcher();
  6.   ~CallWatcher();
  7.   void RequestNotification();
  8. private:
  9.   void RunL();
  10.   void DoCancel();
  11. signals:
  12.   void callStarted();
  13.   void callEnded();
  14. private:
  15.   CTelephony* iTelephony;
  16.   CTelephony::TCallStatusV1 iLineStatus;
  17.   CTelephony::TCallStatusV1Pckg iLineStatusPckg;
  18.   bool started;
  19. };

* This source code was highlighted with Source Code Highlighter.

собственно, суть в том, что мы будем ждать сообщения (события, нотификатора, кому как нравится) от объекта класса CTelephony, входящего в библиотеку etel3dparty.

не забываем про инклюды!

#include <e32base.h>
#include <Etel3rdParty.h>
#include <QObject>

* This source code was highlighted with Source Code Highlighter.

первый - для CActive и иже с ним, второй для CTelephony.

теперь пишем сам код:

  1. CallWatcher::CallWatcher(): CActive(EPriorityStandard), QObject(NULL), iLineStatusPckg(iLineStatus)
  2. {
  3.   CActiveScheduler::Add(this);
  4.   iLineStatus.iStatus = CTelephony::EStatusUnknown;
  5.   iTelephony = CTelephony::NewL();
  6.   RequestNotification();
  7.   started = false;
  8. }
  9.  
  10. CallWatcher::~CallWatcher()
  11. {
  12.   delete iTelephony;
  13. }
  14.  
  15. void CallWatcher::RequestNotification()
  16. {
  17.   _LIT(KCallWatcherPanic, "CallWatcher");
  18.   __ASSERT_ALWAYS(!IsActive(), User::Panic(KCallWatcherPanic, 1));
  19.  
  20.   iTelephony->NotifyChange(iStatus, CTelephony::EVoiceLineStatusChange, iLineStatusPckg);
  21.   SetActive();
  22. }
  23.  
  24. void CallWatcher::RunL()
  25. {
  26.   if(iStatus == KErrNone)
  27.   {
  28.     if (!started && (iLineStatus.iStatus != CTelephony::EStatusIdle))
  29.     {
  30.       started = true;
  31.       emit callStarted();
  32.     }
  33.     else if (iLineStatus.iStatus == CTelephony::EStatusIdle)
  34.     {
  35.       started = false;
  36.       emit callEnded();
  37.     }
  38.     RequestNotification();
  39.   }
  40. }
  41.  
  42. void CallWatcher::DoCancel()
  43. {
  44.   iTelephony->CancelAsync(CTelephony::EVoiceLineStatusChangeCancel);
  45. }
  46.  

* This source code was highlighted with Source Code Highlighter.

в конструкторе нашего класса мы сначала добавляем наш объект (CActive*) к шедулеру, т.е., запускаем его на прослушку сообщений.
Затем выставляем статус линии в "неизвестный", создаём объект CTelephony (неудобоваримо на первый взгляд, но так принято в симбиане - двухэтапные конструкторы и прочее), запрашиваем события и помечаем, что разговор не начат ещё.

в функции RequestNotification() мы делаем проверку на валидность (точнее, на активность себя же), а затем запрашиваем у нашего iTelephony нотификатор изменения статуса линии, а затем активизируем себя.

функция RunL() будет вызвана когда придёт нужное нам сообщение. в ней мы проверяем статус на ошибки, и если таковых нет, делаем всё совсем стандартно (не охота подробно комментировать), главное - статус линии хранится в iLineStatus.iStatus. а затем просто запрашиваем следующее событие. так уж сделано там - событие пришло, значит больше не нужно. немного нелогично на мой взгляд, но видимо у разработчиков были свои причины на это.

ну, и для финала делаем функцию отмены DoCancel(), в которой просто отменяем запрос.

всё, теперь можно компилить (не забываем про LIBS += -letel3rdparty в .pro файле) и тестить. в нужный нам момент (входящий или исходящий вызов начат или завершён) мы получим соответствующие сигналы.

стоит отметить, что для использования CTelephony нужны особые капабилитиз - TARGET.CAPABILITY += NetworkServices. но это не помешает тестировать прогу с самоподписанным сертификатом.

Tagged as: , , , , No Comments
5Oct/100

Q_OBJECT special

занятная фишка: нельзя объявлять классы-наследники от QObject вне хедеров (если мы хотим использовать сигналы-слоты). если объявить такой класс в .cpp-шнике, то линковщик будет ругаться на vtable. ибо qmake плохо обрабатывает .cpp-шники...

UPD: один хороший человек подсказал солюшн: надо в конце .cpp-шника сделать #include "myfile.moc". я не проверял, но говорят, что работает.

Tagged as: , , No Comments
4Oct/100

QMenuBar, symbian

Лучше не пытайтесь кастомизировать стандартные меню (те, которые вешаются на софткеи) симбиана с помощью stylesheet'ов! Я вот попытался. В итоге вместо стандартных меню получил пустоту (просто не отрисовались они), при попытке нажать на них клик передаётся окну под моим.

В общем, для реализации своего меню со своим дизайном пришлось создавать QToolBar + QToolButton'ы с меню. Смотрится гораздо лучше. А хоткеи навесить легко с помощью QAction'ов.

16Sep/100

good news, everyone!

в новой версии qt creator'а (2.1) будет универсальная подсветка синтаксиса, основанная на втсроенной в Kate. под линуксом можно просто поставить Kate (если не под кедами) и криейтор сам определит где искать xml-файлы с описанием подсветки. под виндой можно скачать Kate с гиториуса и указать в настройках криейтора папку \part\syntax\data\. и всё работает. хотя пока что это в снэпшот-версии (2.0.90).

Tagged as: , No Comments
28Aug/100

qt creator 2.0.1

ура! новый криейтор позволяет убирать этап сборки, на котором создаётся .sis файл! теперь в библиотеки не нужно добавлять DEPLOYMENT.

Tagged as: , , No Comments
25Aug/100

qt creator todo plugin

есть хороший плагин для Qt Creator'а, позволяющий легко и красиво отслеживать метки типа TODO, FIXME, NOTE и т.п.

доступен в виде исходников. вот по этой ссылке есть описание, ссылка на скачивание + мои замечания по поводу сборки под виндой. (UPD: автор плагина обещал обновить исходники в соответствии с моими фиксами)

тут ещё одно замечание сделаю: если есть желание использовать с qt creator'ом из qt sdk, то надо собирать плагин с помощью MSVS 2008 (и криейтор тоже), т.к. именно им собран криейтор из сдк. как будет возможность, выложу куда-нибудь бинарник этого дела.

UPD: выложил плагин сюда

18Aug/100

qt symbian plugins

Не знаю, как у вас, а у меня всегда что-то компилится.

так вот, сегодня собираем плагины под симбиан.

если хочешь собрать плагин под симбиан, то надо сделать вот что. в .pro файле плагина пишем так:

TEMPLATE        = lib
CONFIG         += plugin
TARGET          = $$qtLibraryTarget(myplugin)

symbian: {
	load(data_caging_paths)
	TARGET.EPOCALLOWDLLDATA=1
	TARGET.UID3 = 0xXXXXXXXX
	TARGET.CAPABILITY += "LocalServices Location NetworkServices ReadUserData UserEnvironment WriteUserData"
	# dummy deployment (for qt creator)
	plugin.sources = myplugin.dll
	plugin.path = !:/sys/bin
	DEPLOYMENT += plugin
}

UID3 указывается индивидально. например, получить UID можно на сайте SymbianSigned

последние три строки кода нужны для qt creator'а, чтобы он создал .sis файл. нам это не нужно, но криейтор без этого не может.

в принципе, всё. плагин готов (если конечно содержимое у него адекватное). теперь самое простое - деплой вместе с приложением. например, для деплоя плагина в папку !:/Private/XXXXXXXX/plugins (XXXXXXXX - UID приложения), надо в .pro нашей проги написать:

symbian: {
	plugins.sources = myplugin.dll \
			myplugin2.dll
	plugins.path = ./plugins
	DEPLOYMENT += plugins
}

теперь всё готово. можно собирать. искать плагины надо в QApplication::applicationDirPath() + "/plugins". и можно радоваться жизни.

10Aug/100

qt + symbian

итак, чудо из чудес: qt под symbian.
чудо качается в виде Nokia Qt SDK и устанавливается как и обычный qt sdk. при этом всё работает хорошо, даже замечательно - эмулятор, отладка на девайсе... и так далее.
но всё так только до тех пор, пока не потребуется собрать что-то сложное. тут-то и начинается веселье!

замечу, что я до того не имел дела с программированием под симбиан. только qt.

итак!

во-первых, текущая версия Nokia Qt SDK (1.0) не умеет собирать под симбиан проекты с TEMPLATE = subdirs. точнее, что-то как-то собирается, но сплошные ошибки. от этого лучше сразу отказаться.

далее. если хочешь собрать библиотеку, то столкнёшься с такой штукой: qt creator обязательно (!) выполняет в конце make sis. и это не отключить. так что даже если не хочется создавать инсталлятор отдельно для библиотеки, то делать это придётся, если собирать из криейтора. делается это просто, но всё же это излишество.

вот собственно пример:

# экспортируем хедеры
BLD_INF_RULES.prj_exports += utilsexport.h \
                             utils.h
# заглушка для qt creator'а, создаём инсталлятор
myFiles.sources = utils.dll
myFiles.path = !:/sys/bin
DEPLOYMENT += myFiles

итак, теперь у нас есть библиотека, лежит она в \Symbian\SDK\epoc32\release\gcce\udeb\ (или \urel\ если собиралась в релизе). но есть и лишний .sis файл... ну да ладно, на него забьём спокойно.
вообще, на этом этапе может возникнуть ошибка борки. но мы это поправим так (из командной строки с подключенными переменными среды из SDK - в главном меню есть ярлык):

abld freeze

после чего собираем снова - и всё, должно заработать.

заметим так же, что есть такое понятие, как капабилитиз, их надо указать, если проект будет хоть что-то делать с сетью, например. подробнее можно почитать тут.

если что-то не работает, то указываем все капабилити, доступные для самоподписанного приложения:

TARGET.CAPABILITY += "LocalServices Location NetworkServices ReadUserData UserEnvironment WriteUserData"

теперь собираем проект, который эту библиотеку цеплять будет.

в проекте пишем стандартные вещи (LIBS += -lutils), прописываем те же капабилитиз, что и у библиотеки. кроме того, опять терзаем DEPLOYMENT, т.к. автоматически в него добавляются только файлы текущего проекта (да и то только если это не библиотека):

utils.sources = utils.dll
utils.path = !:/sys/bin
DEPLOYMENT += utils

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

ну что ж, если прога относительно простая, то всё готово, можно заливать на девайс и тестить. или воспользоваться Remote Device Access. должно заработать.

в моём случае такой номер прошёл только с самым простым приложением. а вот с портированием сложного началась веселуха...

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

что выясняется? а выясняется, что qt creator ещё слишком сырой для полноценной разработки под симбиан.
dumpsis + makesis сделали дело. всё заработало. вуаля!
поясняю. получили .sis файл, распаковываем его с помощью dumpsis и упаковываем заново с помощью makesis. круто? конечно круто! теперь опять делаем открытое подписывание и всё наконец-то работает!

чудеса техники.

Tagged as: , , , No Comments