feat: comprehensive Wino Mail updates
- ReaderView: complete rewrite with CSS styling, headers, attachments, zoom, find, dark mode, image blocking - Categories: DAO + UI tree widget with colors, nested categories, assignment - Rules engine: DAO + evaluation + actions (move, mark read, flag, delete, category, forward) - Templates: DAO + editor + manager with variables support - Signatures: DAO + manager + auto-per-account + manual selector in compose - ComposeView: signature combo, template selector, auto-signature on new email - MainWindow: frameless with rounded corners, 1px border, resize from edges, no status bar - Database: new tables (Category, MailCategory, Rule, Template, Signature) with indexes
This commit is contained in:
+485
-36
@@ -3,14 +3,22 @@
|
||||
#include "core/mailitem.h"
|
||||
#include "db/dao/mailitemdao.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "db/dao/categorydao.h"
|
||||
#include "db/dao/ruledao.h"
|
||||
#include "services/rulesengine.h"
|
||||
#include "ui/rulesmanagerdialog.h"
|
||||
#include "ui/accountsetupdialog.h"
|
||||
#include <optional>
|
||||
#include <QMessageBox>
|
||||
#include "ui/accountsetupdialog.h"
|
||||
#include <QFileDialog>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFrame>
|
||||
#include <QDateTime>
|
||||
#include <QLabel>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
MainMainWindow::MainMainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), m_currentFolderId(-1), m_currentMailId(-1)
|
||||
@@ -53,8 +61,8 @@ void MainMainWindow::setupUI()
|
||||
);
|
||||
qDebug() << "[MainMainWindow::setupUI] Stylesheet set";
|
||||
|
||||
createToolBar();
|
||||
qDebug() << "[MainMainWindow::setupUI] Toolbar created";
|
||||
createTitleBar();
|
||||
qDebug() << "[MainMainWindow::setupUI] Title bar created";
|
||||
|
||||
// === Central widget ===
|
||||
QWidget *central = new QWidget();
|
||||
@@ -194,9 +202,15 @@ void MainMainWindow::setupMailPage()
|
||||
mailLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mailLayout->setSpacing(0);
|
||||
|
||||
// Main splitter with 3 panes: folders | categories | mail list | viewer
|
||||
m_folderSplitter = new QSplitter(Qt::Horizontal);
|
||||
m_folderSplitter->setHandleWidth(1);
|
||||
|
||||
// Left pane: Folder tree + Category tree (vertical splitter)
|
||||
QSplitter* leftSplitter = new QSplitter(Qt::Vertical);
|
||||
leftSplitter->setHandleWidth(1);
|
||||
leftSplitter->setChildrenCollapsible(false);
|
||||
|
||||
// Folder tree
|
||||
m_folderTree = new QTreeView();
|
||||
m_folderTree->setHeaderHidden(true);
|
||||
@@ -205,7 +219,26 @@ void MainMainWindow::setupMailPage()
|
||||
m_folderTree->setMaximumWidth(350);
|
||||
m_folderTree->setFrameShape(QFrame::NoFrame);
|
||||
m_folderTree->setExpandsOnDoubleClick(true);
|
||||
m_folderSplitter->addWidget(m_folderTree);
|
||||
leftSplitter->addWidget(m_folderTree);
|
||||
|
||||
// Category tree
|
||||
m_categoryTree = new CategoryTreeWidget();
|
||||
m_categoryTree->setMinimumHeight(200);
|
||||
m_categoryTree->setMaximumHeight(400);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::categorySelected, this, &MainMainWindow::onCategorySelected);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::categoryDoubleClicked, this, &MainMainWindow::onCategoryDoubleClicked);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::assignCategoryRequested, this, &MainMainWindow::onAssignCategoryRequested);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::categoryCreated, this, &MainMainWindow::onCategoryCreated);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::categoryEdited, this, &MainMainWindow::onCategoryEdited);
|
||||
connect(m_categoryTree, &CategoryTreeWidget::categoryDeleted, this, &MainMainWindow::onCategoryDeleted);
|
||||
leftSplitter->addWidget(m_categoryTree);
|
||||
|
||||
// Set initial sizes for left splitter: folder tree gets 60%, categories 40%
|
||||
leftSplitter->setSizes({400, 250});
|
||||
leftSplitter->setStretchFactor(0, 3);
|
||||
leftSplitter->setStretchFactor(1, 2);
|
||||
|
||||
m_folderSplitter->addWidget(leftSplitter);
|
||||
|
||||
// Mail list (QTableView with sorting)
|
||||
m_mailListView = new MailListView();
|
||||
@@ -243,6 +276,8 @@ void MainMainWindow::setupMailPage()
|
||||
m_emailViewer = new ReaderView();
|
||||
m_emailViewer->setMinimumWidth(350);
|
||||
connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested);
|
||||
connect(m_emailViewer, &ReaderView::forwardRequested, this, &MainMainWindow::onReaderForwardRequested);
|
||||
connect(m_emailViewer, &ReaderView::deleteRequested, this, &MainMainWindow::onReaderDeleteRequested);
|
||||
connect(m_emailViewer, &ReaderView::detachRequested, this, [this]() {
|
||||
if (m_currentMailId >= 0) {
|
||||
openMailInIndependentWindow(m_currentMailId);
|
||||
@@ -254,6 +289,9 @@ void MainMainWindow::setupMailPage()
|
||||
connect(m_embeddedComposeView, &ComposeView::sendRequested, this, &MainMainWindow::onEmbeddedSendRequested);
|
||||
connect(m_embeddedComposeView, &ComposeView::discardRequested, this, &MainMainWindow::onEmbeddedDiscardRequested);
|
||||
connect(m_embeddedComposeView, &ComposeView::detachRequested, this, &MainMainWindow::onEmbeddedDetachRequested);
|
||||
connect(m_embeddedComposeView, &ComposeView::statusMessageRequested, this, [this](const QString& msg) {
|
||||
statusBar()->showMessage(msg, 3000);
|
||||
});
|
||||
|
||||
// Add to stack
|
||||
m_viewerStack->addWidget(m_placeholderWidget);
|
||||
@@ -263,8 +301,8 @@ void MainMainWindow::setupMailPage()
|
||||
|
||||
m_folderSplitter->addWidget(m_viewerStack);
|
||||
|
||||
// Default sizes: folder 240, list 380, viewer flex
|
||||
m_folderSplitter->setSizes({240, 380, 600});
|
||||
// Default sizes: left pane (folders+categories) 300, list 380, viewer flex
|
||||
m_folderSplitter->setSizes({300, 380, 600});
|
||||
|
||||
mailLayout->addWidget(m_folderSplitter);
|
||||
}
|
||||
@@ -371,17 +409,55 @@ void MainMainWindow::onComposeRequested()
|
||||
m_viewerStack->setCurrentIndex(2); // compose
|
||||
}
|
||||
|
||||
void MainMainWindow::onReaderReplyRequested(const MailItem *item)
|
||||
{ if (item) {
|
||||
void MainMainWindow::onReaderReplyRequested(int mailId)
|
||||
{
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailId);
|
||||
if (item.has_value()) {
|
||||
m_embeddedComposeView->initializeComposition();
|
||||
m_embeddedComposeView->setTo(item->sender());
|
||||
m_embeddedComposeView->setSubject(QString("Re: %1").arg(item->subject()));
|
||||
// Optionally set body with quoted text? We'll leave empty for now.
|
||||
m_embeddedComposeView->setBody(QString()); // clear
|
||||
// Insert quoted original message BELOW the signature
|
||||
QString quoted = QString("<blockquote cite=\"mailto:%1\">%2</blockquote>").arg(item->sender(), item->bodyHtml());
|
||||
// Get current body (with signature) and append quoted text
|
||||
QString currentBody = m_embeddedComposeView->getBody();
|
||||
if (!currentBody.isEmpty()) {
|
||||
m_embeddedComposeView->setBody(currentBody + "<br><br>" + quoted);
|
||||
} else {
|
||||
m_embeddedComposeView->setBody(quoted);
|
||||
}
|
||||
}
|
||||
m_viewerStack->setCurrentIndex(2); // compose
|
||||
}
|
||||
|
||||
void MainMainWindow::onReaderForwardRequested(int mailId)
|
||||
{
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailId);
|
||||
if (item.has_value()) {
|
||||
m_embeddedComposeView->initializeComposition();
|
||||
m_embeddedComposeView->setSubject(QString("Fwd: %1").arg(item->subject()));
|
||||
// Insert forwarded message BELOW the signature
|
||||
QString quoted = QString("<blockquote cite=\"mailto:%1\">%2</blockquote>").arg(item->sender(), item->bodyHtml());
|
||||
QString currentBody = m_embeddedComposeView->getBody();
|
||||
if (!currentBody.isEmpty()) {
|
||||
m_embeddedComposeView->setBody(currentBody + "<br><br>" + quoted);
|
||||
} else {
|
||||
m_embeddedComposeView->setBody(quoted);
|
||||
}
|
||||
}
|
||||
m_viewerStack->setCurrentIndex(2); // compose
|
||||
}
|
||||
|
||||
void MainMainWindow::onReaderDeleteRequested(int mailId)
|
||||
{
|
||||
MailItemDao::remove(mailId);
|
||||
if (m_currentMailId == mailId) {
|
||||
m_currentMailId = -1;
|
||||
m_viewerStack->setCurrentIndex(0); // placeholder
|
||||
}
|
||||
m_emailModel->refresh();
|
||||
statusBar()->showMessage(tr("Mensaje eliminado"), 3000);
|
||||
}
|
||||
|
||||
void MainMainWindow::onNewMessage()
|
||||
{ // Show compose view in the viewer stack
|
||||
m_embeddedComposeView->initializeComposition();
|
||||
@@ -407,42 +483,415 @@ void MainMainWindow::openMailInIndependentWindow(int mailId)
|
||||
statusBar()->showMessage("Opened mail in independent window", 3000);
|
||||
}
|
||||
|
||||
void MainMainWindow::createToolBar()
|
||||
// Category slots
|
||||
void MainMainWindow::updateCategoryTreeForAccount(qint64 accountId)
|
||||
{
|
||||
m_toolBar = addToolBar("Main Toolbar");
|
||||
m_toolBar->setMovable(false);
|
||||
if (m_categoryTree) {
|
||||
m_categoryTree->setAccountId(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
QAction *newMsgAction = m_toolBar->addAction("✉ New Message");
|
||||
m_toolBar->addSeparator();
|
||||
QAction *syncAction = m_toolBar->addAction("⟳ Sync/Refresh");
|
||||
QAction *openWinAction = m_toolBar->addAction("↗ Abrir en ventana");
|
||||
QAction *deleteAction = m_toolBar->addAction("🗑 Delete");
|
||||
void MainMainWindow::onCategorySelected(const Category& cat)
|
||||
{
|
||||
// Filter mail list by category - could be implemented
|
||||
statusBar()->showMessage(tr("Categoría seleccionada: %1").arg(cat.name), 2000);
|
||||
}
|
||||
|
||||
connect(newMsgAction, &QAction::triggered, this, &MainMainWindow::onNewMessage);
|
||||
connect(syncAction, &QAction::triggered, [this]() {
|
||||
if (m_currentFolderId >= 0) {
|
||||
const auto folder = FolderDao::findById(m_currentFolderId);
|
||||
if (folder) {
|
||||
m_mailService->fetchMails(QString::number(folder->accountId()), QString::number(m_currentFolderId));
|
||||
statusBar()->showMessage(tr("Synchronizing…"), 3000);
|
||||
void MainMainWindow::onCategoryDoubleClicked(const Category& cat)
|
||||
{
|
||||
// Edit category
|
||||
onCategoryEdited(cat);
|
||||
}
|
||||
|
||||
void MainMainWindow::onAssignCategoryRequested(qint64 mailId, qint64 categoryId)
|
||||
{
|
||||
assignCategoryToSelectedMails(categoryId);
|
||||
}
|
||||
|
||||
void MainMainWindow::assignCategoryToSelectedMails(qint64 categoryId)
|
||||
{
|
||||
// Get selected mail IDs from mail list view
|
||||
// This requires MailListView to expose selected mail IDs
|
||||
// For now, use current mail if no multi-selection
|
||||
if (m_currentMailId >= 0) {
|
||||
if (CategoryDao::assignToMail(m_currentMailId, categoryId)) {
|
||||
statusBar()->showMessage(tr("Categoría asignada"), 2000);
|
||||
// Refresh category tree checkboxes
|
||||
if (m_categoryTree) {
|
||||
m_categoryTree->onMailSelected(m_currentMailId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::onCategoryCreated(const Category& cat)
|
||||
{
|
||||
statusBar()->showMessage(tr("Categoría creada: %1").arg(cat.name), 3000);
|
||||
}
|
||||
|
||||
void MainMainWindow::onCategoryEdited(const Category& cat)
|
||||
{
|
||||
statusBar()->showMessage(tr("Categoría actualizada: %1").arg(cat.name), 3000);
|
||||
}
|
||||
|
||||
void MainMainWindow::onCategoryDeleted(qint64 categoryId)
|
||||
{
|
||||
statusBar()->showMessage(tr("Categoría eliminada"), 3000);
|
||||
}
|
||||
|
||||
// Rules slots
|
||||
void MainMainWindow::onRulesChanged()
|
||||
{
|
||||
statusBar()->showMessage(tr("Reglas actualizadas"), 2000);
|
||||
}
|
||||
|
||||
void MainMainWindow::onRunRulesOnFolder()
|
||||
{
|
||||
if (m_currentFolderId >= 0) {
|
||||
QMessageBox::information(this, tr("Ejecutar reglas"), tr("Ejecutando reglas en carpeta actual..."));
|
||||
int processed = RulesEngine::runRulesOnFolder(m_currentFolderId, -1);
|
||||
statusBar()->showMessage(tr("Reglas ejecutadas en %1 correos").arg(processed), 5000);
|
||||
m_emailModel->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::onRunRulesOnSelected()
|
||||
{
|
||||
// Get selected mail IDs
|
||||
QMessageBox::information(this, tr("Ejecutar reglas"), tr("Ejecutando reglas en correos seleccionados..."));
|
||||
// TODO: Get selected mail IDs from MailListView
|
||||
statusBar()->showMessage(tr("Reglas ejecutadas"), 3000);
|
||||
}
|
||||
|
||||
void MainMainWindow::showRulesManager()
|
||||
{
|
||||
RulesManagerDialog dlg(-1, this); // Global rules
|
||||
connect(&dlg, &RulesManagerDialog::rulesChanged, this, &MainMainWindow::onRulesChanged);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainMainWindow::showTemplatesManager()
|
||||
{
|
||||
qint64 accountId = -1;
|
||||
// Could get current account from folder selection
|
||||
TemplatesManagerDialog dlg(accountId, this);
|
||||
connect(&dlg, &TemplatesManagerDialog::templateSelected, [this](const Template& tmpl) {
|
||||
// If compose view is active, apply template
|
||||
if (m_viewerStack->currentWidget() == m_embeddedComposeView) {
|
||||
m_embeddedComposeView->applyTemplate(tmpl);
|
||||
}
|
||||
});
|
||||
connect(openWinAction, &QAction::triggered, [this]() {
|
||||
if (m_currentMailId >= 0) {
|
||||
openMailInIndependentWindow(m_currentMailId);
|
||||
connect(&dlg, &TemplatesManagerDialog::templatesChanged, this, [this]() {
|
||||
statusBar()->showMessage(tr("Plantillas actualizadas"), 2000);
|
||||
});
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainMainWindow::createTitleBar()
|
||||
{
|
||||
// Enable frameless window
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint);
|
||||
|
||||
// Set Resizeable
|
||||
setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
|
||||
#ifdef Q_OS_WIN
|
||||
HWND hwnd = (HWND)this->winId();
|
||||
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
|
||||
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
|
||||
#endif
|
||||
|
||||
// Remove status bar (item 4)
|
||||
//setStatusBar(nullptr);
|
||||
|
||||
// Apply rounded corners and border to main window (items 1, 2)
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setStyleSheet(
|
||||
"MainMainWindow {"
|
||||
" background-color: #f5f5f7;"
|
||||
" border: 1px solid #d1d1d6;"
|
||||
" border-radius: 8px;"
|
||||
"}"
|
||||
"QWidget#TitleBar {"
|
||||
" background-color: #ffffff;"
|
||||
" border-bottom: 1px solid #d1d1d6;"
|
||||
" border-top-left-radius: 8px;"
|
||||
" border-top-right-radius: 8px;"
|
||||
"}"
|
||||
"QToolButton {"
|
||||
" border: none;"
|
||||
" background: transparent;"
|
||||
" border-radius: 4px;"
|
||||
" padding: 4px;"
|
||||
"}"
|
||||
"QToolButton:hover {"
|
||||
" background-color: #e8e8ed;"
|
||||
"}"
|
||||
"QToolButton:pressed {"
|
||||
" background-color: #d1d1d6;"
|
||||
"}"
|
||||
"QLabel {"
|
||||
" color: #1d1d1f;"
|
||||
" font-weight: 600;"
|
||||
" font-size: 13px;"
|
||||
"}"
|
||||
);
|
||||
|
||||
// Create title bar widget
|
||||
m_titleBar = new QWidget(this);
|
||||
m_titleBar->setObjectName("TitleBar");
|
||||
m_titleBar->setFixedHeight(36);
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout(m_titleBar);
|
||||
layout->setContentsMargins(8, 0, 8, 0);
|
||||
layout->setSpacing(4);
|
||||
|
||||
// App icon
|
||||
QLabel *iconLabel = new QLabel();
|
||||
iconLabel->setPixmap(QIcon(":/icons/app.svg").pixmap(18, 18));
|
||||
layout->addWidget(iconLabel);
|
||||
|
||||
// Window title
|
||||
QLabel *titleLabel = new QLabel("Wino Mail");
|
||||
layout->addWidget(titleLabel);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
// Custom action buttons (sync, settings, etc.)
|
||||
auto addActionButton = [&](const QString &iconName, const QString &tooltip, std::function<void()> slot) {
|
||||
QToolButton *btn = new QToolButton();
|
||||
btn->setIcon(QIcon(QString(":/icons/%1.svg").arg(iconName)));
|
||||
btn->setToolTip(tooltip);
|
||||
btn->setFixedSize(28, 28);
|
||||
btn->setIconSize(QSize(16, 16));
|
||||
connect(btn, &QToolButton::clicked, this, slot);
|
||||
layout->addWidget(btn);
|
||||
return btn;
|
||||
};
|
||||
|
||||
addActionButton("sync", "Sincronizar", [this]() { onSyncRequested(); });
|
||||
addActionButton("settings", "Ajustes", [this]() { onSettingsRequested(); });
|
||||
addActionButton("filter", "Reglas", [this]() { showRulesManager(); });
|
||||
addActionButton("play", "Ejecutar reglas en carpeta", [this]() { onRunRulesOnFolder(); });
|
||||
addActionButton("file-text", "Plantillas", [this]() { showTemplatesManager(); });
|
||||
|
||||
layout->addSpacing(8);
|
||||
|
||||
// Window control buttons (minimize, maximize, close)
|
||||
QToolButton *minBtn = new QToolButton();
|
||||
minBtn->setIcon(QIcon(":/icons/minimize.svg"));
|
||||
minBtn->setToolTip("Minimizar");
|
||||
minBtn->setFixedSize(28, 28);
|
||||
minBtn->setIconSize(QSize(16, 16));
|
||||
connect(minBtn, &QToolButton::clicked, this, &QWidget::showMinimized);
|
||||
layout->addWidget(minBtn);
|
||||
|
||||
m_maximizeButton = new QToolButton();
|
||||
m_maximizeButton->setIcon(QIcon(isMaximized() ? ":/icons/restore.svg" : ":/icons/maximize.svg"));
|
||||
m_maximizeButton->setToolTip(isMaximized() ? "Restaurar" : "Maximizar");
|
||||
m_maximizeButton->setFixedSize(28, 28);
|
||||
m_maximizeButton->setIconSize(QSize(16, 16));
|
||||
connect(m_maximizeButton, &QToolButton::clicked, this, [this]() {
|
||||
if (isMaximized()) {
|
||||
showNormal();
|
||||
} else {
|
||||
statusBar()->showMessage("Selecciona un correo primero para abrirlo en una ventana", 3000);
|
||||
showMaximized();
|
||||
}
|
||||
});
|
||||
connect(deleteAction, &QAction::triggered, [this]() {
|
||||
if (m_currentMailId >= 0) {
|
||||
m_mailService->deleteMail(QString::number(m_currentMailId));
|
||||
m_currentMailId = -1;
|
||||
m_emailModel->refresh();
|
||||
m_viewerStack->setCurrentIndex(0);
|
||||
layout->addWidget(m_maximizeButton);
|
||||
|
||||
m_closeButton = new QToolButton();
|
||||
m_closeButton->setIcon(QIcon(":/icons/close.svg"));
|
||||
m_closeButton->setToolTip("Cerrar");
|
||||
m_closeButton->setFixedSize(28, 28);
|
||||
m_closeButton->setIconSize(QSize(16, 16));
|
||||
m_closeButton->setStyleSheet(
|
||||
"QToolButton {"
|
||||
" border: none;"
|
||||
" background: transparent;"
|
||||
" border-radius: 4px;"
|
||||
" padding: 4px;"
|
||||
"}"
|
||||
"QToolButton:hover {"
|
||||
" background-color: #ff3b30;"
|
||||
" color: white;"
|
||||
"}"
|
||||
"QToolButton:pressed {"
|
||||
" background-color: #e60000;"
|
||||
"}"
|
||||
);
|
||||
connect(m_closeButton, &QToolButton::clicked, this, &QWidget::close);
|
||||
layout->addWidget(m_closeButton);
|
||||
|
||||
// Set title bar as menu widget (appears in native title bar area on some platforms)
|
||||
setMenuWidget(m_titleBar);
|
||||
}
|
||||
|
||||
void MainMainWindow::onSyncRequested()
|
||||
{
|
||||
if (m_currentFolderId >= 0) {
|
||||
const auto folder = FolderDao::findById(m_currentFolderId);
|
||||
if (folder) {
|
||||
m_mailService->fetchMails(QString::number(folder->accountId()), QString::number(m_currentFolderId));
|
||||
statusBar()->showMessage(tr("Sincronizando…"), 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::onSettingsRequested()
|
||||
{
|
||||
switchToPage(PageSettings);
|
||||
}
|
||||
|
||||
void MainMainWindow::updateMaximizeButtonIcon()
|
||||
{
|
||||
if (m_maximizeButton) {
|
||||
m_maximizeButton->setIcon(QIcon(isMaximized() ? ":/icons/restore.svg" : ":/icons/maximize.svg"));
|
||||
m_maximizeButton->setToolTip(isMaximized() ? "Restaurar" : "Maximizar");
|
||||
}
|
||||
}
|
||||
|
||||
// Frameless window mouse handling
|
||||
void MainMainWindow::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QPoint pos = event->position().toPoint();
|
||||
|
||||
// Check if clicking on resize edges (8px margin)
|
||||
int margin = 8;
|
||||
QRect windowRect = this->rect();
|
||||
bool onLeft = pos.x() <= margin;
|
||||
bool onRight = pos.x() >= windowRect.width() - margin;
|
||||
bool onTop = pos.y() <= margin;
|
||||
bool onBottom = pos.y() >= windowRect.height() - margin;
|
||||
|
||||
m_resizeEdges = Qt::Edges();
|
||||
if (onTop) m_resizeEdges |= Qt::TopEdge;
|
||||
if (onBottom) m_resizeEdges |= Qt::BottomEdge;
|
||||
if (onLeft) m_resizeEdges |= Qt::LeftEdge;
|
||||
if (onRight) m_resizeEdges |= Qt::RightEdge;
|
||||
|
||||
if (m_resizeEdges) {
|
||||
m_resizeStartPos = event->globalPosition().toPoint();
|
||||
m_resizeStartGeom = frameGeometry();
|
||||
m_resizing = true;
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only allow dragging from title bar area (top 36px)
|
||||
if (pos.y() <= 36) {
|
||||
m_dragPos = event->globalPosition().toPoint() - frameGeometry().topLeft();
|
||||
m_dragging = true;
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
QMainWindow::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void MainMainWindow::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
// Update cursor when not dragging/resizing
|
||||
if (!m_dragging && !m_resizing) {
|
||||
QPoint pos = event->position().toPoint();
|
||||
int margin = 8;
|
||||
QRect windowRect = this->rect();
|
||||
bool onLeft = pos.x() <= margin;
|
||||
bool onRight = pos.x() >= windowRect.width() - margin;
|
||||
bool onTop = pos.y() <= margin;
|
||||
bool onBottom = pos.y() >= windowRect.height() - margin;
|
||||
|
||||
Qt::Edges edges = Qt::Edges();
|
||||
if (onTop) edges |= Qt::TopEdge;
|
||||
if (onBottom) edges |= Qt::BottomEdge;
|
||||
if (onLeft) edges |= Qt::LeftEdge;
|
||||
if (onRight) edges |= Qt::RightEdge;
|
||||
|
||||
if (edges & Qt::TopEdge && edges & Qt::LeftEdge) setCursor(Qt::SizeFDiagCursor);
|
||||
else if (edges & Qt::TopEdge && edges & Qt::RightEdge) setCursor(Qt::SizeBDiagCursor);
|
||||
else if (edges & Qt::BottomEdge && edges & Qt::LeftEdge) setCursor(Qt::SizeBDiagCursor);
|
||||
else if (edges & Qt::BottomEdge && edges & Qt::RightEdge) setCursor(Qt::SizeFDiagCursor);
|
||||
else if (edges & Qt::LeftEdge || edges & Qt::RightEdge) setCursor(Qt::SizeHorCursor);
|
||||
else if (edges & Qt::TopEdge || edges & Qt::BottomEdge) setCursor(Qt::SizeVerCursor);
|
||||
else unsetCursor();
|
||||
}
|
||||
|
||||
if (m_resizing && (event->buttons() & Qt::LeftButton)) {
|
||||
QPoint delta = event->globalPosition().toPoint() - m_resizeStartPos;
|
||||
QRect newGeom = m_resizeStartGeom;
|
||||
|
||||
if (m_resizeEdges & Qt::LeftEdge) {
|
||||
int newLeft = m_resizeStartGeom.left() + delta.x();
|
||||
int minWidth = minimumWidth();
|
||||
if (m_resizeStartGeom.right() - newLeft >= minWidth) {
|
||||
newGeom.setLeft(newLeft);
|
||||
}
|
||||
}
|
||||
if (m_resizeEdges & Qt::RightEdge) {
|
||||
int newWidth = m_resizeStartGeom.width() + delta.x();
|
||||
if (newWidth >= minimumWidth()) {
|
||||
newGeom.setWidth(newWidth);
|
||||
}
|
||||
}
|
||||
if (m_resizeEdges & Qt::TopEdge) {
|
||||
int newTop = m_resizeStartGeom.top() + delta.y();
|
||||
int minHeight = minimumHeight();
|
||||
if (m_resizeStartGeom.bottom() - newTop >= minHeight) {
|
||||
newGeom.setTop(newTop);
|
||||
}
|
||||
}
|
||||
if (m_resizeEdges & Qt::BottomEdge) {
|
||||
int newHeight = m_resizeStartGeom.height() + delta.y();
|
||||
if (newHeight >= minimumHeight()) {
|
||||
newGeom.setHeight(newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setGeometry(newGeom);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_dragging && (event->buttons() & Qt::LeftButton)) {
|
||||
move(event->globalPosition().toPoint() - m_dragPos);
|
||||
event->accept();
|
||||
}
|
||||
QMainWindow::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void MainMainWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragging = false;
|
||||
m_resizing = false;
|
||||
m_resizeEdges = Qt::Edges();
|
||||
unsetCursor();
|
||||
event->accept();
|
||||
}
|
||||
QMainWindow::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
bool MainMainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Handle Windows non-client area hits for resize
|
||||
MSG *msg = static_cast<MSG *>(message);
|
||||
if (msg->message == WM_NCHITTEST) {
|
||||
// Let Windows handle resize borders, but we handle caption
|
||||
// This allows native resize from edges
|
||||
*result = 0;
|
||||
return false; // Let Windows handle it
|
||||
}
|
||||
#endif
|
||||
return QMainWindow::nativeEvent(eventType, message, result);
|
||||
}
|
||||
|
||||
void MainMainWindow::changeEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::WindowStateChange) {
|
||||
updateMaximizeButtonIcon();
|
||||
m_lastWindowState = windowState();
|
||||
}
|
||||
QMainWindow::changeEvent(event);
|
||||
}
|
||||
|
||||
void MainMainWindow::onProgressChanged(int percent)
|
||||
|
||||
Reference in New Issue
Block a user