// ===================== ComposeView ===================== #include "composeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ComposeView::ComposeView(QWidget *parent) : QWidget(parent) { setupUI(); // Connect the rich text editor's signature signal to our slot connect(m_bodyEditor, &RichTextEditor::signatureClicked, this, &ComposeView::onSignatureClicked); connect(m_bodyEditor, &RichTextEditor::signatureEditRequested, this, &ComposeView::onSignatureEditRequested); } ComposeView::~ComposeView() { // Destructor implementation } void ComposeView::onSignatureClicked() { // This slot is called when the signature button in the rich text editor is clicked // The actual menu handling is done in RichTextEditor, so we just need to // handle any ComposeView-specific logic here if needed } void ComposeView::onSignatureEditRequested() { QDialog dialog(this); dialog.setWindowTitle(tr("Edit Signature")); dialog.setMinimumSize(600, 400); QVBoxLayout *layout = new QVBoxLayout(&dialog); RichTextEditor *editor = new RichTextEditor(&dialog); editor->setupToolbar(layout); layout->addWidget(editor); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); if (dialog.exec() == QDialog::Accepted) { QString signatureHtml = editor->toHtml(); if (!signatureHtml.isEmpty()) { QTextCursor cursor = m_bodyEditor->textCursor(); cursor.insertHtml(signatureHtml); } } } void ComposeView::setAccountService(AccountService *service) { m_accountService = service; populateAccountCombo(); } void ComposeView::populateAccountCombo() { if (!m_accountService) { qWarning() << "AccountService not set"; return; } m_accountCombo->clear(); m_accountCombo->addItem(tr("Select Account..."), QVariant()); QVector accounts = m_accountService->getAllAccounts(); for (const Account &account : accounts) { m_accountCombo->addItem(account.email(), account.id()); } // Select the first account by default if there are accounts if (accounts.size() > 0) { m_accountCombo->setCurrentIndex(1); // Skip the placeholder item m_currentAccountId = accounts.first().id(); } } void ComposeView::onAccountChanged(int index) { if (index <= 0) { // Placeholder item selected or invalid index m_currentAccountId = -1; return; } m_currentAccountId = m_accountCombo->itemData(index).toInt(); loadSignatureForCurrentAccount(); } void ComposeView::loadSignatureForCurrentAccount() { if (!m_accountService || m_currentAccountId <= 0) { // Clear signature if no account selected m_bodyEditor->clear(); return; } Account* account = m_accountService->findAccountById(m_currentAccountId); if (account) { // For now, we'll just clear the editor since we don't have a signature field in Account yet // In a real implementation, we would retrieve and display the signature m_bodyEditor->clear(); delete account; } else { m_bodyEditor->clear(); } } void ComposeView::setupUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(20, 15, 20, 15); mainLayout->setSpacing(8); this->setStyleSheet( "QLineEdit, QTextEdit { border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px; font-family: 'Segoe UI', Helvetica; font-size: 13px; }" "QLineEdit:focus, QTextEdit:focus { border-color: #1976D2; }" ); // De: field + Account selector (renamed from From:) QHBoxLayout *fromLayout = new QHBoxLayout(); QLabel *fromLabel = new QLabel(tr("De:")); fromLabel->setFixedWidth(40); fromLabel->setStyleSheet("font-weight: bold; color: #555;"); m_accountCombo = new QComboBox(); m_accountCombo->setFixedWidth(180); m_accountCombo->setStyleSheet( "QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 8px; " "background: white; min-height: 20px; }" "QComboBox:hover { border-color: #bdbdbd; }" "QComboBox:focus { border-color: #1976D2; }" "QComboBox::drop-down { border: none; width: 20px; }" "QComboBox::down-arrow { image: url(:/icons/dropdown.png); width: 10px; height: 10px; }" ); connect(m_accountCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ComposeView::onAccountChanged); fromLayout->addWidget(fromLabel); fromLayout->addWidget(m_accountCombo); fromLayout->addStretch(); // Este resorte empuja el par (label+combo) a la izquierda mainLayout->addLayout(fromLayout); // Para: field + Cc/Bcc toggle buttons QHBoxLayout *toLayout = new QHBoxLayout(); QLabel *toLabel = new QLabel(tr("Para:")); toLabel->setFixedWidth(40); toLabel->setStyleSheet("font-weight: bold; color: #555;"); m_toField = new everload_tags::TagsLineEdit(); toLayout->addWidget(toLabel); toLayout->addWidget(m_toField, 1); m_ccButton = new QPushButton("Cc"); m_ccButton->setFixedWidth(32); m_ccButton->setStyleSheet( "QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }" "QPushButton:hover { background: #d0d0d0; }" ); connect(m_ccButton, &QPushButton::clicked, this, &ComposeView::onCcToggle); toLayout->addWidget(m_ccButton); m_bccButton = new QPushButton("Bcc"); m_bccButton->setFixedWidth(36); m_bccButton->setStyleSheet( "QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }" "QPushButton:hover { background: #d0d0d0; }" ); connect(m_bccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle); toLayout->addWidget(m_bccButton); mainLayout->addLayout(toLayout); // Cc: row (hidden by default) m_ccRow = new QWidget(); QHBoxLayout *ccLayout = new QHBoxLayout(m_ccRow); ccLayout->setContentsMargins(0, 0, 0, 0); QLabel *ccLabel = new QLabel(tr("Cc:")); ccLabel->setFixedWidth(40); ccLabel->setStyleSheet("color: #555;"); m_ccField = new everload_tags::TagsLineEdit(); ccLayout->addWidget(ccLabel); ccLayout->addWidget(m_ccField, 1); m_hideCcButton = new QPushButton(""); m_hideCcButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg"))); m_hideCcButton->setFixedSize(20, 20); m_hideCcButton->setToolTip(tr("Hide Cc")); m_hideCcButton->setStyleSheet( "QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }" "QPushButton:hover { color: #333; }" ); connect(m_hideCcButton, &QPushButton::clicked, this, &ComposeView::onCcToggle); ccLayout->addWidget(m_hideCcButton); m_ccRow->setVisible(false); mainLayout->addWidget(m_ccRow); // Bcc: row (hidden by default) m_bccRow = new QWidget(); QHBoxLayout *bccLayout = new QHBoxLayout(m_bccRow); bccLayout->setContentsMargins(0, 0, 0, 0); QLabel *bccLabel = new QLabel(tr("Bcc:")); bccLabel->setFixedWidth(40); bccLabel->setStyleSheet("color: #555;"); m_bccField = new everload_tags::TagsLineEdit(); bccLayout->addWidget(bccLabel); bccLayout->addWidget(m_bccField, 1); m_hideBccButton = new QPushButton(""); m_hideBccButton->setFixedSize(20, 20); m_hideBccButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg"))); m_hideBccButton->setToolTip(tr("Hide Bcc")); m_hideBccButton->setStyleSheet( "QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }" "QPushButton:hover { color: #333; }" ); connect(m_hideBccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle); bccLayout->addWidget(m_hideBccButton); m_bccRow->setVisible(false); mainLayout->addWidget(m_bccRow); // Asunto: field QHBoxLayout *subjectLayout = new QHBoxLayout(); QLabel *subjectLabel = new QLabel(tr("Asunto:")); subjectLabel->setFixedWidth(60); subjectLabel->setStyleSheet("font-weight: bold; color: #555;"); m_subjectField = new QLineEdit(); m_subjectField->setPlaceholderText(tr("Subject")); m_subjectField->setFixedHeight(40); QFont subjectFont = m_subjectField->font(); subjectFont.setPointSize(14); subjectFont.setBold(true); m_subjectField->setFont(subjectFont); //subjectLayout->addWidget(subjectLabel); subjectLayout->addWidget(m_subjectField, 1); // Detach button placed to the right of the subject line m_detachButton = new QPushButton("");//("↥ Detach"); m_detachButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg"))); m_detachButton->setToolTip(tr("Open compose window in a separate window")); m_detachButton->setStyleSheet( "QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 12px; color: #555; font-size: 12px; }" "QPushButton:hover { background: #f0f0f0; }" ); connect(m_detachButton, &QPushButton::clicked, this, &ComposeView::onDetachClicked); subjectLayout->addWidget(m_detachButton); mainLayout->addLayout(subjectLayout); // Separator line admin@email.com QFrame *line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); line1->setStyleSheet("color: #e0e0e0;"); mainLayout->addWidget(line1); // Rich text editor with toolbar m_bodyEditor = new RichTextEditor(); m_bodyEditor->setupToolbar(mainLayout); mainLayout->addWidget(m_bodyEditor, 1); // Schedule panel m_schedulePanel = new QWidget(); QHBoxLayout *scheduleLayout = new QHBoxLayout(m_schedulePanel); scheduleLayout->setContentsMargins(0, 0, 0, 0); QLabel *scheduleLabel = new QLabel(tr("Send at:")); scheduleLabel->setStyleSheet("color: #555; font-weight: bold;"); m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600)); m_schedulePicker->setCalendarPopup(true); m_schedulePicker->setDisplayFormat("dd/MM/yyyy hh:mm AP"); scheduleLayout->addWidget(scheduleLabel); scheduleLayout->addWidget(m_schedulePicker); scheduleLayout->addStretch(); m_scheduleSendButton = new QPushButton(tr("Schedule Send")); m_scheduleSendButton->setFixedWidth(150); m_scheduleSendButton->setStyleSheet( "QPushButton { background-color: #1976D2; color: white; border: none; border-radius: 4px; padding: 6px 16px; font-weight: bold; }" "QPushButton:hover { background-color: #1565C0; }" ); connect(m_scheduleSendButton, &QPushButton::clicked, this, &ComposeView::onScheduleClicked); scheduleLayout->addWidget(m_scheduleSendButton, 2); m_schedulePanel->setVisible(false); m_schedulePanel->setVisible(false); mainLayout->addWidget(m_schedulePanel); // Attachment section QHBoxLayout *attachmentLayout = new QHBoxLayout(); m_attachmentList = new QListWidget(); m_attachmentList->setViewMode(QListView::IconMode); m_attachmentList->setResizeMode(QListView::Adjust); m_attachmentList->setMovement(QListView::Static); m_attachmentList->setGridSize(QSize(300, 50)); m_attachmentList->setSpacing(10); m_attachmentList->setLayoutDirection(Qt::LeftToRight); m_attachmentList->setSelectionMode(QAbstractItemView::SingleSelection); m_attachmentList->setStyleSheet( "QListWidget { border: 1px solid #d1d1d6; border-radius: 4px; }" "QListWidget::item { border: none; padding: 5px; }" "QListWidget::item:selected { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #1976D2, stop:1 #1565C0); border-radius: 4px; }" ); m_attachmentList->setVisible(false); attachmentLayout->addWidget(m_attachmentList, 1); mainLayout->addLayout(attachmentLayout); // Action buttons row QHBoxLayout *actionsLayout = new QHBoxLayout(); // Attachments and Templates buttons (right-aligned) QHBoxLayout *buttonRow = new QHBoxLayout(); m_attachButton = new QToolButton(); m_attachButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg"))); m_attachButton->setToolTip(tr("Add attachment")); m_attachButton->setIconSize(QSize(20,20)); m_attachButton->setStyleSheet("QToolButton { border: none; padding: 5px; } QToolButton:hover { background: #e0e0e0; border-radius: 3px; }"); connect(m_attachButton, &QToolButton::clicked, this, &ComposeView::onAddAttachmentClicked); buttonRow->addWidget(m_attachButton); m_templateButton = new QToolButton(); m_templateButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Files/File Text.svg"))); // Assuming you have a template icon m_templateButton->setToolTip(tr("Email templates")); m_templateButton->setIconSize(QSize(20,20)); m_templateButton->setStyleSheet("QToolButton { border: none; padding: 5px; } QToolButton:hover { background: #e0e0e0; border-radius: 3px; }"); connect(m_templateButton, &QToolButton::clicked, this, &ComposeView::onTemplateClicked); buttonRow->addWidget(m_templateButton); buttonRow->setSpacing(4); actionsLayout->addLayout(buttonRow); actionsLayout->addStretch(); // Este resorte empuja el par (label+combo) a la izquierda m_discardButton = new QPushButton(tr("Discard")); m_discardButton->setStyleSheet( "QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px 20px; color: #555; }" "QPushButton:hover { background: #f5f5f5; }" ); connect(m_discardButton, &QPushButton::clicked, this, &ComposeView::discardRequested); actionsLayout->addWidget(m_discardButton); // Split button for Send / Schedule m_sendMenu = new QMenu(this); m_sendNowAction = m_sendMenu->addAction(tr("Send Now")); connect(m_sendNowAction, &QAction::triggered, this, &ComposeView::onSendClicked); m_scheduleAction = m_sendMenu->addAction(tr("Schedule for later...")); connect(m_scheduleAction, &QAction::triggered, [this]() { m_schedulePanel->setVisible(true); }); m_sendSplit = new QToolButton(); m_sendSplit->setText(tr("Send")); m_sendSplit->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg"))); m_templateButton->setIconSize(QSize(20,20)); //m_sendSplit->setToolButtonStyle(Qt::ToolButtonTextOnly); m_sendSplit->setPopupMode(QToolButton::MenuButtonPopup); m_sendSplit->setMenu(m_sendMenu); m_sendSplit->setStyleSheet( "QToolButton { background-color: #1976D2; color: white; border: none; border-radius: 4px; padding: 8px 24px; font-weight: bold; }" "QToolButton:hover { background-color: #1565C0; }" "QToolButton::menu-button { border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; padding-right: 8px; width: 20px;}" "QToolButton::menu-button:hover { background-color: #1565C0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }" ); connect(m_sendSplit, &QToolButton::clicked, this, &ComposeView::onSendClicked); actionsLayout->addWidget(m_sendSplit); mainLayout->addLayout(actionsLayout); } void ComposeView::onCcToggle() { m_ccVisible = !m_ccVisible; m_ccRow->setVisible(m_ccVisible); m_ccButton->setVisible(!m_ccVisible); if (m_ccVisible) m_ccField->setFocus(); } void ComposeView::onBccToggle() { m_bccVisible = !m_bccVisible; m_bccRow->setVisible(m_bccVisible); m_bccButton->setVisible(!m_bccVisible); if (m_bccVisible) m_bccField->setFocus(); } void ComposeView::onSendClicked() { emit sendRequested( m_toField->tags2().join(", "), m_ccVisible ? m_ccField->tags2().join(", ") : QString(), m_bccVisible ? m_bccField->tags2().join(", ") : QString(), m_subjectField->text(), m_bodyEditor->toHtml(), QDateTime(), // null = send now m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(), m_attachmentFiles ); } void ComposeView::onScheduleClicked() { emit sendRequested( m_toField->tags2().join(", "), m_ccVisible ? m_ccField->tags2().join(", ") : QString(), m_bccVisible ? m_bccField->tags2().join(", ") : QString(), m_subjectField->text(), m_bodyEditor->toHtml(), m_schedulePicker->dateTime(), m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(), m_attachmentFiles ); } void ComposeView::onDetachClicked() { emit detachRequested(this); } void ComposeView::setTo(const QString &to) { m_toField->tags(to.split(',', Qt::SkipEmptyParts)); } void ComposeView::setSubject(const QString &subject) { m_subjectField->setText(subject); } void ComposeView::setBody(const QString &body) { m_bodyEditor->setHtml(body); } void ComposeView::initializeComposition() { m_toField->tags(QStringList{}); m_ccField->tags(QStringList{}); m_bccField->tags(QStringList{}); m_subjectField->clear(); m_bodyEditor->clear(); m_attachmentFiles.clear(); m_attachmentList->clear(); m_attachmentList->setVisible(false); m_ccRow->setVisible(false); m_bccRow->setVisible(false); m_schedulePanel->setVisible(false); m_toField->setFocus(); } void ComposeView::startNewEmail(const QString &initialRecipient) { initializeComposition(); if (!initialRecipient.isEmpty()) m_toField->tags(initialRecipient.split(',', Qt::SkipEmptyParts)); m_toField->setFocus(); } void ComposeView::onAddAttachmentClicked() { qDebug() << "Attach button clicked"; QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Attachments"), QString(), tr("All Files (*)")); if (files.isEmpty()) return; for (const QString &file : files) { m_attachmentFiles.append(file); QListWidgetItem *item = new QListWidgetItem(m_attachmentList); item->setData(Qt::UserRole, file); QWidget *widget = new QWidget; widget->setFixedSize(300, 50); widget->setStyleSheet( "background-color: #f8f9fa; " "border-radius: 8px; " "border: 1px solid #e9ecef;" ); QHBoxLayout *layout = new QHBoxLayout(widget); layout->setContentsMargins(8, 4, 8, 4); layout->setSpacing(8); // Icon from system QFileIconProvider provider; QIcon icon = provider.icon(QFileInfo(file)); QLabel *iconLabel = new QLabel; QPixmap pix = icon.pixmap(32, 32); if (pix.isNull()) pix = QIcon::fromTheme("unknown").pixmap(32,32); iconLabel->setPixmap(pix); iconLabel->setFixedSize(32,32); layout->addWidget(iconLabel); // Text vertical layout QVBoxLayout *textLayout = new QVBoxLayout; textLayout->setSpacing(2); QString fileName = QFileInfo(file).fileName(); QLabel *nameLabel = new QLabel(fileName); nameLabel->setStyleSheet("font-weight: bold;"); QLabel *sizeLabel = new QLabel(tr("%1 KB").arg(QFileInfo(file).size()/1024)); sizeLabel->setStyleSheet("color: #666; font-size: 10px;"); textLayout->addWidget(nameLabel); textLayout->addWidget(sizeLabel); layout->addLayout(textLayout); layout->addStretch(); // Delete button QPushButton *delBtn = new QPushButton; QIcon delIcon = QIcon::fromTheme("edit-delete"); if (delIcon.isNull()) delIcon = QIcon::fromTheme("list-remove"); if (delIcon.isNull()) delIcon = QIcon::fromTheme("gtk-delete"); delBtn->setIcon(delIcon); delBtn->setToolTip(tr("Remove attachment")); delBtn->setFixedSize(24,24); delBtn->setStyleSheet( "QPushButton { border: none; background: transparent; }" "QPushButton:hover { background: #e9ecef; border-radius: 4px; }" ); layout->addWidget(delBtn); // Connect delete button connect(delBtn, &QPushButton::clicked, [this, item]() { int row = m_attachmentList->row(item); if (row >= 0) { QListWidgetItem *it = m_attachmentList->takeItem(row); if (it) { m_attachmentFiles.removeAt(row); delete it; } m_attachmentList->setVisible(!m_attachmentFiles.isEmpty()); } }); m_attachmentList->addItem(item); m_attachmentList->setItemWidget(item, widget); } m_attachmentList->setVisible(!m_attachmentFiles.isEmpty()); } void ComposeView::onRemoveAttachmentClicked() { QListWidgetItem *item = m_attachmentList->currentItem(); if (!item) return; int row = m_attachmentList->row(item); m_attachmentList->takeItem(row); m_attachmentFiles.removeAt(row); delete item; m_attachmentList->setVisible(!m_attachmentFiles.isEmpty()); } void ComposeView::onTemplateClicked() { // Placeholder for template functionality QMessageBox::information(this, tr("Email Templates"), tr("Email templates feature not yet implemented.")); } #include "composeview.moc"