diff --git a/CMakeLists.txt b/CMakeLists.txt index 08aed74..06b5798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,11 +41,17 @@ file(GLOB_RECURSE SERVICES_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/services/*.h" ) +file(GLOB_RECURSE MODELS_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/models/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/models/*.h" +) + add_executable(ARCDesktopProject main.cpp ${VIEW_SOURCES} ${SERVICES_SOURCES} ${DB_SOURCES} + ${MODELS_SOURCES} ) if (WIN32) @@ -65,4 +71,11 @@ elseif (APPLE) Qt6::Sql tesseract ) -endif () \ No newline at end of file +endif () + +target_include_directories(ARCDesktopProject PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/views + ${CMAKE_CURRENT_SOURCE_DIR}/database + ${CMAKE_CURRENT_SOURCE_DIR}/services + ${CMAKE_CURRENT_SOURCE_DIR}/models +) \ No newline at end of file diff --git a/database/database_manager.cpp b/database/DatabaseManager.cpp similarity index 95% rename from database/database_manager.cpp rename to database/DatabaseManager.cpp index d49dfe8..63ab141 100644 --- a/database/database_manager.cpp +++ b/database/DatabaseManager.cpp @@ -1,4 +1,4 @@ -#include "database_manager.h" +#include "DatabaseManager.h" #include #include diff --git a/database/database_manager.h b/database/DatabaseManager.h similarity index 100% rename from database/database_manager.h rename to database/DatabaseManager.h diff --git a/database/app_data.db b/database/app_data.db index d5b3627..1eedefa 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/database/dao/common_data_dao.cpp b/database/dao/CommonDataDAO.cpp similarity index 95% rename from database/dao/common_data_dao.cpp rename to database/dao/CommonDataDAO.cpp index ccb20e7..003b7ff 100644 --- a/database/dao/common_data_dao.cpp +++ b/database/dao/CommonDataDAO.cpp @@ -1,5 +1,5 @@ -#include "common_data_dao.h" -#include "../database_manager.h" +#include "CommonDataDAO.h" +#include "DatabaseManager.h" #include #include diff --git a/database/dao/common_data_dao.h b/database/dao/CommonDataDAO.h similarity index 100% rename from database/dao/common_data_dao.h rename to database/dao/CommonDataDAO.h diff --git a/database/dao/DeviceDAO.cpp b/database/dao/DeviceDAO.cpp new file mode 100644 index 0000000..d248f70 --- /dev/null +++ b/database/dao/DeviceDAO.cpp @@ -0,0 +1,139 @@ +#include +#include +#include "DeviceDAO.h" +#include "DatabaseManager.h" +#include "DeviceInfo.h" + +QList DeviceDAO::getAllDevices() { + QList devices; + + QSqlQuery query(DatabaseManager::instance().database()); + query.prepare( + "SELECT id, status, name, android, screen_width, screen_height, battery, updateTime, image FROM devices WHERE image IS NOT NULL AND status = 'device'"); + + if (!query.exec()) { + qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); + return devices; + } + + while (query.next()) { + DeviceInfo device; + device.id = query.value("id").toString(); + device.status = query.value("status").toString(); + device.name = query.value("name").toString(); + device.android = query.value("android").toString(); + device.screenWidth = query.value("screen_width").toInt(); + device.screenHeight = query.value("screen_height").toInt(); + device.battery = query.value("battery").toInt(); + device.updateTime = QDateTime::fromString(query.value("updateTime").toString(), Qt::ISODate); + device.image = query.value("image").toByteArray(); + + devices.append(device); + } + + return devices; +} + +bool DeviceDAO::upsertDevice(const DeviceInfo &device) { + QSqlQuery query(DatabaseManager::instance().database()); + + // Проверка: существует ли запись + query.prepare("SELECT COUNT(*) FROM devices WHERE id = :id"); + query.bindValue(":id", device.id); + + if (!query.exec() || !query.next()) { + qWarning() << "Ошибка при проверке существования записи:" << query.lastError().text(); + return false; + } + + bool exists = query.value(0).toInt() > 0; + + if (exists) { + // Обновление + query.prepare(R"( + UPDATE devices SET + status = :status, + name = :name, + android = :android, + screen_width = :width, + screen_height = :height, + battery = :battery, + updateTime = :updateTime + WHERE id = :id + )"); + } else { + // Вставка + query.prepare(R"( + INSERT INTO devices ( + id, status, name, android, screen_width, screen_height, battery, updateTime + ) VALUES ( + :id, :status, :name, :android, :width, :height, :battery, :updateTime + ) + )"); + } + + query.bindValue(":id", device.id); + query.bindValue(":status", device.status); + query.bindValue(":name", device.name); + query.bindValue(":android", device.android); + query.bindValue(":width", device.screenWidth); + query.bindValue(":height", device.screenHeight); + query.bindValue(":battery", device.battery); + query.bindValue(":updateTime", device.updateTime.toString(Qt::ISODate)); + + if (!query.exec()) { + qWarning() << "Ошибка при вставке/обновлении устройства:" << query.lastError().text(); + return false; + } + + return true; +} + +bool DeviceDAO::markDevicesOfflineExcept(const QStringList &onlineDeviceIds) { + QSqlQuery query(DatabaseManager::instance().database()); + + // Если список ID пуст, помечаем все как offline + if (onlineDeviceIds.isEmpty()) { + if (!query.exec("UPDATE devices SET status = 'offline'")) { + qWarning() << "Ошибка при обновлении устройств на offline:" << query.lastError().text(); + return false; + } + return true; + } + + // Генерируем плейсхолдеры :id0, :id1, ... + QStringList placeholders; + for (int i = 0; i < onlineDeviceIds.size(); ++i) { + placeholders << QString(":id%1").arg(i); + } + + const QString queryString = QString( + "UPDATE devices SET status = 'offline' WHERE id NOT IN (%1)" + ).arg(placeholders.join(", ")); + + query.prepare(queryString); + // Привязываем значения к плейсхолдерам + for (int i = 0; i < onlineDeviceIds.size(); ++i) { + query.bindValue(QString(":id%1").arg(i), onlineDeviceIds[i]); + } + + if (!query.exec()) { + qWarning() << "Ошибка при обновлении устройств на offline (исключая online):" << query.lastError().text(); + return false; + } + return true; +} + +bool DeviceDAO::updateImage(const QString &deviceId, const QByteArray &imageData) { + QSqlQuery query(DatabaseManager::instance().database()); + query.prepare("UPDATE devices SET image = :image WHERE id = :id"); + query.bindValue(":image", imageData); + query.bindValue(":id", deviceId); + + if (!query.exec()) { + qWarning() << "Ошибка при обновлении изображения:" << query.lastError().text(); + return false; + } + + return true; +} diff --git a/database/dao/DeviceDAO.h b/database/dao/DeviceDAO.h new file mode 100644 index 0000000..b9b664c --- /dev/null +++ b/database/dao/DeviceDAO.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class DeviceDAO { +public: + [[nodiscard]] static bool upsertDevice(const DeviceInfo &device); + + [[nodiscard]] static bool markDevicesOfflineExcept(const QStringList &onlineDeviceIds); + + [[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData); + + [[nodiscard]] static QList getAllDevices(); +}; diff --git a/database/dao/device_dao.cpp b/database/dao/device_dao.cpp deleted file mode 100644 index 32368eb..0000000 --- a/database/dao/device_dao.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "device_dao.h" -#include -#include -#include -#include -#include "../database_manager.h" - -bool DeviceDAO::upsertDevice(const DeviceInfo& device) { - QSqlQuery query(DatabaseManager::instance().database()); - - // Проверка: существует ли запись - query.prepare("SELECT COUNT(*) FROM devices WHERE id = :id"); - query.bindValue(":id", device.id); - - if (!query.exec() || !query.next()) { - qWarning() << "Ошибка при проверке существования записи:" << query.lastError().text(); - return false; - } - - bool exists = query.value(0).toInt() > 0; - - if (exists) { - // Обновление - query.prepare(R"( - UPDATE devices SET - status = :status, - name = :name, - android = :android, - screen_width = :width, - screen_height = :height, - battery = :battery, - updateTime = :updateTime - WHERE id = :id - )"); - } else { - // Вставка - query.prepare(R"( - INSERT INTO devices ( - id, status, name, android, screen_width, screen_height, battery, updateTime - ) VALUES ( - :id, :status, :name, :android, :width, :height, :battery, :updateTime - ) - )"); - } - - query.bindValue(":id", device.id); - query.bindValue(":status", device.status); - query.bindValue(":name", device.name); - query.bindValue(":android", device.android); - query.bindValue(":width", device.screenWidth); - query.bindValue(":height", device.screenHeight); - query.bindValue(":battery", device.battery); - query.bindValue(":updateTime", device.updateTime.toString(Qt::ISODate)); - - if (!query.exec()) { - qWarning() << "Ошибка при вставке/обновлении устройства:" << query.lastError().text(); - return false; - } - - return true; -} - -bool DeviceDAO::updateImage(const QString& deviceId, const QByteArray& imageData) { - QSqlQuery query(DatabaseManager::instance().database()); - query.prepare("UPDATE devices SET image = :image WHERE id = :id"); - query.bindValue(":image", imageData); - query.bindValue(":id", deviceId); - - if (!query.exec()) { - qWarning() << "Ошибка при обновлении изображения:" << query.lastError().text(); - return false; - } - - return true; -} diff --git a/database/dao/device_dao.h b/database/dao/device_dao.h deleted file mode 100644 index 8a9060f..0000000 --- a/database/dao/device_dao.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -struct DeviceInfo { - QString id; - QString status; - QString name; - QString android; - int screenWidth; - int screenHeight; - int battery; - QDateTime updateTime; -}; - -class DeviceDAO { -public: - [[nodiscard]] static bool upsertDevice(const DeviceInfo &device); - - [[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData); -}; diff --git a/main.cpp b/main.cpp index 478db6e..5b626fc 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,10 @@ #include #include -#include "views/MyWindow.h" -#include +#include "views/MainWindow.h" -#include "database/database_manager.h" -#include "database/dao/common_data_dao.h" -#include "services/device_screener.h" +#include "database/DatabaseManager.h" +#include "database/dao/CommonDataDAO.h" +#include "services/DeviceScreener.h" void setupWorkerAndThread(QCoreApplication& app) { auto* thread = new QThread; @@ -73,7 +72,7 @@ int main(int argc, char *argv[]) { // qDebug() << "Распознанный текст:" << result; - MyWindow window; + MainWindow window; window.show(); return app.exec(); } diff --git a/models/DeviceInfo.h b/models/DeviceInfo.h new file mode 100644 index 0000000..b87ed5c --- /dev/null +++ b/models/DeviceInfo.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct DeviceInfo { + QString id; + QString status; + QString name; + QString android; + int screenWidth; + int screenHeight; + int battery; + QDateTime updateTime; + QByteArray image; +}; \ No newline at end of file diff --git a/services/device_screener.cpp b/services/DeviceScreener.cpp similarity index 82% rename from services/device_screener.cpp rename to services/DeviceScreener.cpp index 6927846..2cd6d72 100644 --- a/services/device_screener.cpp +++ b/services/DeviceScreener.cpp @@ -1,10 +1,13 @@ -#include "device_screener.h" +#include "DeviceScreener.h" #include #include #include +#include #include -#include <../../database/dao/device_dao.h> +#include +#include +#include DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { } @@ -15,26 +18,47 @@ void DeviceScreener::start() { qDebug() << "Worker started in thread:" << QThread::currentThread(); while (m_running) { QList> devices = readAdbDevices(); + QStringList onlineIds; for (const auto&[id, status] : devices) { qDebug() << "Id:" << id << ", Status:" << status; DeviceInfo device; device.id = id; - device.status = status; - device.updateTime = QDateTime::currentDateTime(); if (status == "device") { + onlineIds << device.id; device = readDeviceInfo(id); takeScreenshot(id); } - + device.status = status; + device.updateTime = QDateTime::currentDateTime(); qDebug() << "Update device: " << DeviceDAO::upsertDevice(device); } - QThread::sleep(5); + qDebug() << "All devices offline: " << DeviceDAO::markDevicesOfflineExcept(onlineIds); + QThread::sleep(3); } emit finished(); } +QByteArray convertPngToJpeg(const QByteArray& pngData, const int quality = 90) { + QImage image; + if (!image.loadFromData(pngData, "PNG")) { + qWarning() << "Не удалось загрузить PNG из байт"; + return {}; + } + + // if (!image.save("test.jpg", "JPG", quality)) { + // qWarning() << "Не удалось сохранить как JPG:" << "test.jpg"; + // return {}; + // } + + QByteArray jpgData; + QBuffer buffer(&jpgData); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "JPG", quality); + return jpgData; +} + bool DeviceScreener::takeScreenshot(const QString& deviceId) { QProcess process; QStringList arguments; @@ -55,7 +79,7 @@ bool DeviceScreener::takeScreenshot(const QString& deviceId) { } // Обновляем изображение в БД - if (!DeviceDAO::updateImage(deviceId, imageData)) { + if (!DeviceDAO::updateImage(deviceId, convertPngToJpeg(imageData, 90))) { qWarning() << "Не удалось записать изображение в БД для" << deviceId; return false; } diff --git a/services/device_screener.h b/services/DeviceScreener.h similarity index 90% rename from services/device_screener.h rename to services/DeviceScreener.h index 54266f2..903ced3 100644 --- a/services/device_screener.h +++ b/services/DeviceScreener.h @@ -1,6 +1,7 @@ #pragma once #include -#include <../../database/dao/device_dao.h> +#include +#include class DeviceScreener final : public QObject { Q_OBJECT diff --git a/views/DeviceWidget.cpp b/views/DeviceWidget.cpp new file mode 100644 index 0000000..822f179 --- /dev/null +++ b/views/DeviceWidget.cpp @@ -0,0 +1,37 @@ +#include "DeviceWidget.h" +#include +#include +#include +#include + +DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(parent) { + auto *layout = new QVBoxLayout(this); + + this->setStyleSheet("background-color: lightgray;padding: 10px;"); + + QImage img; + img.loadFromData(device.image, "JPG"); + + auto *imageLabel = new QLabel(this); + qDebug() << "imageLabel" << img.width() ; + imageLabel->setPixmap(QPixmap::fromImage(img).scaled(200, 350, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + imageLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(imageLabel); + + const QString result = QString("%1, Android %2, battery %3% [%4x%5]") + .arg(device.name).arg(device.android).arg(device.battery). + arg(device.screenWidth).arg(device.screenHeight); + + auto *statusLabel = new QLabel(result, this); + statusLabel->setAlignment(Qt::AlignCenter); + statusLabel->setMaximumWidth(200); + statusLabel->setWordWrap(true); + + const QString styleSheet = QString("color: %1; font-weight: bold;") + .arg(device.status == "online" ? "green" : "red"); + statusLabel->setStyleSheet(styleSheet); + + // layout->setContentsMargins(10, 10, 10, 10); + layout->setSpacing(0); + layout->addWidget(statusLabel); +} diff --git a/views/DeviceWidget.h b/views/DeviceWidget.h new file mode 100644 index 0000000..2a5d0c8 --- /dev/null +++ b/views/DeviceWidget.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include "DeviceInfo.h" + +class DeviceWidget final : public QWidget { + Q_OBJECT + +public: + explicit DeviceWidget(const DeviceInfo& device, QWidget* parent = nullptr); +}; diff --git a/views/MainWindow.cpp b/views/MainWindow.cpp new file mode 100644 index 0000000..083efbf --- /dev/null +++ b/views/MainWindow.cpp @@ -0,0 +1,63 @@ +#include "MainWindow.h" + +#include +#include + +#include "DeviceInfo.h" +#include "DeviceWidget.h" +#include "dao/DeviceDAO.h" +#include "widget/FlowLayout.h" + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + // QPushButton* btn = new QPushButton("Hello Qt6!", this); + // setCentralWidget(btn); + + auto* scrollArea = new QScrollArea(this); + auto* contentWidget = new QWidget; + flowLayout = new FlowLayout(contentWidget); + + + + contentWidget->setContentsMargins(0, 0, 0, 0); // Убираем отступы у контейнера + flowLayout->setContentsMargins(0, 0, 0, 0); // Убираем отступы у layout + flowLayout->setSpacing(0); + + + QVector devices = DeviceDAO::getAllDevices(); + for (const auto& device : devices) { + auto* deviceWidget = new DeviceWidget(device); // Создаем виджет, используя первый элемент из списка devices + deviceWidget->setFixedWidth(220); + deviceWidget->setFixedHeight(400); + + deviceWidget->setContentsMargins(0, 0, 0, 0); + flowLayout->addWidget(deviceWidget); + } + + resize(800, 600); + + contentWidget->setLayout(flowLayout); + scrollArea->setWidget(contentWidget); + scrollArea->setWidgetResizable(true); + setCentralWidget(scrollArea); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &MainWindow::loadDevices); + timer->start(1000); // каждые 1 сек +} + + +void MainWindow::loadDevices() const { + const auto devices = DeviceDAO::getAllDevices(); + + // Очистить старые виджеты + QLayoutItem* item; + while ((item = flowLayout->takeAt(0)) != nullptr) { + delete item->widget(); // Удаляем виджет + delete item; // Удаляем сам элемент из layout + } + + // Добавляем новые виджеты для каждого устройства + for (const auto& device : devices) { + flowLayout->addWidget(new DeviceWidget(device)); + } +} diff --git a/views/MainWindow.h b/views/MainWindow.h new file mode 100644 index 0000000..9d43a82 --- /dev/null +++ b/views/MainWindow.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include +#include "DeviceInfo.h" +#include "widget/FlowLayout.h" + +class MainWindow final : public QMainWindow { + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = nullptr); + void loadDevices() const; + +private: + QWidget* central; + FlowLayout* flowLayout; + QTimer* timer; +}; diff --git a/views/MyWindow.cpp b/views/MyWindow.cpp deleted file mode 100644 index bb0f9ec..0000000 --- a/views/MyWindow.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "MyWindow.h" -#include - -MyWindow::MyWindow(QWidget *parent) : QMainWindow(parent) { - QPushButton* btn = new QPushButton("Hello Qt6!", this); - setCentralWidget(btn); -} \ No newline at end of file diff --git a/views/MyWindow.h b/views/MyWindow.h deleted file mode 100644 index b804cd3..0000000 --- a/views/MyWindow.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include - -class MyWindow : public QMainWindow { - Q_OBJECT -public: - MyWindow(QWidget *parent = nullptr); -}; \ No newline at end of file diff --git a/views/widget/FlowLayout.cpp b/views/widget/FlowLayout.cpp new file mode 100644 index 0000000..a520277 --- /dev/null +++ b/views/widget/FlowLayout.cpp @@ -0,0 +1,130 @@ +#include "FlowLayout.h" +#include + +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing): QLayout(parent), m_hSpace(hSpacing), + m_vSpace(vSpacing) { + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(const int margin, const int hSpacing, const int vSpacing) : m_hSpace(hSpacing), + m_vSpace(vSpacing) { + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() { + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} + +int FlowLayout::horizontalSpacing() const { + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const { + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int FlowLayout::count() const { + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const { + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) { + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + return nullptr; +} + +Qt::Orientations FlowLayout::expandingDirections() const { + return {}; +} + +bool FlowLayout::hasHeightForWidth() const { + return true; +} + +int FlowLayout::heightForWidth(int width) const { + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) { + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const { + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const { + QSize size; + for (const QLayoutItem *item: std::as_const(itemList)) + size = size.expandedTo(item->minimumSize()); + + const QMargins margins = contentsMargins(); + size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const { + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + for (QLayoutItem *item: std::as_const(itemList)) { + const QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} + +void FlowLayout::addItem(QLayoutItem *item) { + itemList.append(item); +} + +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const { + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } else { + return static_cast(parent)->spacing(); + } +} diff --git a/views/widget/FlowLayout.h b/views/widget/FlowLayout.h new file mode 100644 index 0000000..e134d6c --- /dev/null +++ b/views/widget/FlowLayout.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include + +class FlowLayout : public QLayout { +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + + ~FlowLayout(); + + void addItem(QLayoutItem *item) override; + + int horizontalSpacing() const; + + int verticalSpacing() const; + + Qt::Orientations expandingDirections() const override; + + bool hasHeightForWidth() const override; + + int heightForWidth(int) const override; + + int count() const override; + + QLayoutItem *itemAt(int index) const override; + + QSize minimumSize() const override; + + void setGeometry(const QRect &rect) override; + + QSize sizeHint() const override; + + QLayoutItem *takeAt(int index) override; + +private: + int doLayout(const QRect &rect, bool testOnly) const; + + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +};