feat: rework main window - always-visible left navigator panel, remove sidebar
- Left panel is now always visible and its content changes per section: * Emails -> folder + category tree * Contactos -> compact contact list (aggregated from local mails, by frequency) * Calendario -> mini QCalendarWidget - Sidebar removed; the three navigation buttons (Emails/Contactos/Calendario) sit at the bottom of the left panel instead (no Compose, no Settings, no 'new mail' button) - Mail list + reader splitter now only holds list | viewer (folders moved out) - switchToPage syncs the navigator stack + highlighted button Verified: compiles and runs clean in Linux.
This commit is contained in:
+205
-67
@@ -22,6 +22,11 @@
|
||||
#include <QFrame>
|
||||
#include <QDateTime>
|
||||
#include <QLabel>
|
||||
#include <QHash>
|
||||
#include <QRegularExpression>
|
||||
#include <QListWidget>
|
||||
#include <QCalendarWidget>
|
||||
#include <algorithm>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -57,12 +62,15 @@ void MainMainWindow::setupUI()
|
||||
{
|
||||
qDebug() << "[MainMainWindow::setupUI] Start";
|
||||
// === Global stylesheet ===
|
||||
this->setStyleSheet(
|
||||
/*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; }"
|
||||
);
|
||||
|
||||
|
||||
, k
|
||||
);*/
|
||||
qDebug() << "[MainMainWindow::setupUI] Stylesheet set";
|
||||
|
||||
createTitleBar();
|
||||
@@ -71,23 +79,23 @@ void MainMainWindow::setupUI()
|
||||
// === Central widget ===
|
||||
QWidget *central = new QWidget();
|
||||
central->setObjectName("CentralWidget");
|
||||
central->setStyleSheet(
|
||||
/*central->setStyleSheet(
|
||||
"QWidget#CentralWidget {"
|
||||
" background-color: #f5f5f7;"
|
||||
" border-bottom-left-radius: 8px;"
|
||||
" border-bottom-right-radius: 8px;"
|
||||
"}"
|
||||
);
|
||||
);*/
|
||||
// Install event filter to handle resize from window edges
|
||||
central->installEventFilter(this);
|
||||
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);
|
||||
// === Left navigator panel (always visible) ===
|
||||
setupNavigator();
|
||||
qDebug() << "[MainMainWindow::setupUI] Navigator created";
|
||||
centralLayout->addWidget(m_navigatorPanel);
|
||||
|
||||
// === Separator line ===
|
||||
QFrame *separator = new QFrame();
|
||||
@@ -187,28 +195,120 @@ void MainMainWindow::setupUI()
|
||||
qDebug() << "[MainMainWindow::setupUI] Done";
|
||||
}
|
||||
|
||||
void MainMainWindow::setupSidebar()
|
||||
void MainMainWindow::setupNavigator()
|
||||
{
|
||||
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; }"
|
||||
// Always-visible left panel: its content (navigator) changes per section,
|
||||
// and the three section buttons sit at the bottom of the panel.
|
||||
m_navigatorPanel = new QWidget();
|
||||
m_navigatorPanel->setObjectName("NavigatorPanel");
|
||||
m_navigatorPanel->setFixedWidth(240);
|
||||
m_navigatorPanel->setStyleSheet(
|
||||
"QWidget#NavigatorPanel { background-color: #f7f7f9; }"
|
||||
);
|
||||
QVBoxLayout *panelLay = new QVBoxLayout(m_navigatorPanel);
|
||||
panelLay->setContentsMargins(0, 0, 0, 0);
|
||||
panelLay->setSpacing(0);
|
||||
|
||||
m_sidebar->addItem("📧\nMail");
|
||||
m_sidebar->addItem("✏️\nCompose");
|
||||
m_sidebar->addItem("⚙️\nSettings");
|
||||
m_sidebar->addItem("👥\nContacts");
|
||||
m_sidebar->addItem("📅\nCalendar");
|
||||
// The three navigator pages.
|
||||
m_navStack = new QStackedWidget();
|
||||
m_navStack->setStyleSheet("background: transparent;");
|
||||
|
||||
m_sidebar->setCurrentRow(0);
|
||||
connect(m_sidebar, &QListWidget::currentRowChanged, this, &MainMainWindow::onNavChanged);
|
||||
// Page 0: folder + category navigator (Emails). Built now; the folder/category
|
||||
// trees are wired to models in connectModels().
|
||||
m_folderNav = new QWidget();
|
||||
m_folderNav->setObjectName("FolderNav");
|
||||
QVBoxLayout *folderNavLay = new QVBoxLayout(m_folderNav);
|
||||
folderNavLay->setContentsMargins(4, 8, 4, 8);
|
||||
folderNavLay->setSpacing(8);
|
||||
|
||||
m_folderTree = new QTreeView();
|
||||
m_folderTree->setHeaderHidden(true);
|
||||
m_folderTree->setIndentation(20);
|
||||
m_folderTree->setMinimumWidth(150);
|
||||
m_folderTree->setMaximumWidth(300);
|
||||
m_folderTree->setFrameShape(QFrame::NoFrame);
|
||||
m_folderTree->setExpandsOnDoubleClick(true);
|
||||
m_folderTree->setItemDelegate(new FolderTreeDelegate(this));
|
||||
folderNavLay->addWidget(m_folderTree, 3);
|
||||
|
||||
m_categoryTree = new CategoryTreeWidget();
|
||||
m_categoryTree->setMinimumHeight(120);
|
||||
m_categoryTree->setMaximumHeight(320);
|
||||
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);
|
||||
folderNavLay->addWidget(m_categoryTree, 2);
|
||||
m_navStack->addWidget(m_folderNav);
|
||||
|
||||
// Page 1: compact contact list navigator.
|
||||
m_contactsNav = new QWidget();
|
||||
m_contactsNav->setObjectName("ContactsNav");
|
||||
QVBoxLayout *contactsNavLay = new QVBoxLayout(m_contactsNav);
|
||||
contactsNavLay->setContentsMargins(4, 8, 4, 8);
|
||||
QLabel *contactsTitle = new QLabel("Contactos");
|
||||
contactsTitle->setStyleSheet("font-weight:700; font-size:13px; color:#1d1d1f; padding:4px;");
|
||||
contactsNavLay->addWidget(contactsTitle);
|
||||
m_contactsNavList = new QListWidget();
|
||||
m_contactsNavList->setStyleSheet(
|
||||
"QListWidget { background:#ffffff; border:1px solid #e0e0e5; border-radius:6px; font-size:12px; }"
|
||||
"QListWidget::item { padding:6px 8px; border-bottom:1px solid #f0f0f3; }"
|
||||
"QListWidget::item:selected { background:#e8f1fd; color:#1c1c1e; }"
|
||||
);
|
||||
contactsNavLay->addWidget(m_contactsNavList);
|
||||
m_navStack->addWidget(m_contactsNav);
|
||||
|
||||
// Page 2: mini calendar navigator.
|
||||
m_calendarNav = new QWidget();
|
||||
m_calendarNav->setObjectName("CalendarNav");
|
||||
QVBoxLayout *calendarNavLay = new QVBoxLayout(m_calendarNav);
|
||||
calendarNavLay->setContentsMargins(4, 8, 4, 8);
|
||||
m_calendarNavWidget = new QCalendarWidget();
|
||||
m_calendarNavWidget->setStyleSheet(
|
||||
"QCalendarWidget { background:#ffffff; border:1px solid #e0e0e5; border-radius:6px; }"
|
||||
);
|
||||
calendarNavLay->addWidget(m_calendarNavWidget);
|
||||
m_navStack->addWidget(m_calendarNav);
|
||||
|
||||
panelLay->addWidget(m_navStack, 1);
|
||||
|
||||
// Bottom navigation buttons: only Emails, Contacts, Calendar (no Compose, no Settings).
|
||||
QWidget *navBtns = new QWidget();
|
||||
navBtns->setObjectName("NavButtons");
|
||||
QHBoxLayout *btnLay = new QHBoxLayout(navBtns);
|
||||
btnLay->setContentsMargins(6, 8, 6, 8);
|
||||
btnLay->setSpacing(4);
|
||||
|
||||
auto makeNavBtn = [&](const QString &text, QToolButton **out) {
|
||||
QToolButton *b = new QToolButton();
|
||||
b->setText(text);
|
||||
b->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
||||
b->setCheckable(true);
|
||||
b->setAutoExclusive(true);
|
||||
b->setStyleSheet(
|
||||
"QToolButton { border:none; border-radius:6px; padding:8px 4px; font-size:11px; color:#3a3a3c; }"
|
||||
"QToolButton:hover { background:#e0e4ea; }"
|
||||
"QToolButton:checked { background:#1976D2; color:#ffffff; font-weight:600; }"
|
||||
);
|
||||
if (out) *out = b;
|
||||
return b;
|
||||
};
|
||||
m_navMailBtn = makeNavBtn("📧 Emails", nullptr);
|
||||
m_navContactsBtn = makeNavBtn("👥 Contactos", nullptr);
|
||||
m_navCalendarBtn = makeNavBtn("📅 Calendario", nullptr);
|
||||
btnLay->addWidget(m_navMailBtn, 1);
|
||||
btnLay->addWidget(m_navContactsBtn, 1);
|
||||
btnLay->addWidget(m_navCalendarBtn, 1);
|
||||
panelLay->addWidget(navBtns);
|
||||
|
||||
connect(m_navMailBtn, &QToolButton::clicked, this, [this]() { switchToPage(PageMail); });
|
||||
connect(m_navContactsBtn, &QToolButton::clicked, this, [this]() { switchToPage(PageContacts); });
|
||||
connect(m_navCalendarBtn, &QToolButton::clicked, this, [this]() { switchToPage(PageCalendar); });
|
||||
|
||||
m_navMailBtn->setChecked(true);
|
||||
m_navStack->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void MainMainWindow::setupMailPage()
|
||||
@@ -217,45 +317,11 @@ void MainMainWindow::setupMailPage()
|
||||
mailLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mailLayout->setSpacing(0);
|
||||
|
||||
// Main splitter with 3 panes: folders | categories | mail list | viewer
|
||||
// Main splitter: mail list | viewer (folders/categories now live in the
|
||||
// always-visible left navigator panel built by setupNavigator()).
|
||||
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);
|
||||
m_folderTree->setIndentation(20);
|
||||
m_folderTree->setMinimumWidth(160); // Reduced from 220
|
||||
m_folderTree->setMaximumWidth(350);
|
||||
m_folderTree->setFrameShape(QFrame::NoFrame);
|
||||
m_folderTree->setExpandsOnDoubleClick(true);
|
||||
m_folderTree->setItemDelegate(new FolderTreeDelegate(this)); // Custom delegate with unread count badges
|
||||
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();
|
||||
connect(m_mailListView, &MailListView::emailSelected, this, &MainMainWindow::onEmailSelected);
|
||||
@@ -331,7 +397,7 @@ void MainMainWindow::setupMailPage()
|
||||
m_folderSplitter->addWidget(m_viewerStack);
|
||||
|
||||
// Default sizes: left pane (folders+categories) 300, list 380, viewer flex
|
||||
m_folderSplitter->setSizes({300, 380, 600});
|
||||
m_folderSplitter->setSizes({420, 600});
|
||||
|
||||
// Allow each pane to shrink further
|
||||
m_folderSplitter->setHandleWidth(1);
|
||||
@@ -397,11 +463,83 @@ void MainMainWindow::onNavChanged(int 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
|
||||
// Sync the left navigator panel: highlight the matching button and show the
|
||||
// corresponding navigator content (folders/contacts/calendar).
|
||||
if (m_navMailBtn && m_navContactsBtn && m_navCalendarBtn && m_navStack) {
|
||||
m_navMailBtn->blockSignals(true);
|
||||
m_navContactsBtn->blockSignals(true);
|
||||
m_navCalendarBtn->blockSignals(true);
|
||||
switch (static_cast<Page>(pageIndex)) {
|
||||
case PageMail:
|
||||
m_navMailBtn->setChecked(true);
|
||||
m_navStack->setCurrentIndex(0); // folder/category navigator
|
||||
break;
|
||||
case PageContacts:
|
||||
m_navContactsBtn->setChecked(true);
|
||||
m_navStack->setCurrentIndex(1); // contacts navigator
|
||||
refreshContactsNavigator();
|
||||
break;
|
||||
case PageCalendar:
|
||||
m_navCalendarBtn->setChecked(true);
|
||||
m_navStack->setCurrentIndex(2); // mini calendar navigator
|
||||
break;
|
||||
default:
|
||||
// Mail/Compose/Settings: keep the folder navigator highlighted.
|
||||
m_navMailBtn->setChecked(true);
|
||||
m_navStack->setCurrentIndex(0);
|
||||
break;
|
||||
}
|
||||
m_navMailBtn->blockSignals(false);
|
||||
m_navContactsBtn->blockSignals(false);
|
||||
m_navCalendarBtn->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::refreshContactsNavigator()
|
||||
{
|
||||
if (!m_contactsNavList) return;
|
||||
m_contactsNavList->clear();
|
||||
|
||||
// Aggregate contacts (sender + recipients) from local mails, most frequent first.
|
||||
QHash<QString, int> contactCount; // email (lower) -> count
|
||||
QHash<QString, QString> contactName; // email (lower) -> display label
|
||||
QRegularExpression emailRx("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}");
|
||||
|
||||
auto ingest = [&](const QString &raw) {
|
||||
QRegularExpressionMatch m = emailRx.match(raw);
|
||||
if (!m.hasMatch()) return;
|
||||
QString email = m.captured(0).toLower();
|
||||
contactCount[email]++;
|
||||
// Prefer "Name <email>" display; fallback to email.
|
||||
if (!contactName.contains(email)) {
|
||||
int lt = raw.indexOf('<');
|
||||
QString cand = lt > 0 ? raw.left(lt).trimmed() : email;
|
||||
contactName[email] = cand.isEmpty() ? email : cand;
|
||||
}
|
||||
};
|
||||
|
||||
for (const MailItem &mail : MailItemDao::findAll()) {
|
||||
ingest(mail.sender());
|
||||
ingest(mail.recipient());
|
||||
ingest(mail.to());
|
||||
ingest(mail.cc());
|
||||
}
|
||||
|
||||
// Sort by frequency descending.
|
||||
QStringList keys = contactCount.keys();
|
||||
std::sort(keys.begin(), keys.end(), [&](const QString &a, const QString &b) {
|
||||
return contactCount.value(a) > contactCount.value(b);
|
||||
});
|
||||
|
||||
for (const QString &email : keys) {
|
||||
QString label = contactName.value(email, email);
|
||||
QListWidgetItem *item = new QListWidgetItem(label + " (" + QString::number(contactCount.value(email)) + ")",
|
||||
m_contactsNavList);
|
||||
item->setToolTip(email);
|
||||
item->setData(Qt::UserRole, email);
|
||||
m_contactsNavList->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::onFolderSelected(const QModelIndex &index)
|
||||
|
||||
+13
-2
@@ -11,6 +11,8 @@
|
||||
#include <QAction>
|
||||
#include <QToolButton>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QCalendarWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QWindowStateChangeEvent>
|
||||
@@ -101,7 +103,7 @@ private slots:
|
||||
void onEmbeddedDetachRequested(QWidget *widget);
|
||||
private:
|
||||
void setupUI();
|
||||
void setupSidebar();
|
||||
void setupNavigator(); // builds left navigator panel (folders/contacts/calendar + nav buttons)
|
||||
void setupMailPage();
|
||||
void connectModels();
|
||||
void createTitleBar();
|
||||
@@ -110,9 +112,18 @@ private:
|
||||
QWidget *createWindowControlButton(const QString &iconName, const QString &tooltip, std::function<void()> slot);
|
||||
void updateCategoryTreeForAccount(qint64 accountId);
|
||||
void assignCategoryToSelectedMails(qint64 categoryId);
|
||||
void refreshContactsNavigator(); // populate the left-panel contact list
|
||||
|
||||
// Navigation
|
||||
QListWidget *m_sidebar;
|
||||
QWidget *m_navigatorPanel; // always-visible left panel
|
||||
QStackedWidget *m_navStack; // navigator content: 0=folder/category, 1=contact list, 2=mini calendar
|
||||
QToolButton *m_navMailBtn, *m_navContactsBtn, *m_navCalendarBtn;
|
||||
// Navigators (content of the left panel, swapped per section)
|
||||
QWidget *m_folderNav; // folders + categories (shown for Emails)
|
||||
QWidget *m_contactsNav; // compact contact list
|
||||
QWidget *m_calendarNav; // mini calendar widget
|
||||
QListWidget *m_contactsNavList;
|
||||
QCalendarWidget *m_calendarNavWidget;
|
||||
QStackedWidget *m_stack;
|
||||
|
||||
enum Page {
|
||||
|
||||
Reference in New Issue
Block a user