Последнее обновление:
August 16, 2020

Есть мысль... Жми, напиши!
Что имеем: Постов : 140 Авторов: 1 Категорий: 22

Модульное тестирование (Unit test) в Qt

И вот наконец-то и мне потребовалось, да не просто для понтов и потому что мейнстрим, а реально пиздецки потребовались тесты, ибо говнокодить над проектом стал не только я один.

И так, имеем проект с подпроектами. Хотим тесты. 

Следуя мануалу Qt нужно создать новый проект Unit test, в нём класс для теста, с помощью мастера создания Qt сделает базовый скелет.

Но вот тут начинаются проблемы. Следуя задумке Qt:

  1. Один проект — один класс для теста. Мда…
  2. С помощью макроса QTEST_MAIN (или QTEST_APPLESS_MAIN) создаётся функция main.
  3. Поэтому каждый проект соответственно компилируется в бинарник
  4. Соответственно каждый тест запускается отдельно и не зависимо от други

Мне кажется такая организация немного не логичной.  

Так как если класс сильно зависит от других  (вообще это не айс, но всякое бывает), то придётся чуть ли не весь основной проект добавлять в инклуды и так каждый тест, ну и если класс использует QML, то тут точно в main нужно прописывать инициализацию qml, установку всех свойств и переменных и т.д., в общем, это у меня сначала и отбило всякое желание делать тесты.

Но так нужно, что бы в QtCreator работала панель «Tests», в которой удобно просматривать результаты тестирования и выборочно запускать сами тесты, вот так это выглядит:

Можно, конечно,  реализовать все классы тестов просто в  отдельной папке проекта (например Tests), убрать все макрос QTEST_MAIN, и запускать все тесты из функции main основного проекта, но тут минус в том, что они будут выполняться при любом запуске проекта, ну и выборочный запуск будет работать через жопу, либо вообще не работать — только все сразу. 

Вот так, например:

void main()
{
    ...
    MyTestClass1 tc1;
    if (QTest::qExec(&tc1, argc, argv)>0) return 42;
    
    MyTestClass2 tc2;
    if (QTest::qExec(&tc2, argc, argv)>0) return 42;
    ...
}
        
        
        

Ну или делать через подключаемые библиотеки и т.д. — кто на сколько головой ударился.

Кому достаточно такого поведения, можно дальше не париться =)

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

Решение такое:

Сделаем наш файл проекта (*.pro) подключаемым/шаблонным (*.pri) что бы его можно было инклудить в тестовые проекты,
для этого в *.pro  файле убираем из SOURCES  main.cpp (её мы будем подключать в тестовые проекты отдельно) и всё из *.pro файла переносим в *.pri файл (сначала создав обычный пустой текстовый файл),
потом в *.pri файле в самом верху прописываем:

VPATH += $$PWD #Что бы файлы подключались относительно оригинального расположения файла
INCLUDEPATH += $$PWD

В *.pro файле основного проекта прописываем:

include(common.pri)  #Подключаем основной класс проекта
SOURCES += main.cpp \  #Как обычно добавим в исходники наш основной main

Ну и теперь осталось изменить немного функцию main, что бы когда она была в основном проекте, то запускалась бы как обычно, а когда была в тестовом проекте, запускала бы тест.

#include <QApplication>
#include <QQmlApplicationEngine>
......

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main/main.qml")));
    .....

   //-- Даём возможность инклудить нас в тесты
    #ifdef MY_TEST
        return runTest(argc, argv);
    #else
        return app.exec();
    #endif
}

С помощью проверки, объявлен ли макрос запускаем на исполнение тест, иначе всё как обычно.

Такой финт ушами нужен, потому что QTestLib сканирует классы на наличие  «QTest::qExec(….)» и, если у нас этого не будет в самом классе, то тест не будет доступен в панельке, поэтому объявление функции запуска будет находиться в самом тестовом классе, а запуск этой функции из main.

Так, теперь как использовать:

В основном проекте создаёте проект с поддиректориями, называете, например «Tests», в этом подпроекте с помощью мастера создания создаёте уже проект «Unit test», например «test1».

В *.pro файле (у нас «test1.pro») тестового проекта прописываем:

include(../../MyProject/common.pri)

И в *.cpp тестовом классе подключаем нашу main функцию для запуска основного проекта и теста:

int runTest(int argc, char *argv[]) //-- Нужно, что бы парсер тестов нашёл этот тест, поэтому запускаем мы его из main
{
    MyTestClassName t;
    return QTest::qExec(&t, argc, argv);
}

#define MY_TEST
#include "../MyProject/main.cpp"

Надеюсь, принцип понятен. Только с относительными путями не наебитесь 😀

Вот теперь всё тестится, управляется, и да же желание тесты писать появилось =)

 

 

 

Views :

1371

Использование интерфейсов классов в Qt и QML

Привет!

Порою удобнее в QML работать именно с интерфейсом класса, а так же иметь возможность засунуть его в QVariant.  

Разумеется простым способом в «лоб» не получится, т.к. Qt в QML работает с QObject, а мы от него не унаследовались и никакой информации для метасистемы не дали.

Долго я копался в недрах метасистемы Qt, уж собирался делать костыли, но наткнулся на макрос Q_DECLARE_INTERFACE.

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

Пишем:

#ifndef IINTERFACE_H
#define IINTERFACE_H

#include <QObject>


class IInterface {
public:
    virtual QString myProperty() const =0;
    virtual void setMyProperty(QString myProperty) =0;
    virtual void myPropertyChanged(QString myProperty) =0;
};

Q_DECLARE_INTERFACE(IInterface, "pavelk.iinterface")


#endif // IINTERFACE_H

Обратите внимание на строку с «Q_DECLARE_INTERFACE» — тем самым мы даём понять метаобъектной системе Qt, что это интерфейс, что бы он прописал необходимые функции по получению исходного класса.

Ну и сам класс:

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

#include "iinterface.h"

class MyClass : public QObject, public IInterface
{
    Q_OBJECT
    Q_INTERFACES(IInterface)
    Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
    explicit MyClass(QObject *parent = nullptr);

    QString myProperty() const;

signals:
    void myPropertyChanged(QString myProperty);

public slots:
    void setMyProperty(QString myProperty);

private:
    QString m_myProperty;
};

#endif // MYCLASS_H

Обратите внимание на строку с «Q_INTERFACES(IInterface)» — тем самым мы даём знать Qt как именно преобразовывать этот класс к указанному интерфейсу. Кстати, можно наследоваться сразу от нескольких интерфейсов — просто пропишите через пробел их все.

Ну и теперь самое интересное. Преобразовываем класс к интерфейсу и передаём этот интерфейс в QML

   MyClass * myClass = new MyClass(0);
   IInterface * interface = myClass;
   engine.rootObjects().at(0)->setProperty("myClassInterface", qVariantFromValue( dynamic_cast<QObject*>(interface) ));

Напрямую в QVariant интерфейс засовывать нельзя, т.к. он не знает необходимой информации, поэтому преобразовываем сначала в QObject*, а благодаря тому, что мы прописал Q_DECLARE_INTERFACE метасистема знает как работать с этим интерфейсом.

Полный код примера тут:  https://github.com/Riflio/QMLInterfaces

Вот как-то так =)

Views :

732

NGINX location alias + php + rewrite (try_files)

Понадобилось мне как-то у домена сделать алиас на совсем другую директорию, которая была вне root, помучался немного и вот что получилось:

Прописываем в секции server {}:

location /bareos-webui/ {
    alias /usr/share/bareos-webui/public/;
    autoindex on;
    try_files $uri $uri/ /bareos-webui//bareos-webui//index.php?$query_string;
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+?\.php)(/.*)?$;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

Вся проблема оказалась в том, что как-то странно работает try_files вместе с alias…, т.е. нужно два раза повторить адрес из location.

Угробил бля 3 часа на эксперименты… Видимо баг самого NGINX.

Views :

678

AVFrame(AVPicture) конвертация в OpenCV::Mat

Понадобилось тут мне сконвертировать  AVFrame в Mat для дальнейших издевательст с помощью OpenCV, загуглил я это дело, и нашёл кучу способов.

Вот один из них:

void AVFrameToMat(const AVFrame * frame, Mat& image)
{
    int width = frame->width;
    int height = frame->height;
    image = Mat(height, width, CV_8UC3);
    int cvLinesizes[1];
    cvLinesizes[0] = image.step1();
    SwsContext* conversion = sws_getContext(width, height, (AVPixelFormat) frame->format, width, height, PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    sws_scale(conversion, frame->data, frame->linesize, 0, height, &image.data, cvLinesizes);
    sws_freeContext(conversion);
}

Мне одному кажется, что столько преобразований излишни?  Ведь OpenCV напрямую позволяет работать со всеми форматами и можно сделать преобразование напрямую! 

В моём случае я работаю с форматом видео YUV420P.

if ( m_picture->format == AV_PIX_FMT_YUV420P ) {
    int numBytes=avpicture_get_size(AV_PIX_FMT_YUV420P, m_picture->width, m_picture->height);        
    uint8_t * buffer = reinterpret_cast<uint8_t *>(av_malloc(numBytes));
    avpicture_layout( ( AVPicture*)m_picture, AV_PIX_FMT_YUV420P, m_picture->width, m_picture->height, buffer, numBytes); //av_image_copy_to_buffer() в новом API


    Mat yuv = Mat(m_picture->height+m_picture->height/2, m_picture->width, CV_8UC1, buffer );
    Mat gray = Mat(m_picture->height, m_picture->width, CV_8UC1, buffer ); //-- Если нужно получить сразу в градациях серого (формат YUV содержит в первом канале как раз в градациях серого кадр)

    Mat rgb = Mat(m_picture->height, m_picture->width, CV_8UC3);
    cvtColor(yuv, rgb, CV_YUV420p2RGB);

    imshow("RGB", rgb);
    imshow("GRAY", gray);
}

ВАЖНО: Не забудьте очистить память из под buffer, так как после удаления Mat она не очищается самостоятельно!

В вашем случае замените форматы на те, с которыми работаете.

Вот как-то так…

 

 

Views :

397

Компиляция libav для поддержки h264 видео.

Привет! 

Понадобилось мне в одном из проектов декодировать h264 поток, думал обойдусь без сторонних либ, глянул спеку на 300 страниц и передумал, нашёл библиотеку libav, у которой очень много фич по декодированию аудио и видео. 

Ок, компилируем:

sudo apt-get install libx264-148 libx264-dev #148 замените на актуальную версию
cd ~/Projects/libav
git clone git://git.libav.org/libav.git
./configure --enable-libx264 --enable-gpl --disable-x86asm --enable-shared
make
make install

Вот как-то так, думал будет труднее =)

Views :

135

Печатаем из Qt на онлайн фискальнике ККТ PAYONLINE-01-ФА

В связи с очередным злоебучим (по моему мнению) законом 54-ФЗ пришлось клиентам резко обновлять кассы и выкидывать из своего кармана 40 штук, что бы поменять шило на полноценный анальный зонд.

Я бы да же этому был бы рад из-за возможности заработать на обновлении, но вот что-то радоваться чужому горю вообще не фонтан.

В общем, подключаем приблуду по микро USB (ком-порта у меня в ноуте нет), заходим на оф. сайт, качаем и драйвер «USB VCOM«.

В дальнейшем оттуда нас будет так же интересовать руководство программиста, почитайте.

С драйвером мы будем работать через ActiveX (старовато, ну а херли делать. Радует немного, что Qt поддерживает эту технологию).

Создаём новый проект (у меня консольное приложение), создаём в проекте папку activex, в неё кидаем саму либо. В неё же в дальнейшем сгенерируем заголовочник и исходник.

C:\Projects\KKT-PAYONLINE-01-FA\KKT-PAYONLINE-01-FA\activex
C:\Qt\5.9.2\mingw53_32\bin\dumpcpp DrvFR.dll

Пути замените на свои. Кстати, в путях не используйте кириллицу. Если есть пробелы, то возьмите в кавычки. 

В папке должно появится два файла: «drvfrlib.h» и «drvfrlib.cpp» — они то нам и нужны, добавляем их в проект.

В настройках проекта («*.pro» файле) подключаем пару модулей 

QT += axcontainer widgets

Они нам нужны для работы с ActiveX.

Наша цель сейчас подключиться к фискальнику, проверить кассовую смену (если истекла — печатаем z-отчёт, если закрыта — открываем, иначе нам ничего не сделать больше), делаем продажу, отключаемся.

#include <QCoreApplication>
#include <QApplication>
#include <QDebug>

#include "activex/drvfrlib.h" //-- Подключаем заголовочник либы
using namespace DrvFRLib; //-- Сразу задаём в каком пространстве имён будем работать

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug()<<"Started!";

    DrvFR * fr = new DrvFR(0, 0); //-- Создаём класс драйвера

    //-- Подключаемся к поебени
    fr->SetConnectionType(0); //-- Метод её подключения к нам, 0-локально
    fr->SetProtocolType(0); //-- Вервия протокола, 0-стандартная
    fr->SetComputerName("HCSTerminal"); //-- Задаём имя, под которым она нас будет знать
    fr->SetTimeout(0); //-- Сколько будет ждать ответа (рекомендую 0, иначе будут сюрпризы)
    fr->SetComNumber(3); //-- Устанавливаем на каком com-порту висит (COM3 - в моём случае)
    fr->SetBaudRate(115200); //-- Устанавливаем скорость подключения (115200 - дефолтная)
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ по дефолту)
    qDebug()<<"Connect"<<fr->Connect();


    //-- Проверим состояние
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    qDebug()<<"EcrStatus"<< fr->GetECRStatus();
    qDebug()<<"EcrMode"<< fr->ECRMode(); //-- Подробнее на стр. 64. Сейчас нас интересует: 4-смена закрыта, 3-смена открыта, но кончилась (нужно напечатать Z-отчёт с гашением)
    qDebug()<<fr->ECRModeDescription();



    //-- Печатаем z-отчёт с гашением
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    fr->PrintReportWithCleaning();
  
      //-- Откроем новую смену
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    fr->OpenSession(); //-- открывает смену
    

    //-- Начинаем формирование нового чека
    fr->SetCheckType(0); //-- Устанавливаем тип нового чека, 0-продажа
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    qDebug()<<"OpenCheck"<<fr->OpenCheck();
    qDebug()<<"Result"<<fr->ResultCode()<<fr->ResultCodeDescription();
    
    //-- Добавляем позиции продажи
    fr->SetQuantity(1.0); //-- Устанавливаем количество
    fr->SetPrice(3); //-- Устанавливаем цену
    fr->SetStringForPrinting("Проверка печати"); //-- Описание товара
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    fr->SetDepartment(1); //-- Задаём отдел продажи (если хуёвин несколько, что бы отличать)
    qDebug()<<"Sale result"<<fr->Sale();


    //-- Завершаем формирование чека, после закрытия выйдет на печать
    fr->SetSumm1(3); //-- Сумма оплаты наличными, если несколько типов оплат, то соответственно fr->setSumm2(); Если сумма по всем позициям будет не совпадать, то на чеке автоматически будет выведена сдача.
    fr->SetStringForPrinting("==="); //-- В чеке будет двойная линия
    fr->SetPassword(30); //-- Задаём пароль оператора (30 - админ)
    qDebug()<<"Check status"<<fr->CloseCheck();
    
    //-- Отключаемся
    fr->Disconnect();

    delete fr;

    qDebug()<<"Ended!";
    return a.exec();
}

Вот как-то так выглядит алгоритм печати чека (продажи). Если какой-то метод вернёт что-то отличное от 0, значит произошла ошибка.

Разумеется каждый раз печатать z-отчёт и открывать смену не нужно, как и писал выше, нужно проверять сначала, но для примера не стал (лень). 

Если будет ругаться на «enum TBarcodePrintType«, то просто закомментите в двух местах.

А вот теперь попробуем  продать товар с ценой, где есть копейки:

....
fr->SetPrice(3.23);
....
fr->SetSumm1(3.23);
....

Облом, да? Дробная часть у цена откинулась наглухо. Дело в том, что в документации у цены стоит тип «Cyrrency» (Денежный).
Так как в C++ явно такого типа нет, то Qt преобразовал его в qlonglong.

Currency широко используется в Delphi для хранения денежных единиц. Представляет он из себя, по сути, тип с фиксированной точкой, а храниться как 8 байтное целое, где 4 младших разряда — неявная дробная часть. Подробнее.

Перевод из double (c++) в Currency (Delphi) прост:

double d = 12.3456;
qlonglong currency = (round(d*10000)/10000)*10000;

Пояснение:
(round(d*10000)/10000) так как у Currency значащими являются только 4 знака после запятой, то так мы округляем до них.
*10000 — тем самым приводим к виду Currency.

Кстати, здесь не используется банковское округление (когда 0.5 считается в меньшую сторону).

Что бы каждый раз не возиться, предлагаю создать простенький класс TCurrency, в котором заодно перегрузим основные операции сложение, вычитания:

class TCurrency
{
public:
    explicit TCurrency(double d) { _val = fromDouble(d); }
    explicit TCurrency(qlonglong ll) { _val = ll; }
    static inline qlonglong fromDouble(double d) { return (round(d*10000)/10000)*10000; }

    TCurrency& operator =(double d) { _val = fromDouble(d); return *this; }

    const TCurrency operator +(double d) { return TCurrency(_val+fromDouble(d)); }
    const TCurrency operator +(const TCurrency &tc) { return TCurrency(_val+tc._val); }
    TCurrency& operator +=(double d) { _val += fromDouble(d); return *this; }
    TCurrency& operator +=(const TCurrency &tc) { _val += tc._val; return *this; }

    const TCurrency operator -(double d) { return TCurrency(_val-fromDouble(d)); }
    const TCurrency operator -(const TCurrency &tc) { return TCurrency(_val-tc._val); }
    TCurrency& operator -=(double d) { _val -= fromDouble(d); return *this; }
    TCurrency& operator -=(const TCurrency &tc) { _val -= tc._val; return *this; }

    operator qlonglong() { return _val; }

private:
    qlonglong _val;

};

В итоге:

fr->SetPrice(TCurrency(3.23));
или 
TCurrency cr;
cr = 2.23;
cr += 0.1;
fr->SetPrice(cr);

Вот как-то так.

Views :

420

Qt, QEventLoop and connect/disconnect lambda function

Приветствую!

Кому некогда, можно сразу прыгнуть в конец к итогу.

Бывают ситуации, когда нужно синхронно дождаться завершения асинхронного действия, при этом не подвешивая основной поток (например, не продолжать выполнение функции, пока ответ в QTCPSocket onReadyRead от сервера не придёт).   

В нашем случае для примера давайте подождём с выполнением функции, пока таймер не досчитает до 5. 

Делаем основу:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QGuiApplication>
#include <QDebug>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах


        qDebug()<<"Timer END";
    }

signals:

public slots:

private:
    QTimer * _timer;
};

#endif // APPCORE_H

Но если мы запустим так, то вывод  «Timer END» произойдёт без какой-либо задержки, т.к. таймер ведёт отсчёт асинхронно в другом потоке.

Решением в «лоб» было бы подписаться на событие срабатывания таймера, объявить флаг срабатывания и в бесконечном цикле отслеживать его, как-то так, например:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах

        while (true) {
            if (_timeOut) {
                break;
            }
            QGuiApplication::processEvents();
        }

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _timeOut = true;
    }

private:
    QTimer * _timer;
    bool _timeOut=false;
};

#endif // APPCORE_H

Кстати, если бы мы не указали «QGuiApplication::processEvents();«, то слот «onTimeOut()» не вызвался бы никогда, т.к. цикл у нас бесконечный, а так мы заставляем всё таки обработать события, а заодно и не подвешивать сильно интерфейс. 

Но решение это слишком топорное и не красивое. Что бы не использовать бесконечные циклы у Qt есть QEventLoop.  Выполнение функции приостанавливается методом «exec()» и QEventLoop ждёт, пока не будет вызван метод «exit()» и лишь потом продолжается.

Делаем:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _loop.exit();
    }

private:
    QTimer * _timer;
    QEventLoop _loop;
};

#endif // APPCORE_H

Стало чуть-чуть красивее, но у нас всё ещё висит одноразовый слот «onTimeOut()» и одноразовая переменная «_loop«.  Это сейчас она одна, а если в нашем классе нужно в 5 разных местах дожидаться ответов? Как-то по 5 одноразовых слотов и переменных иметь некрасиво…

Благо в Qt начиная с 5 версии появилась возможность при соединении сигнал-слота вместо слота использовать лямбда-функцию, этой фишкой мы и воспользуемся, что бы избавиться он объявления глобального слота «onTimeOut()» и переменной «_loop»

Делаем: 

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        connect(_timer, &QTimer::timeout, [&](){
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Запускаем и ровно через 5 секунд после «Timer BEGIN» у нас выведется «Timer END»  и вроде бы добились чего хотели, но тут есть одна тонкость. Для наглядности я в лямбду добавлю вывод информации о срабатывании, вот так теперь она выглядит::

connect(_timer, &QTimer::timeout, [&_loop](){
    qDebug()<<"TIME OUT!";
    _loop.exit();
});

Допустим нам нужно в нескольких местах ждать ответа, и мы два раза вызываем функцию:

    ....
    waitFunction();
    waitFunction();
    ....

Вывод будет такой:

Timer BEGIN
TIME OUT!
Timer END
<br>Timer BEGIN
TIME OUT!
TIME OUT!
Timer END

Как видите, «TIME OUT» после второго вызова вывелось два раза! А если бы мы функцию «waitFunction()» вызвали 10 раз, то соответственно «TIME OUT» вывелось бы то же 10 раз подряд.  Так явно не должно быть! В чём дело?  А дело в том, что лямбда функция автоматически не отключается! Если забыть про эту фишку можно нарваться в том числе и на «SIGSEGV Segmentation fault», сегфолт короче. 

Решение — это не забывать отключать (disconnect signal lambda) сигнал от лямбды, но просто так это сделать не получится, т.к. это всё таки лямбда, нужно запоминать информацию, которую возвращает метод «QMetaObject::Connection conn = connet(….)«, а по ней отключать «disconnect(conn)«. 

Делаем:

void waitFunction()
{
    qDebug()<<"Timer BEGIN";

    QEventLoop _loop;

    QMetaObject::Connection conn = connect(_timer, &QTimer::timeout, [&_loop](){
        qDebug()<<"TIME OUT!";
        _loop.exit();
    });

    _timer->start(5000); //-- 5 секунд в миллисекундах
    _loop.exec(); //-- Ждём, пока будет вызван exit();

    disconnect(conn);

    qDebug()<<"Timer END";
}

Срабатывать будет  один раз и отключаться, вывод придёт в норму:

Timer BEGIN
TIME OUT!
Timer END

Timer BEGIN
TIME OUT!
Timer END

Но об  «disconnect(….)»  можно забыть, поэтому предлагаю использовать умные указатели, а именно QSharedPointer, но у него нужно не забыть реализовать отключение, т.к. сам по себе он это делать не умеет, а так как писанины получается многовато, поэтому предлагаю запилить макрос.

Итоговый код:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>
#include <QSharedPointer>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    #define AutoDisconnect(l) \
        QSharedPointer<QMetaObject::Connection> l = QSharedPointer<QMetaObject::Connection>(\
            new QMetaObject::Connection(), \
            [](QMetaObject::Connection * conn) { /*QSharedPointer сам по себе не производит отключения при удалении*/ \
                QObject::disconnect(*conn);\
            }\
        ); *l //-- Use AutoDisconnect(conn1) = connect(....);

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        AutoDisconnect(conn) = connect(_timer, &QTimer::timeout, [&_loop](){
            qDebug()<<"TIME OUT!";
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Вот как-то так =)

P.S. Макросы не зло, нужно просто уметь их готовить 😉

Views :

765

Layout.fillWidth: true и Layout.preferredWidth/Layout.minimumWidth зависимость (очередная хитрость)

Сталкиваюсь иногда с некоторыми хитростями в QML, о которых, по всей видимости, приходится только догадываться, ибо то ли я проглядел это в документации, то ли этого действительно в ней не указано.

Так вот, задача: нужно три колонки одинаковой ширины.

Делаем:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QMLTips&Tricks24")

    RowLayout {
        anchors.fill: parent
        spacing: 0

        Rectangle {
            color: "blue"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

        Rectangle {
            color: "orange"
            Layout.fillHeight: true
            Layout.fillWidth: true
        }


        Rectangle{
            color: "green"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

    }
}

Соответственно на выходе получаем:

Получилось как и задумывали.

Обновим задачу:  Оранжевая колонка должна иметь предпочтительную ширину 200 пикселей.
Делаем:

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

Что-то не то, да..? 

Вот тут начинается самое интересное.

Дело в том, что Layout.prefferedWidth (а так же Layout.minimumWidth) управляет пропорционально шириной относительно соседей, когда задано Layout.fillWidth: true

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

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

То есть как и задумывали, три одинаковые колонки.

Теперь наглядный пример насчёт пропорционально соседям изменяемости размеров. Если мы у синего прямоугольника зададим желаемую ширину в 50, а у зелёного 100, то соответственно синий будет в 4 раза меньше оранжевого (200/50=4), а зелёный буде в два раза меньше оранжевого (200/100=2). 

То есть родитель Layout (RowLayout/ColumnLayout/GridLayout) берёт от потомков наибольший желаемый размер и относительно него пропорционально выставляет ширину всем потомкам.
Повторюсь, что так QML себя ведёт, только когда задано Layout.fillWidth: true

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 50
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 100
}

Получаем:

Короче, считаем, что при указании preferredWidth, minimumWidth мы работаем с пропорциями относительно остальных в одном контейнере и всё. 

Почему именно так, а не иначе?  

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

 

Аналогичная ситуация произойдёт, если мы оранжевой колонке захотим указать minimumWidth: 100 при fillWidth:true

Что делать, если нужно, что бы все колонки имели одинаковую ширину и заполняли всё пространство (т.е. fillWidth: true), но при этом у них (у всех или у некоторых) должна быть задана разная минимальная ширина? 

Я в таком случае прописываю у всех:  preferredWidth: 1000;  что бы желательная ширина у всех была одинаковая и обязательно больше, чем минимальная, иначе учитываться не будет (логично, да?)

Надеюсь, теперь больше с этим проблем не возникнет.

Кстати, такое же поведение будет если у компонента задано свойство implicitWidth или implicitHeight, потому что если мы не задали явно Layout.preferredWidth или  Layout.fillHeight, то берутся как раз они. Наверное вы спрашиваете: как быть, если это не просто прямоугольник, а какой-то компонент? То всё просто — оберните его в Item или Rectangle или задайте явно Layout.preferredWidth

Кстати, вот документация на лэйауты: тынц.

Вот как-то так =)

Views :

458

Конвертер картинок для Arduino LCD OLED 128×64 I2C дисплея

Пришёл вот такой дисплейчик:

Но вот нигде не нашёл для него генератора, что бы модно было конвертировать jpg/png/bmp картинку в код. 
Неспешно накалякал, выбираете любой jpg/bmp файл и получаете на выходе код:

Тестовый скетч:

#include <OLED_I2C.h>  

OLED  myOLED(A4, A5, A4); 

extern uint8_t SmallFont[];

//--PASTE GENERATED CODE HERE

void setup()
{
    myOLED.begin();
    myOLED.setFont(SmallFont);    
}

void loop() 
{
    myOLED.clrScr(); 
    myOLED.drawBitmap(0, 0, icon1, 21, 21); //-- X, Y, IMG, Width, Height 
    myOLED.update();
    delay(150);
}




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

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

Библиотека OLED_I2C.

Views :

6585

Компиляций libusb из исходников на Windows

Тут особо нечего рассказывать, всё не так трудно, поэтому минимум комментов.

Качаем MSYS2, ставим, запускаем  C:\msys32\mingw32.exe

pacman -Syu
pacman -Su
pacman -S git
pacman -S base-devel 
pacman -S libtool
pacman -S mingw-w64-i686-toolchain

touch /e/LibUSB2
cd /e/LibUSB2/
git clone https://github.com/libusb/libusb .
./autogen.sh
touch build-Win32
cd build-Win32
touch bin
../configure --prefix=/e/LibUSB2/build-Win32/bin --build=i686-w64-mingw32 --host=i686-w64-mingw32
make -j4
make install

Вся либа будет в /e/LibUSB2/build-Win32/bin.

Вот как-то так =)

Views :

434