Последнее обновление:
August 9, 2018

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

Bareos 18.2, компиляция из исходников

Понадоиблось тут Bareos под ARM платформу скомпилировать, которая стоит на домашнем NAS.

Впринципе, всё просто, за исключением нескольких мелочей.

Для использования bareos-webui необходимо скомпилить вместе с Jansson, а для этого его нужно получить, собрать, установить и показать конфигуратору bareos где он лежит:

sudo apt-get install pkg-config
sudo apt-get install autoconf
sudo apt-get install libtool
sudo apt-get install build-essential
git clone https://github.com/akheron/jansson
cd jansson
autoreconf -i
./configure
make
make install
cd ../

Если используется Debian 8, то поставим нужный cmake и g++ 6:

echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" >> /etc/apt/sources.list.d/unstable.list
sudo apt-get install g++-6
sudo apt-get install cmake

sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-6 30
sudo update-alternatives --set c++ /usr/bin/g++
update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-6 20
update-alternatives --set cc /usr/bin/gcc-6

c++ --version //-- Вывод должен быть 6.4.0 (не ниже и точно не 7 или 8 - не скомпилится)
cmake --version //-- Вывод должен быть 3.11.2 (не ниже)

Ну а дальше всё просто — качаем исходники Bareos, компилим, собираем всё это в deb пакет, что бы потом не парится с новыми версиями:

git clone https://github.com/bareos/bareos/ -b bareos-18.2
cd bareos/core
dpkg-checkbuilddeps //-- установите все зависимости из вывода

cp -a platforms/packaging/bareos.changes debian/changelog
VERSION=$(sed -n -r 's/#define VERSION "(.*)"/\1/p'  src/include/version.h)
dch -v $VERSION "Switch version number"
fakeroot debian/rules binary

Если будет ошибка «dpkg-shlibdeps: error: no dependency information found for …«,
то в файле «core/debian/rules» ищем строку «dh_shlibdeps» и дополняем опцией «—dpkg-shlibdeps-params=—ignore-missing-info»

Не забудьте установить новый пароль для root в mysql!

Устанавливаем теперь созданные пакеты:

cd ../

//-- Всё, что нужно для директора (bareos-dir):
apt-get install dbconfig-common
dpkg -i bareos-database-common_18.2.3_armel.deb
dpkg -i bareos-database-mysql_18.2.3_armel.deb
dpkg -i bareos-database-tools_18.2.3_armel.deb
dpkg -i bareos-director_18.2.3_armel.deb
dpkg -i bareos-bconsole_18.2.3_armel.deb

//-- Хранилище (bareos-sd):
dpkg -i bareos-storage_18.2.3_armel.deb
//-- Клиент (bareos-fd)
dpkg -i bareos-filedaemon_18.2.3_armel.deb
dpkg -i bareos-client_18.2.3_armel.deb

Запускаем:

sudo service bareos-sd start
sudo service bareos-fd start
sudo service bareos-dir start

Если будет ошибка «/sbin/bareos-dir: symbol lookup error: /sbin/bareos-dir: undefined symbol: _Z16DbSetBackendDirsP5alist»
то тогда нужно чутка изменить ссылки, что бы bareoscats.so.18 указывала на backends/bareoscatsmysql.so  — пять часов на выяснение причин потратил.

Теперь поставим «Bareos WEBUI».
Ставим необходимые для сборки пакеты и аналогично собираем в deb пакет:

wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ jessie main" > /etc/apt/sources.list.d/php.list
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates
sudo apt-get install autotools-dev apache2-dev apache2-prefork-dev
sudo apt-get install php7.1-cli php7.1-curl php7.1-fpm php7.1-gd php7.1-intl php7.1-json php7.1-mbstring php7.1-mcrypt php7.1-pdo-mysql php7.1-xml php7.1-zip
sudo apt-get install libapache2-mod-php7
service apache2 restart
cd webui
dpkg-checkbuilddeps
cp -a packaging/obs/bareos-webui.changes debian/changelog
fakeroot debian/rules binary

Устанавливаем созданный пакет:

cd ../
dpkg -i bareos-webui_15.2.1_all.deb

Ну и можно зайти, в браузере набираем:

http://127.0.0.1/bareos-webui

По настройке будет отдельный пост.

Вот и всё =)

Views :

9

Вылеты Q_ASSERT(c->sender == q_ptr);

Появилась достаточно странная бага — вылеты при отладке и чуть реже при релизе с этой ошибкой.

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

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

В общем причина была в незакрытом  #pragma pack(8), заменим его на #pragma pack(push, 1)  тем самым запомнив предыдущие настройки, а после объявлений структур восстановим настройки #pragma pack(pop) что бы Qt сума не сходил.

 

 

Views :

26

Модульное тестирование (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 :

100

Использование интерфейсов классов в 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 :

152

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 :

124

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 :

70

Компиляция 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 :

35

Печатаем из 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-отчёт, если закрыта — открываем, иначе нам ничего не сделать больше), делаем продажу, отключаемся.

Код с комментами, надеюсь ясно, «main.cpp«:

#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++ разумеется такого идиотского типа нет, поэтому dumpcpp нам его заменил на qlonglong, но вот про извращения с дробной частью он не знал.

Придётся ему рассказать, а следовательно перекомпилировать модуль ActiveQt, по пути: «C:\Qt\5.9.2\Src\qtactiveqt\src\activeqt».  Сам виновник: container/qaxbase.cpp».

Надеюсь, отметили установку исходников. Если нет, то запускаете UpdateManager и выкачиваете исходники.

Открываем «container/qaxbase.cpp», в районе строки 2002 находим:

case VT_CY:
        str = "qlonglong";

Меняем на:

case VT_CY:
        str = "double";

Немного не правильно, но лучшего решения я не вижу, предлагайте в комментах.

Компилим:

cd C:\Qt\5.9.2\Src\qtactiveqt\src\activeqt
set PATH=C:\Qt\5.9.2\mingw53_32\bin;%PATH%
set PATH=C:\Qt\Tools\mingw530_32\bin;%PATH%
qmake
mingw32-make
mingw32-make install

Пути не забудьте на свои поменять.

После этого заново генерируем исходник и заголовочный из ActiveX.

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

Views :

100

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 :

237

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 :

69