Update: minor adjustments to CMakeLists.txt, ComposePage.qml, emailmanager.cpp, main.cpp
This commit is contained in:
+4
-3
@@ -65,6 +65,7 @@ set(SRC_FILES
|
||||
src/core/eventbus.cpp
|
||||
src/utils/notificationmanager.cpp
|
||||
src/core/emailmanager.cpp
|
||||
src/core/emailcomposerbridge.cpp
|
||||
src/core/synchronizerprovider.cpp
|
||||
src/syncscheduler.cpp
|
||||
src/core/accountsetupdialoglauncher.cpp
|
||||
@@ -76,7 +77,7 @@ set(SRC_FILES
|
||||
add_executable(wino-mail-qt ${SRC_FILES})
|
||||
|
||||
# Link Qt
|
||||
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Multimedia)
|
||||
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Quick Qt6::Multimedia)
|
||||
|
||||
# Link DTKWidget if found
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
@@ -84,8 +85,8 @@ if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
endif()
|
||||
|
||||
# If using external libs
|
||||
# target_link_libraries(wino-mail-qt PRIVATE ${LIBETPAN_LIBRARIES} ${GMIME_LIBRARIES})
|
||||
# target_include_directories(wino-mail-qt PRIVATE ${LIBETPAN_INCLUDE_DIRS} ${GMIME_INCLUDE_DIRS})
|
||||
target_link_libraries(wino-mail-qt PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(wino-mail-qt PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
|
||||
# Install (optional)
|
||||
install(TARGETS wino-mail-qt DESTINATION bin)
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtWebEngine 1.15
|
||||
import QtQml 2.15
|
||||
|
||||
Item {
|
||||
id: composePage
|
||||
anchors.fill: parent
|
||||
|
||||
// Expose a bridge to C++ for sending email and getting Quill content
|
||||
property var emailBridge: emailComposerBridge
|
||||
|
||||
// Back button
|
||||
Rectangle {
|
||||
anchors {
|
||||
@@ -69,17 +74,47 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Body
|
||||
// Body - WebEngineView loading editor.html with Quill
|
||||
Text {
|
||||
text: qsTr("Body:")
|
||||
font.pointSize: 14
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
TextArea {
|
||||
id: bodyArea
|
||||
placeholderText: qsTr("Write your message...")
|
||||
WebEngineView {
|
||||
id: bodyWebView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
url: "qrc:/resources/web/editor.html"
|
||||
// Settings to enable webchannel
|
||||
settings.javascriptEnabled: true
|
||||
settings.localContentCanAccessRemoteUrls: true
|
||||
settings.localContentCanAccessFileUrls: true
|
||||
}
|
||||
|
||||
// Attachments area (simple list placeholder)
|
||||
RowLayout {
|
||||
id: attachmentsRow
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
// Will be populated dynamically via C++ model or JS array
|
||||
// For now, placeholder
|
||||
Text {
|
||||
text: qsTr("Attachments: ")
|
||||
font.pointSize: 12
|
||||
}
|
||||
Text {
|
||||
id: attachmentsText
|
||||
text: qsTr("None")
|
||||
font.pointSize: 12
|
||||
color: "#666"
|
||||
}
|
||||
Button {
|
||||
text: qsTr("Attach")
|
||||
onClicked: {
|
||||
// Call C++ to open file dialog and get selected files
|
||||
emailBridge.openAttachmentDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send button
|
||||
@@ -93,10 +128,11 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// TODO: Implement sending email
|
||||
console.log("Sending email to:", toField.text, "subject:", subjectField.text)
|
||||
// For now, just go back
|
||||
StackView.view.pop()
|
||||
// Trigger sending via bridge
|
||||
emailBridge.sendComposedEmail(
|
||||
toField.text,
|
||||
subjectField.text
|
||||
)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
|
||||
+106
-34
@@ -1,3 +1,20 @@
|
||||
#include <gmime/gmime.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Undefine macros that conflict with Qt's signals/slots
|
||||
#ifdef public
|
||||
#undef public
|
||||
#endif
|
||||
#ifdef signals
|
||||
#undef signals
|
||||
#endif
|
||||
#ifdef slots
|
||||
#undef slots
|
||||
#endif
|
||||
#ifdef emit
|
||||
#undef emit
|
||||
#endif
|
||||
|
||||
#include "emailmanager.h"
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
@@ -5,6 +22,45 @@
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
|
||||
/* Helper: recursively find a part with given subtype */
|
||||
static GMimeObject *find_part_by_subtype(GMimeObject *obj, const char *subtype)
|
||||
{
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
GMimeContentType *ctype = g_mime_object_get_content_type(obj);
|
||||
if (ctype &&
|
||||
g_ascii_strcasecmp(g_mime_content_type_get_media_subtype(ctype), subtype) == 0)
|
||||
return obj;
|
||||
|
||||
if (GMIME_IS_MULTIPART(obj)) {
|
||||
GMimeMultipart *multipart = GMIME_MULTIPART(obj);
|
||||
guint count = g_mime_multipart_get_count(multipart);
|
||||
for (guint i = 0; i < count; ++i) {
|
||||
GMimeObject *part = g_mime_multipart_get_part(multipart, i);
|
||||
GMimeObject *found = find_part_by_subtype(part, subtype);
|
||||
if (found)
|
||||
return found;
|
||||
g_object_unref(part);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Helper: extract content as QString */
|
||||
static QString extract_content_as_string(GMimeObject *obj)
|
||||
{
|
||||
GMimeStream *stream = g_mime_stream_mem_new();
|
||||
g_mime_object_write_to_stream(obj, nullptr, stream);
|
||||
GMimeStreamMem *mem = GMIME_STREAM_MEM(stream);
|
||||
GByteArray *array = g_mime_stream_mem_get_byte_array(mem);
|
||||
const char *data = reinterpret_cast<const char*>(array->data);
|
||||
gsize size = array->len;
|
||||
QString result = QString::fromUtf8(data, size);
|
||||
g_object_unref(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
EmailManager::EmailManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -12,70 +68,86 @@ EmailManager::EmailManager(QObject *parent)
|
||||
|
||||
MailItem EmailManager::getMailItemById(qint64 id) const
|
||||
{
|
||||
// Use the DAO to fetch the MailItem by id
|
||||
auto optItem = MailItemDao::findById(id);
|
||||
if (optItem.has_value()) {
|
||||
return optItem.value();
|
||||
}
|
||||
// Return an empty MailItem if not found
|
||||
return MailItem();
|
||||
return optItem.has_value() ? optItem.value() : MailItem();
|
||||
}
|
||||
|
||||
QString EmailManager::getStorageDirectory() const
|
||||
{
|
||||
// Define where .eml files are stored (e.g., in the application data directory)
|
||||
QString storagePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(storagePath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath("."); // create if it doesn't exist
|
||||
}
|
||||
// Assuming .eml files are stored directly in this directory
|
||||
if (!dir.exists())
|
||||
dir.mkpath(".");
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
QString EmailManager::getEmailHtmlById(qint64 id) const
|
||||
{
|
||||
// Get the MailItem by id
|
||||
MailItem item = getMailItemById(id);
|
||||
if (item.id() == 0) {
|
||||
if (item.id() == 0)
|
||||
return "<html><body><h2>Error: Email not found</h2></body></html>";
|
||||
}
|
||||
// Construct the file path: storage directory + fileId + .eml
|
||||
|
||||
QString storageDir = getStorageDirectory();
|
||||
QString fileName = item.fileId();
|
||||
if (fileName.isEmpty()) {
|
||||
if (fileName.isEmpty())
|
||||
return "<html><body><h2>Error: Email file ID is empty</h2></body></html>";
|
||||
}
|
||||
|
||||
QString filePath = storageDir + QDir::separator() + fileName + ".eml";
|
||||
// Convert the .eml file to HTML
|
||||
return convertEmlToHtml(filePath);
|
||||
}
|
||||
|
||||
QString EmailManager::convertEmlToHtml(const QString& emlFilePath) const
|
||||
{
|
||||
// TODO: Implement actual MIME to HTML conversion using gmime or similar
|
||||
// For now, return a placeholder indicating the feature is not yet implemented
|
||||
QFile file(emlFilePath);
|
||||
if (!file.exists()) {
|
||||
return "<html><body><h2>Error: Email file not found</h2></body></html>";
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
g_mime_init();
|
||||
initialized = true;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
|
||||
FILE *fp = fopen(emlFilePath.toLocal8Bit().constData(), "r");
|
||||
if (!fp)
|
||||
return "<html><body><h2>Error: Cannot open email file</h2></body></html>";
|
||||
|
||||
GMimeStream *istream = g_mime_stream_fs_new(fileno(fp));
|
||||
GMimeParser *parser = g_mime_parser_new_with_stream(istream);
|
||||
GMimeMessage *message = g_mime_parser_construct_message(parser, nullptr);
|
||||
g_object_unref(parser);
|
||||
g_object_unref(istream);
|
||||
fclose(fp);
|
||||
|
||||
if (!message)
|
||||
return "<html><body><h2>Error: Failed to parse email message</h2></body></html>";
|
||||
|
||||
/* Try to get HTML part */
|
||||
GMimeObject *htmlPart = find_part_by_subtype(GMIME_OBJECT(message), "html");
|
||||
if (htmlPart) {
|
||||
QString html = extract_content_as_string(htmlPart);
|
||||
g_object_unref(htmlPart);
|
||||
g_object_unref(message);
|
||||
return html;
|
||||
}
|
||||
QByteArray data = file.readAll();
|
||||
file.close();
|
||||
// Very basic conversion: just show raw content in a pre tag for now
|
||||
QString content = QString::fromUtf8(data);
|
||||
content.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
return QString("<html><body style='font-family: monospace;'><h2>Email Content (raw)</h2><pre>%1</pre></body></html>")
|
||||
.arg(content);
|
||||
|
||||
/* Fallback to plain text */
|
||||
GMimeObject *textPart = find_part_by_subtype(GMIME_OBJECT(message), "plain");
|
||||
if (textPart) {
|
||||
QString plain = extract_content_as_string(textPart);
|
||||
g_object_unref(textPart);
|
||||
g_object_unref(message);
|
||||
QString escaped = plain;
|
||||
escaped.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
return QString("<html><body style='font-family:monospace;'><pre>%1</pre></body></html>")
|
||||
.arg(escaped);
|
||||
}
|
||||
|
||||
/* No displayable part */
|
||||
g_object_unref(message);
|
||||
return "<html><body><h2>Error: No displayable content in email</h2></body></html>";
|
||||
}
|
||||
|
||||
bool EmailManager::sendEmail(const QString& to, const QString& subject, const QString& body)
|
||||
{
|
||||
// TODO: Implement actual email sending
|
||||
// For now, just show a message and return true
|
||||
QMessageBox::information(nullptr, "Send Email",
|
||||
/* TODO: Implement real sending via RequestProcessor */
|
||||
QMessageBox::information(nullptr, "Send Email",
|
||||
QString("To: %1\nSubject: %2\nBody: %3").arg(to, subject, body));
|
||||
return true;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "core/translator.h"
|
||||
#include "db/dbchangeprocessor.h"
|
||||
#include "core/emailmanager.h"
|
||||
#include "core/emailcomposerbridge.h"
|
||||
#include "syncscheduler.h"
|
||||
#include "utils/notificationmanager.h"
|
||||
#include "core/accountsetupdialoglauncher.h"
|
||||
@@ -24,6 +25,9 @@ int main(int argc, char *argv[])
|
||||
// Create EmailManager to expose to QML
|
||||
EmailManager emailManager(&app);
|
||||
|
||||
// Create EmailComposerBridge to expose to QML
|
||||
EmailComposerBridge emailComposerBridge(&app);
|
||||
|
||||
// Create and start the sync scheduler
|
||||
SyncScheduler syncScheduler(&app);
|
||||
syncScheduler.start();
|
||||
@@ -38,6 +42,7 @@ int main(int argc, char *argv[])
|
||||
QQmlApplicationEngine engine;
|
||||
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
|
||||
engine.rootContext()->setContextProperty("emailManager", &emailManager);
|
||||
engine.rootContext()->setContextProperty("emailComposerBridge", &emailComposerBridge);
|
||||
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
|
||||
engine.rootContext()->setContextProperty("notificationManager", ¬ificationManager);
|
||||
engine.rootContext()->setContextProperty("accountSetupDialogLauncher", &accountSetupDialogLauncher);
|
||||
|
||||
Reference in New Issue
Block a user