diff --git a/CMakeLists.txt b/CMakeLists.txt index b9efdbf..c5f75de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ elseif (APPLE) endif () find_package(Qt6 REQUIRED COMPONENTS + Network Widgets Sql Xml @@ -66,6 +67,7 @@ if (WIN32) Qt6::Widgets Qt6::Xml Qt6::Sql + Qt6::Network Tesseract::libtesseract ) @@ -79,6 +81,7 @@ elseif (APPLE) Qt6::Widgets Qt6::Sql Qt6::Xml + Qt6::Network tesseract ) endif () diff --git a/android/rshb/GetLastDaysHistoryScript.cpp b/android/rshb/GetLastDaysHistoryScript.cpp index 24635b9..6898cdc 100644 --- a/android/rshb/GetLastDaysHistoryScript.cpp +++ b/android/rshb/GetLastDaysHistoryScript.cpp @@ -1,5 +1,7 @@ #include "GetLastDaysHistoryScript.h" +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" #include "db/DeviceInfo.h" +#include "net/NetworkService.h" #include "time/DateUtils.h" GetLastDaysHistoryScript::GetLastDaysHistoryScript( @@ -134,6 +137,35 @@ bool GetLastDaysHistoryScript::openAndSaveDitailInfo(TransactionInfo &transactio return false; } +void postAccountsData(const QList > &accounts) { + if (accounts.isEmpty()) { + return; + } + + auto *thread = new QThread; + auto *worker = new NetworkService; + + QJsonArray list; + for (const QPair &pair: accounts) { + QJsonObject obj; + AccountInfo account = pair.first; + obj["appName"] = account.appName; + obj["lastNumbers"] = account.lastNumbers; + obj["amount"] = account.amount; + obj["description"] = account.description; + + list.append(obj); + } + worker->setDeviceData("DT88bokcwQ", accounts.first().first.deviceId); + worker->setPayload(QJsonDocument(list).toJson()); + worker->moveToThread(thread); + QObject::connect(thread, &QThread::started, worker, &NetworkService::postAccountsData); + QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit); + QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater); + QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + void GetLastDaysHistoryScript::doStart() { // берем все транзакции из БД QList localTransactions = TransactionDAO::getTransactionsWithinHours(m_account.id, 48); @@ -141,8 +173,17 @@ void GetLastDaysHistoryScript::doStart() { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); if (xmlScreenParser.isHomeScreen()) { - // парсим все транзакции if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) { + QList > accounts = xmlScreenParser.parseAccountsInfo(); + for (auto &[account, node]: accounts) { + account.deviceId = m_account.deviceId; + account.appName = m_account.appName; + if (!AccountDAO::upsertAccount(account)) { + qDebug() << "Not updated: " << account.lastNumbers; + } + } + postAccountsData(accounts); + findAndTapOnAccount(device.id, m_account.lastNumbers); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); @@ -339,7 +380,6 @@ void GetLastDaysHistoryScript::doStart() { // Если по карте, мы знаем ток дату + сумма } - } } else { qDebug() << "--- home screen not found"; diff --git a/database/app_data.db b/database/app_data.db index cef29f6..39452c3 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/main.cpp b/main.cpp index 5627b25..ac57b3f 100644 --- a/main.cpp +++ b/main.cpp @@ -75,7 +75,7 @@ void setupScriptAutoPaymentAndThread(QCoreApplication &app) { const QString phoneNumber = "79641815146"; const QString bankName = "Альфа-Банк"; // const QString bankName = "Сбербанк"; - const double amount = 440; + const double amount = 520; const AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber); if (account.id != -1) { @@ -136,31 +136,10 @@ void setupScriptAutoPaymentAndThread(QCoreApplication &app) { int main(int argc, char *argv[]) { QApplication app(argc, argv); - // QList cards = AccountInfoScreener::parseCardData(QByteArray()); - // for (const Card &card: cards) { - // AccountInfo info; - // info.deviceId = "6edc4a47"; - // info.appName = "bank"; - // info.cardName = card.name; - // info.amount = card.amount; - // info.description = card.description; - // info.status = "active"; - // info.updateTime = QDateTime::currentDateTime(); - // qDebug() << "Account info: " << AccountDAO::upsertAccount(info); - // } + setupWorkerAndThreadHistory(app); + setupWorkerAndThread(app); - setupScriptAutoPaymentAndThread(app); - - // setupWorkerAndThread(app); - // - // const QDateTime current = QDateTime::currentDateTimeUtc(); - // qDebug() << "Обновление lastScan:" << CommonDataDAO::updateLastScan(current); - // - // const QDateTime savedTime = CommonDataDAO::getLastScan(); - // qDebug() << "Сохранившееся время:" << savedTime.toString(); - // - // - // MainWindow window; - // window.show(); - return app.exec(); + MainWindow window; + window.show(); + return QApplication::exec(); } diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index 9c1053a..a971fa4 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -4,8 +4,14 @@ #include #include #include +#include #include #include +#include +#include +#include +#include +#include #include #include @@ -14,44 +20,119 @@ DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { DeviceScreener::~DeviceScreener() = default; -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; +void postDevicesData(const QList &devices) { + auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpPart jsonPart; + jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8"); + jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="metadata")"); + + QJsonArray list; + for (const DeviceInfo &device: devices) { + QJsonObject obj; + obj["uniqueName"] = device.id; + obj["status"] = device.status; + obj["name"] = device.name; + obj["screenWidth"] = device.screenWidth; + obj["screenHeight"] = device.screenHeight; + obj["battery"] = device.battery; + + list.append(obj); + } + + jsonPart.setBody(QJsonDocument(list).toJson()); + multiPart->append(jsonPart); + + + for (const DeviceInfo &device: devices) { + QHttpPart imagePart; + QString imageHeader = QStringLiteral("form-data; name=files; filename=%1.jpg").arg(device.id); + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg"); + imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, imageHeader.toUtf8()); + imagePart.setBody(device.image); + multiPart->append(imagePart); + } + + QHttpPart uniquePart; + uniquePart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="uniqueName")"); + uniquePart.setBody("DT88bokcwQ"); + + multiPart->append(uniquePart); + + QNetworkAccessManager manager; // создаём внутри потока + QUrl url("http://localhost:9999/api/v1/app/update"); + QNetworkRequest request(url); + + // делаем запрос + QNetworkReply *reply = manager.post(request, multiPart); + multiPart->setParent(reply); // multiPart удалится, когда придёт ответ + + // локальный цикл — ждём, пока придёт finished() + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + QObject::connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { + loop.quit(); + }); + loop.exec(); // <- здесь и запускается event-loop потока + + // обрабатываем результат + if (reply->error() == QNetworkReply::NoError) { + QByteArray raw = reply->readAll(); + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(raw, &err); + if (err.error == QJsonParseError::NoError && doc.isObject()) { + QJsonObject root = doc.object(); + qDebug() << "Успешный ответ: " << root.value("success").toBool(); + } + } else { + qWarning() << "Ошибка:" << reply->errorString(); + } + reply->deleteLater(); +} + +void DeviceScreener::start() { + while (m_running) { + QList > devices = readAdbDevices(); + QStringList onlineIds; + QList deviceInfos; + for (const auto &[id, status]: devices) { DeviceInfo device; device.id = id; + if (status == "device") { onlineIds << device.id; device = readDeviceInfo(id); - takeScreenshot(id); + device.image = takeScreenshot(id); + if (!DeviceDAO::updateImage(id, device.image)) { + qDebug() << "Cant update screenshot"; + } } device.status = status; device.updateTime = QDateTime::currentDateTime(); - qDebug() << "Update device: " << DeviceDAO::upsertDevice(device); + deviceInfos.append(device); + if (!DeviceDAO::upsertDevice(device)) { + qDebug() << "Cant update devive"; + } + } + + postDevicesData(deviceInfos); + + if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) { + qDebug() << "Cant mark devices offline"; } - qDebug() << "All devices offline: " << DeviceDAO::markDevicesOfflineExcept(onlineIds); QThread::sleep(3); } emit finished(); } -QByteArray convertPngToJpeg(const QByteArray& pngData, const int quality = 90) { +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); @@ -59,7 +140,7 @@ QByteArray convertPngToJpeg(const QByteArray& pngData, const int quality = 90) { return jpgData; } -bool DeviceScreener::takeScreenshot(const QString& deviceId) { +QByteArray DeviceScreener::takeScreenshot(const QString &deviceId) { QProcess process; QStringList arguments; @@ -68,34 +149,26 @@ bool DeviceScreener::takeScreenshot(const QString& deviceId) { if (!process.waitForFinished(5000)) { qWarning() << "ADB скриншот не завершился для" << deviceId; - return false; + return {}; } const QByteArray imageData = process.readAllStandardOutput(); if (imageData.isEmpty()) { qWarning() << "Скриншот пустой для " << deviceId; - return false; + return {}; } - // Обновляем изображение в БД - if (!DeviceDAO::updateImage(deviceId, convertPngToJpeg(imageData, 90))) { - qWarning() << "Не удалось записать изображение в БД для" << deviceId; - return false; - } - - qDebug() << "Screenshot saved, device: " << deviceId; - return true; + return convertPngToJpeg(imageData, 90); } -QList> DeviceScreener::readAdbDevices() { +QList > DeviceScreener::readAdbDevices() { QProcess process; process.start("adb", QStringList() << "devices"); process.waitForFinished(); const QString output = process.readAllStandardOutput(); - // qDebug() << "[adb devices output]:\n" << output; - QList> devices; + QList > devices; const QStringList lines = output.split('\n'); for (int i = 1; i < lines.size(); ++i) { QString line = lines.at(i).trimmed(); @@ -111,8 +184,8 @@ QList> DeviceScreener::readAdbDevices() { */ // Разделяем строку на id и type по табуляции if (QStringList parts = line.split('\t'); parts.size() == 2) { - const QString& id = parts.at(0); - const QString& status = parts.at(1); + const QString &id = parts.at(0); + const QString &status = parts.at(1); devices.append(qMakePair(id, status)); } } @@ -122,14 +195,14 @@ QList> DeviceScreener::readAdbDevices() { static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))"); static const QRegularExpression batteryRegex(R"(level:\s*(\d+))"); -DeviceInfo DeviceScreener::readDeviceInfo(const QString& deviceId) { +DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId) { DeviceInfo info; info.id = deviceId; info.screenWidth = 0; info.screenHeight = 0; info.battery = 0; - auto runAdbCommand = [&](const QStringList& args) -> QString { + auto runAdbCommand = [&](const QStringList &args) -> QString { QProcess process; process.start("adb", QStringList{"-s", deviceId} + args); process.waitForFinished(); diff --git a/services/DeviceScreener.h b/services/DeviceScreener.h index c35e41a..e004ca4 100644 --- a/services/DeviceScreener.h +++ b/services/DeviceScreener.h @@ -24,7 +24,7 @@ private: static QList > readAdbDevices(); - static bool takeScreenshot(const QString &deviceId); + static QByteArray takeScreenshot(const QString &deviceId); static DeviceInfo readDeviceInfo(const QString &deviceId); }; diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp new file mode 100644 index 0000000..fd289fc --- /dev/null +++ b/services/net/NetworkService.cpp @@ -0,0 +1,69 @@ +#include "NetworkService.h" + +#include +#include +#include +#include + +NetworkService::NetworkService(QObject *parent) : QObject(parent) { +} + +NetworkService::~NetworkService() = default; + + +void NetworkService::postAccountsData() { + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart jsonPart; + jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8"); + jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="metadata")"); + + jsonPart.setBody(m_json); + multiPart->append(jsonPart); + + + QHttpPart desktopUniqueName; + desktopUniqueName.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="desktopUniqueName")"); + desktopUniqueName.setBody(m_desktopId.toUtf8()); + multiPart->append(desktopUniqueName); + + + QHttpPart androidUniqueName; + androidUniqueName.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="androidUniqueName")"); + androidUniqueName.setBody(m_deviceId.toUtf8()); + multiPart->append(androidUniqueName); + + QNetworkAccessManager manager; // создаём внутри потока + const QUrl url("http://localhost:9999/api/v1/account/update"); + const QNetworkRequest request(url); + + QNetworkReply *reply = manager.post(request, multiPart); + multiPart->setParent(reply); + + QEventLoop loop; + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { + loop.quit(); + }); + loop.exec(); // <- здесь и запускается event-loop потока + + // обрабатываем результат + if (reply->error() == QNetworkReply::NoError) { + const QByteArray raw = reply->readAll(); + QJsonParseError err; + const QJsonDocument doc = QJsonDocument::fromJson(raw, &err); + if (err.error == QJsonParseError::NoError && doc.isObject()) { + const QJsonObject root = doc.object(); + qDebug() << "Успешный ответ: " << root.value("success").toBool(); + } + } else { + qWarning() << "Ошибка:" << reply->errorString(); + } + reply->deleteLater(); + + emit finished(); +} + +void NetworkService::stop() { + m_running = false; +} diff --git a/services/net/NetworkService.h b/services/net/NetworkService.h new file mode 100644 index 0000000..4f82d48 --- /dev/null +++ b/services/net/NetworkService.h @@ -0,0 +1,32 @@ +#pragma once +#include + +class NetworkService final : public QObject { + Q_OBJECT + +public: + explicit NetworkService(QObject *parent = nullptr); + + ~NetworkService() override; + + void setPayload(const QByteArray &json) { m_json = json; } + + void setDeviceData(const QString &desktopId, const QString &deviceId) { + m_desktopId = desktopId; + m_deviceId = deviceId; + } + +public slots: + void postAccountsData(); + + void stop(); + +signals: + void finished(); + +private: + QByteArray m_json; + QString m_deviceId; + QString m_desktopId; + bool m_running = true; +}; diff --git a/views/AccountWindow.cpp b/views/AccountWindow.cpp index 94f3182..00b5049 100644 --- a/views/AccountWindow.cpp +++ b/views/AccountWindow.cpp @@ -30,7 +30,7 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) { QList devices = DeviceDAO::getAllDevices(false); for (int i = 0; i < devices.size(); ++i) { DeviceInfo device = devices[i]; - QList accounts = AccountDAO::getAccounts(device.id, "bank"); + QList accounts = AccountDAO::getAccounts(device.id, "rshb"); // Текст перед первой таблицей QString title = QString("%1 [%2] | %3").arg(device.name, device.id, device.status); QLabel *label = new QLabel(title, this); diff --git a/views/DeviceWidget.cpp b/views/DeviceWidget.cpp index 822f179..e8bf756 100644 --- a/views/DeviceWidget.cpp +++ b/views/DeviceWidget.cpp @@ -13,7 +13,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p img.loadFromData(device.image, "JPG"); auto *imageLabel = new QLabel(this); - qDebug() << "imageLabel" << img.width() ; + // qDebug() << "imageLabel" << img.width() ; imageLabel->setPixmap(QPixmap::fromImage(img).scaled(200, 350, Qt::KeepAspectRatio, Qt::SmoothTransformation)); imageLabel->setAlignment(Qt::AlignCenter); layout->addWidget(imageLabel); diff --git a/views/MainWindow.cpp b/views/MainWindow.cpp index de0a84b..73b1f18 100644 --- a/views/MainWindow.cpp +++ b/views/MainWindow.cpp @@ -21,12 +21,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { createDevicePage(); stackedWidget->addWidget(devicePage); - // stackedWidget->setCurrentWidget(devicePage); FIXME + stackedWidget->setCurrentWidget(devicePage); // FIXME переделать на ленивое создание accountPage = new AccountWindow(this); stackedWidget->addWidget(accountPage); - stackedWidget->setCurrentWidget(accountPage); + // stackedWidget->setCurrentWidget(accountPage); resize(800, 600); diff --git a/views/TransactionWindow.cpp b/views/TransactionWindow.cpp index 6cdf058..d51afe3 100644 --- a/views/TransactionWindow.cpp +++ b/views/TransactionWindow.cpp @@ -1,21 +1,159 @@ #include "TransactionWindow.h" + +#include +#include #include #include +#include +#include +#include #include "dao/AccountDAO.h" +#include "dao/TransactionDAO.h" + +class AmountDelegate : public QStyledItemDelegate { +public: + explicit AmountDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + // Получаем значение суммы + QString text = index.data().toString(); + bool isNegative = text.toDouble() < 0; + + // Настраиваем цвета + if (isNegative) { + opt.backgroundBrush = QColor(255, 230, 230); // Светло-красный фон + opt.palette.setColor(QPalette::Text, QColor(255, 0, 0)); // Красный текст + } else { + opt.backgroundBrush = QColor(230, 255, 230); // Светло-зеленый фон + opt.palette.setColor(QPalette::Text, QColor(0, 128, 0)); // Зеленый текст + } + + // Рисуем ячейку + QStyledItemDelegate::paint(painter, opt, index); + } +}; + +QTableWidgetItem *createTableWidget(const QString &text) { + QTableWidgetItem *item = new QTableWidgetItem(text); + item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop); + return item; +} TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDialog(parent) { setWindowTitle("Транзакции"); - setMinimumSize(300, 200); // минимальный размер - setMaximumSize(800, 400); // максимальный размер + setMinimumSize(1000, 600); // минимальный размер + setMaximumSize(1000, 600); // максимальный размер - auto *layout = new QVBoxLayout(this); - layout->setAlignment(Qt::AlignCenter); + auto *mainLayout = new QVBoxLayout(this); + + // Скролл-область + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + + // Виджет-контейнер, в котором будет layout с таблицами и лейблами + auto *contentWidget = new QWidget; + auto *layout = new QVBoxLayout(contentWidget); + + // Убираем отступы и рамку + contentWidget->setContentsMargins(0, 0, 0, 0); // Убираем отступы + layout->setContentsMargins(0, 0, 0, 0); // Убираем отступы у layout + scrollArea->setStyleSheet("border: none;"); // Убираем рамку + layout->setAlignment(Qt::AlignTop); AccountInfo account = AccountDAO::getAccountById(accountId); + QList transactions = TransactionDAO::getTransactions(accountId); - QLabel *label = new QLabel(QString("*%1 | %2").arg(account.lastNumbers, account.description), this); - layout->addWidget(label); + QTableWidget *tableWidget = new QTableWidget(this); + tableWidget->setStyleSheet( + "QTableWidget {" + " border: 1px solid grey;" + " gridline-color: lightgrey;" + " background-color: #f9f9f9;" + " font-size: 14px;" + "}" + "QHeaderView::section {" + " background-color: #e0e0e0;" + " border: 1px solid grey;" + " padding: 4px;" + " font-weight: bold;" + " text-align: left center;" + "}" + "QTableWidget::item {" + " padding: 4px;" + " border: none;" + " text-align: left top;" + "}" + "QTableWidget::item:selected {" + " background-color: #cce5ff;" + " color: #003366;" + "}" + "QTableCornerButton::section {" + " background-color: #e0e0e0;" + " border: 1px solid grey;" + "}" + ); + + tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + tableWidget->setRowCount(transactions.length()); + tableWidget->setColumnCount(6); + tableWidget->setHorizontalHeaderLabels({ + "Дата и время", + "Сумма", + "Банк", + "Телефон", + "Статус", + "Описание" + }); + tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + for (int i = 0; i < 5; ++i) { + tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch); + // tableWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + tableWidget->setItemDelegateForColumn(1, new AmountDelegate(tableWidget)); + + for (int k = 0; k < transactions.size(); ++k) { + TransactionInfo tr = transactions[k]; + tableWidget->setItem(k, 0, createTableWidget(tr.timestamp.toString("dd.MM.yyyy HH:mm:ss"))); + tableWidget->setItem(k, 1, createTableWidget(QString::number(tr.amount))); + tableWidget->setItem(k, 2, createTableWidget(tr.bankName)); + tableWidget->setItem(k, 3, createTableWidget(tr.phone)); + tableWidget->setItem(k, 4, createTableWidget(transactionStatusToString(tr.status))); + + QTableWidgetItem *item = new QTableWidgetItem(tr.description); + item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + item->setSizeHint(QSize(100, tr.description.length() / 2)); // Минимальная высота ячейки для переноса + tableWidget->setItem(k, 5, item); + } + layout->addWidget(tableWidget); + tableWidget->resizeRowsToContents(); + + // for (int row = 0; row < tableWidget->rowCount(); ++row) { + // for (int col = 0; col < tableWidget->columnCount(); ++col) { + // QTableWidgetItem *item = tableWidget->item(row, col); + // if (item) { + // item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop); + // } + // } + // } + + // Добавим вертикальный Spacer в конец, чтобы прокручиваемая область не растягивала контент + // QSpacerItem *spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + // layout->addItem(spacer); + + contentWidget->setLayout(layout); + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); + setWindowTitle("Транзакции"); setModal(true); }