Files
wino-mail-dtkqt/src/ui/mainmainwindow.cpp
T

555 lines
21 KiB
C++
Raw Normal View History

#include "mainmainwindow.h"
#include "core/models/account.h"
#include "core/mailitem.h"
#include "db/dao/mailitemdao.h"
#include "db/dao/folderdao.h"
#include <optional>
#include <QMessageBox>
#include "ui/accountsetupdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFrame>
#include <QDateTime>
#include <QLabel>
MainMainWindow::MainMainWindow(QWidget *parent)
: QMainWindow(parent), m_currentFolderId(-1), m_currentMailId(-1)
{
qDebug() << "[MainMainWindow] Constructor start";
setupUI();
qDebug() << "[MainMainWindow] setupUI done";
connectModels();
qDebug() << "[MainMainWindow] connectModels done";
setWindowTitle("Wino Mail DTK");
resize(1280, 820);
// Setup progress bar in status bar
m_progressBar = new QProgressBar(this);
m_progressBar->setMaximumWidth(120);
m_progressBar->setVisible(false);
statusBar()->addPermanentWidget(m_progressBar);
// Connect mail service progress/status signals
connect(m_mailService, &MailService::progressChanged, this, &MainMainWindow::onProgressChanged);
connect(m_mailService, &MailService::statusMessage, this, &MainMainWindow::onStatusMessage);
connect(m_mailService, &MailService::mailSent, this, [this](const QString &) {
statusBar()->showMessage(tr("Message sent"), 5000);
});
connect(m_mailService, &MailService::mailSendFailed, this, [this](const QString &, const QString &error) {
statusBar()->showMessage(tr("Send failed: %1").arg(error), 8000);
});
// Provide account service to settings view
m_settingsView->setAccountService(m_accountService);
qDebug() << "[MainMainWindow] Constructor complete";
}
void MainMainWindow::setupUI()
{
qDebug() << "[MainMainWindow::setupUI] Start";
// === Global stylesheet ===
this->setStyleSheet(
"QMainWindow { background-color: #f5f5f7; }"
"QSplitter::handle { background-color: #d1d1d6; width: 1px; }"
"QTreeView { background-color: #ffffff; border: none; font-family: 'Segoe UI', Helvetica; font-size: 13px; }"
"QToolBar { background-color: #f5f5f7; border-bottom: 1px solid #d1d1d6; spacing: 10px; }"
);
qDebug() << "[MainMainWindow::setupUI] Stylesheet set";
createToolBar();
qDebug() << "[MainMainWindow::setupUI] Toolbar created";
// === Central widget ===
QWidget *central = new QWidget();
QHBoxLayout *centralLayout = new QHBoxLayout(central);
centralLayout->setContentsMargins(0, 0, 0, 0);
centralLayout->setSpacing(0);
// === Sidebar ===
setupSidebar();
qDebug() << "[MainMainWindow::setupUI] Sidebar created";
centralLayout->addWidget(m_sidebar);
// === Separator line ===
QFrame *separator = new QFrame();
separator->setFrameShape(QFrame::VLine);
separator->setStyleSheet("color: #d1d1d6;");
centralLayout->addWidget(separator);
// === Stacked pages ===
m_stack = new QStackedWidget();
m_stack->setStyleSheet("background-color: #f5f5f7;");
qDebug() << "[MainMainWindow::setupUI] Stack created";
// Page 0: Mail (folder tree + mail list + reader)
setupMailPage();
qDebug() << "[MainMainWindow::setupUI] Mail page created";
m_stack->addWidget(m_mailPage);
// Page 1: Compose
m_composeView = new ComposeView();
connect(m_composeView, &ComposeView::sendRequested, [this](const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QDateTime &scheduleTime, const QString &fromAddr, const QStringList &attachmentPaths) {
if (scheduleTime.isValid()) {
statusBar()->showMessage(tr("Scheduled sending is not available yet"), 5000);
return;
}
if (fromAddr.isEmpty()) {
statusBar()->showMessage(tr("Select an account before sending"), 5000);
return;
}
MailItem mail;
mail.setTo(to);
mail.setRecipient(to);
mail.setCc(cc);
mail.setBcc(bcc);
mail.setSubject(subject);
mail.setBodyHtml(body);
mail.setDate(QDateTime::currentDateTimeUtc());
m_mailService->sendMail(mail, fromAddr, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
switchToPage(PageMail);
});
connect(m_composeView, &ComposeView::discardRequested, [this]() {
switchToPage(PageMail);
});
connect(m_composeView, &ComposeView::detachRequested, [this](QWidget *composeView) {
// Detach compose view to a standalone window
QStackedWidget *stack = qobject_cast<QStackedWidget*>(composeView->parentWidget());
if (stack) {
stack->removeWidget(composeView);
}
// Create standalone window
QMainWindow *detachedWin = new QMainWindow();
detachedWin->setWindowTitle("Compose - Wino Mail");
// Assign central widget and ensure it's visible and sized
detachedWin->setCentralWidget(composeView);
composeView->setMinimumSize(800, 600);
composeView->update();
detachedWin->resize(800, 600);
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
detachedWin->show();
statusBar()->showMessage("Compose view detached to separate window", 3000);
});
m_stack->addWidget(m_composeView);
// Page 2: Settings
m_settingsView = new SettingsView();
connect(m_settingsView, &SettingsView::accountAddRequested, [this]() {
AccountSetupDialog dlg(m_accountService, this);
dlg.exec();
});
connect(m_settingsView, &SettingsView::accountEditRequested, this, &MainMainWindow::onAccountEditRequested);
connect(m_settingsView, &SettingsView::accountDeleteRequested, this, &MainMainWindow::onAccountDeleteRequested);
connect(m_settingsView, &SettingsView::themeChanged, [this](const QString &theme) {
statusBar()->showMessage(QString("Theme changed to: %1 (restart may be required)").arg(theme), 3000);
});
m_stack->addWidget(m_settingsView);
qDebug() << "[MainMainWindow::setupUI] Settings page created";
// Page 3: Contacts
m_contactsView = new ContactsView();
m_stack->addWidget(m_contactsView);
// Page 4: Calendar
m_calendarView = new CalendarView();
m_stack->addWidget(m_calendarView);
centralLayout->addWidget(m_stack, 1);
setCentralWidget(central);
qDebug() << "[MainMainWindow::setupUI] Central widget set";
// Show mail page by default
switchToPage(PageMail);
qDebug() << "[MainMainWindow::setupUI] Done";
}
void MainMainWindow::setupSidebar()
{
m_sidebar = new QListWidget();
m_sidebar->setFixedWidth(100);
m_sidebar->setIconSize(QSize(24, 24));
m_sidebar->setSpacing(4);
m_sidebar->setFrameShape(QFrame::NoFrame);
m_sidebar->setStyleSheet(
"QListWidget { background-color: #2c2c2e; border: none; padding: 8px 0; }"
"QListWidget::item { color: #8e8e93; padding: 12px 0; text-align: center; font-size: 10px; border: none; border-radius: 8px; margin: 2px 8px; }"
"QListWidget::item:selected { background-color: #3a3a3c; color: #ffffff; }"
"QListWidget::item:hover { background-color: #3a3a3c; color: #ffffff; }"
);
m_sidebar->addItem("📧\nMail");
m_sidebar->addItem("✏️\nCompose");
m_sidebar->addItem("⚙️\nSettings");
m_sidebar->addItem("👥\nContacts");
m_sidebar->addItem("📅\nCalendar");
m_sidebar->setCurrentRow(0);
connect(m_sidebar, &QListWidget::currentRowChanged, this, &MainMainWindow::onNavChanged);
}
void MainMainWindow::setupMailPage()
{ m_mailPage = new QWidget();
QHBoxLayout *mailLayout = new QHBoxLayout(m_mailPage);
mailLayout->setContentsMargins(0, 0, 0, 0);
mailLayout->setSpacing(0);
m_folderSplitter = new QSplitter(Qt::Horizontal);
m_folderSplitter->setHandleWidth(1);
// Folder tree
m_folderTree = new QTreeView();
m_folderTree->setHeaderHidden(true);
m_folderTree->setIndentation(20);
m_folderTree->setMinimumWidth(220);
m_folderTree->setMaximumWidth(350);
m_folderTree->setFrameShape(QFrame::NoFrame);
m_folderTree->setExpandsOnDoubleClick(true);
m_folderSplitter->addWidget(m_folderTree);
// Mail list (QTableView with sorting)
m_mailListView = new MailListView();
connect(m_mailListView, &MailListView::emailSelected, this, &MainMainWindow::onEmailSelected);
connect(m_mailListView, &MailListView::composeRequested, this, &MainMainWindow::onComposeRequested);
2026-06-18 18:58:27 +02:00
connect(m_mailListView, &MailListView::emailOpenRequested, this, [this](int mailId) {
openMailInIndependentWindow(mailId);
});
m_folderSplitter->addWidget(m_mailListView);
// Viewer stack: placeholder, reader, compose
m_viewerStack = new QStackedWidget();
// Placeholder widget
m_placeholderWidget = new QWidget();
QVBoxLayout *placeholderLayout = new QVBoxLayout(m_placeholderWidget);
placeholderLayout->setAlignment(Qt::AlignCenter);
QLabel *iconLabel = new QLabel();
QPixmap pixmap = QIcon::fromTheme("mail-unread").pixmap(48, 48);
if (pixmap.isNull()) {
pixmap = QPixmap(48, 48);
pixmap.fill(Qt::gray);
}
iconLabel->setPixmap(pixmap);
iconLabel->setAlignment(Qt::AlignCenter);
QLabel *textLabel = new QLabel(tr("Seleccione un correo para leerlo"));
textLabel->setAlignment(Qt::AlignCenter);
textLabel->setStyleSheet("color: #666; font-size: 14px;");
placeholderLayout->addStretch();
placeholderLayout->addWidget(iconLabel);
placeholderLayout->addWidget(textLabel);
placeholderLayout->addStretch();
m_placeholderWidget->setLayout(placeholderLayout);
// Reader view
m_emailViewer = new ReaderView();
m_emailViewer->setMinimumWidth(350);
connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested);
2026-06-18 18:58:27 +02:00
connect(m_emailViewer, &ReaderView::detachRequested, this, [this]() {
if (m_currentMailId >= 0) {
openMailInIndependentWindow(m_currentMailId);
2026-06-18 18:58:27 +02:00
}
});
// Embedded compose view
m_embeddedComposeView = new ComposeView();
connect(m_embeddedComposeView, &ComposeView::sendRequested, this, &MainMainWindow::onEmbeddedSendRequested);
connect(m_embeddedComposeView, &ComposeView::discardRequested, this, &MainMainWindow::onEmbeddedDiscardRequested);
connect(m_embeddedComposeView, &ComposeView::detachRequested, this, &MainMainWindow::onEmbeddedDetachRequested);
// Add to stack
m_viewerStack->addWidget(m_placeholderWidget);
m_viewerStack->addWidget(m_emailViewer);
m_viewerStack->addWidget(m_embeddedComposeView);
m_viewerStack->setCurrentIndex(0); // show placeholder by default
m_folderSplitter->addWidget(m_viewerStack);
// Default sizes: folder 240, list 380, viewer flex
m_folderSplitter->setSizes({240, 380, 600});
mailLayout->addWidget(m_folderSplitter);
}
void MainMainWindow::connectModels()
{
qDebug() << "[MainMainWindow::connectModels] Start";
m_accountService = new AccountService(this);
qDebug() << "[MainMainWindow::connectModels] AccountService created";
m_mailService = new MailService(m_accountService, this);
qDebug() << "[MainMainWindow::connectModels] MailService created";
m_composeView->setAccountService(m_accountService);
m_embeddedComposeView->setAccountService(m_accountService);
m_folderModel = new FolderListModel(m_accountService, this);
qDebug() << "[MainMainWindow::connectModels] FolderListModel created";
m_emailModel = new EmailListModel(this);
qDebug() << "[MainMainWindow::connectModels] EmailListModel created";
m_folderTree->setModel(m_folderModel);
m_mailListView->setModel(m_emailModel);
m_folderTree->expandAll();
connect(m_folderTree, &QTreeView::clicked, this, &MainMainWindow::onFolderSelected);
connect(m_mailService, &MailService::mailFetched, this,
[this](const QString &, const QString &folderId, const QVector<MailItem> &) {
if (folderId.toInt() == m_currentFolderId) {
m_emailModel->refresh();
statusBar()->showMessage(tr("Mail synchronized"), 3000);
}
});
connect(m_mailService, &MailService::mailFetchError, this,
[this](const QString &, const QString &, const QString &error) {
statusBar()->showMessage(tr("Synchronization failed: %1").arg(error), 8000);
});
qDebug() << "[MainMainWindow::connectModels] Done";
}
void MainMainWindow::onNavChanged(int index)
{
switchToPage(static_cast<Page>(index));
}
void MainMainWindow::switchToPage(int pageIndex)
{
m_stack->setCurrentIndex(pageIndex);
m_sidebar->blockSignals(true);
m_sidebar->setCurrentRow(pageIndex);
m_sidebar->blockSignals(false);
// Show/hide toolbar actions per page
}
void MainMainWindow::onFolderSelected(const QModelIndex &index)
{ if (!index.isValid()) return;
int itemType = index.data(FolderListModel::ItemTypeRole).toInt();
if (itemType == FolderTreeItem::FolderNode) {
m_currentFolderId = index.data(FolderListModel::FolderIdRole).toInt();
m_emailModel->setFolderId(m_currentFolderId);
// Fetch mails for this folder
std::optional<Folder> optFolder = FolderDao::findById(m_currentFolderId);
if (optFolder.has_value()) {
Folder folder = optFolder.value();
Account* account = m_accountService->findAccountById(folder.accountId());
if (account) {
m_mailService->fetchMails(QString::number(account->id()), QString::number(m_currentFolderId));
delete account;
}
}
m_emailModel->refresh();
// Clear selection and show placeholder
m_currentMailId = -1;
m_mailListView->tableView()->clearSelection();
m_viewerStack->setCurrentIndex(0); // placeholder
}
}
void MainMainWindow::onEmailSelected(int mailId)
{ m_currentMailId = mailId;
if (mailId >= 0) {
std::optional<MailItem> item = MailItemDao::findById(mailId);
if (!item.has_value()) {
m_emailViewer->setMailItem(nullptr);
m_viewerStack->setCurrentIndex(0); // placeholder
return;
}
MailItem &mail = item.value();
if (!mail.isRead()) {
mail.setRead(true);
MailItemDao::update(mail);
}
m_emailViewer->setMailItem(&mail);
m_viewerStack->setCurrentIndex(1); // reader
} else {
m_emailViewer->setMailItem(nullptr);
m_viewerStack->setCurrentIndex(0); // placeholder
}
}
void MainMainWindow::onComposeRequested()
{ m_embeddedComposeView->initializeComposition();
m_viewerStack->setCurrentIndex(2); // compose
}
void MainMainWindow::onReaderReplyRequested(const MailItem *item)
{ if (item) {
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
}
m_viewerStack->setCurrentIndex(2); // compose
}
void MainMainWindow::onNewMessage()
{ // Show compose view in the viewer stack
m_embeddedComposeView->initializeComposition();
m_viewerStack->setCurrentIndex(2); // compose
}
void MainMainWindow::openMailInIndependentWindow(int mailId)
{
2026-06-18 18:58:27 +02:00
std::optional<MailItem> item = MailItemDao::findById(mailId);
if (!item.has_value()) return;
QMainWindow *detachedWin = new QMainWindow();
detachedWin->setWindowTitle(QString("Mail - %1").arg(item->subject()));
2026-06-18 18:58:27 +02:00
ReaderView *detachedReader = new ReaderView();
detachedReader->setMailItem(&item.value());
2026-06-18 18:58:27 +02:00
detachedWin->setCentralWidget(detachedReader);
detachedWin->resize(800, 600);
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
detachedWin->show();
2026-06-18 18:58:27 +02:00
statusBar()->showMessage("Opened mail in independent window", 3000);
}
void MainMainWindow::createToolBar()
{
m_toolBar = addToolBar("Main Toolbar");
m_toolBar->setMovable(false);
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");
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);
}
}
});
connect(openWinAction, &QAction::triggered, [this]() {
if (m_currentMailId >= 0) {
openMailInIndependentWindow(m_currentMailId);
} else {
statusBar()->showMessage("Selecciona un correo primero para abrirlo en una ventana", 3000);
}
});
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);
}
});
}
void MainMainWindow::onProgressChanged(int percent)
{
if (percent < 0 || percent > 100) {
m_progressBar->setVisible(false);
return;
}
m_progressBar->setValue(percent);
m_progressBar->setVisible(true);
}
void MainMainWindow::onStatusMessage(const QString &msg)
{
// Show temporary message in status bar (timeout 5000 ms)
statusBar()->showMessage(msg, 5000);
}
void MainMainWindow::onAddAccountRequested()
{
AccountSetupDialog *dialog = new AccountSetupDialog(m_accountService, this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowFlag(Qt::Window, true); // make it an independent window
dialog->show();
}
void MainMainWindow::onAccountEditRequested(int accountId)
{
QMessageBox::information(this, "Debug", QString("Edit account slot called for ID %1").arg(accountId));
Account* account = m_accountService->findAccountById(accountId);
if (!account) {
statusBar()->showMessage(QString("Error: account %1 not found").arg(accountId), 3000);
return;
}
AccountSetupDialog dlg(m_accountService, this);
dlg.loadAccountForEditing(*account);
dlg.exec();
delete account;
}
void MainMainWindow::onAccountDeleteRequested(int accountId)
{
m_accountService->removeAccount(accountId);
m_folderModel->refresh();
m_emailModel->setFolderId(-1);
m_emailModel->refresh();
m_currentFolderId = -1;
m_currentMailId = -1;
m_viewerStack->setCurrentIndex(0);
statusBar()->showMessage(tr("Account removed"), 3000);
}
void MainMainWindow::onEmbeddedSendRequested(const QString &to, const QString &cc, const QString &bcc,
const QString &subject, const QString &body,
const QDateTime &scheduleTime,
const QString &fromAddress,
const QStringList &attachmentPaths)
{
if (scheduleTime.isValid()) {
statusBar()->showMessage(tr("Scheduled sending is not available yet"), 5000);
return;
}
if (fromAddress.isEmpty()) {
statusBar()->showMessage(tr("Select an account before sending"), 5000);
return;
}
MailItem mail;
mail.setTo(to);
mail.setRecipient(to);
mail.setCc(cc);
mail.setBcc(bcc);
mail.setSubject(subject);
mail.setBodyHtml(body);
mail.setDate(QDateTime::currentDateTimeUtc());
m_mailService->sendMail(mail, fromAddress, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
m_viewerStack->setCurrentIndex(0); // placeholder
m_embeddedComposeView->initializeComposition();
}
void MainMainWindow::onEmbeddedDiscardRequested()
{
// Go back to placeholder
m_viewerStack->setCurrentIndex(0); // placeholder
m_embeddedComposeView->initializeComposition();
}
void MainMainWindow::onEmbeddedDetachRequested(QWidget *widget)
{
// Detach the compose view to a standalone window (similar to main compose view's detach)
QStackedWidget *stack = qobject_cast<QStackedWidget*>(widget->parentWidget());
if (stack) {
stack->removeWidget(widget);
}
// Create standalone window
QMainWindow *detachedWin = new QMainWindow();
detachedWin->setWindowTitle(tr("Compose - Wino Mail"));
// Assign central widget and ensure it's visible and sized
detachedWin->setCentralWidget(widget);
widget->setMinimumSize(800, 600);
widget->update();
detachedWin->resize(800, 600);
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
detachedWin->show();
statusBar()->showMessage(tr("Compose view detached to separate window"), 3000);
}