presentation/QuickComposeBackend.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "data/FreeSlots.h" | ||
| 4 | |||
| 5 | #include "ComposeRecipients.h" | ||
| 6 | #include "ComposeReplyPrefill.h" | ||
| 7 | #include "ComposeSessionStore.h" | ||
| 8 | #include "MailOutboxSender.h" | ||
| 9 | #include "data/IdentityStore.h" | ||
| 10 | #include "data/TemplateStore.h" | ||
| 11 | #include "data/SpellLanguageMemory.h" | ||
| 12 | #include "AddressPickerModel.h" | ||
| 13 | #include "ComposePreview.h" | ||
| 14 | #include "SpellService.h" | ||
| 15 | #include "data/MailOutboxStore.h" | ||
| 16 | #include "ServerDraftCleanup.h" | ||
| 17 | #include "data/MailRequest.h" | ||
| 18 | #include <QHash> | ||
| 19 | #include <QSet> | ||
| 20 | #include <QObject> | ||
| 21 | #include <QStringList> | ||
| 22 | #include <QThreadPool> | ||
| 23 | #include <QVariantMap> | ||
| 24 | #include <functional> | ||
| 25 | #include <memory> | ||
| 26 | |||
| 27 | class MailSessionCore; | ||
| 28 | class WorkspaceState; | ||
| 29 | |||
| 30 | // Compose surface of the Quick frontend (Sprint 91 S91.3.1, rebuilt in | ||
| 31 | // Sprint 108): the composer sessions (ComposeSessionStore), their server | ||
| 32 | // drafts, the outbox, identities, templates, spelling, the address book | ||
| 33 | // sheet, the preview, suggest times and invitations, and the reply/forward | ||
| 34 | // prefill. QML reaches it as `backend.compose`. | ||
| 35 | // | ||
| 36 | // Owner: QuickMailBackend (value member). GUI thread; MIME builds run on the | ||
| 37 | // backend's single-thread pool (passed by reference), reply sources on the | ||
| 38 | // prefill's own pool. The backend calls connectComposeSource() and | ||
| 39 | // connectServerDrafts() at the positions of the former wire blocks, so | ||
| 40 | // receiver order is unchanged (cancelReplyDraft stays the first | ||
| 41 | // WorkspaceState::selectionChanged receiver). | ||
| 42 | namespace Imip { | ||
| 43 | struct Meeting; | ||
| 44 | struct Attendee; | ||
| 45 | } // namespace Imip | ||
| 46 | |||
| 47 | class QuickComposeBackend final : public QObject { | ||
| 48 | 232 | Q_OBJECT | |
| 49 | Q_PROPERTY(bool composeWindowed READ composeWindowed WRITE setComposeWindowed NOTIFY composeWindowedChanged) | ||
| 50 | // Sprint 108: every open composer (ComposeSessionStore), the message in | ||
| 51 | // its undo window ({id, summary, deadline}) and the outbox. | ||
| 52 | Q_PROPERTY(QObject *sessions READ sessionsObject CONSTANT) | ||
| 53 | // S108.5.3 (D-14): the rows of the address book sheet. | ||
| 54 | Q_PROPERTY(QObject *addressPicker READ addressPickerObject CONSTANT) | ||
| 55 | // §4.14: ⋯ › Preview of the focused composer. | ||
| 56 | Q_PROPERTY(QObject *preview READ previewObject CONSTANT) | ||
| 57 | // Phase 4: Sonnet's languages and dictionaries. | ||
| 58 | Q_PROPERTY(QObject *spell READ spellObject CONSTANT) | ||
| 59 | Q_PROPERTY(QVariantMap pendingSend READ pendingSend NOTIFY pendingSendChanged) | ||
| 60 | Q_PROPERTY(int outboxCount READ outboxCount NOTIFY outboxChanged) | ||
| 61 | Q_PROPERTY(bool outboxHasFailures READ outboxHasFailures NOTIFY outboxChanged) | ||
| 62 | // D-20: seconds a sent message waits for *Undo* (0–30). | ||
| 63 | Q_PROPERTY(int undoDelaySeconds READ undoDelaySeconds WRITE setUndoDelaySeconds NOTIFY undoDelayChanged) | ||
| 64 | // D-7: Markdown-style typing in formatted mails (Settings → Mail → Writing). | ||
| 65 | Q_PROPERTY(bool markdownTyping READ markdownTyping WRITE setMarkdownTyping NOTIFY markdownTypingChanged) | ||
| 66 | // D-26: the quick reply field above the shown message (Settings → Mail). | ||
| 67 | Q_PROPERTY(bool quickReply READ quickReply WRITE setQuickReply NOTIFY quickReplyChanged) | ||
| 68 | // Bumped when an identity or signature changed (Settings → account page). | ||
| 69 | Q_PROPERTY(int identitiesRevision READ identitiesRevision NOTIFY identitiesChanged) | ||
| 70 | public: | ||
| 71 | struct Deps { | ||
| 72 | std::function<bool(const MailId &)> acceptsIdentity; | ||
| 73 | std::function<QString()> accountName; | ||
| 74 | std::function<QString()> accountEmail; | ||
| 75 | std::function<QString()> activeAccountId; | ||
| 76 | std::function<void(const QString &)> setStatus; | ||
| 77 | std::function<void(const QString &)> reportError; | ||
| 78 | }; | ||
| 79 | QuickComposeBackend(MailSessionCore &session, WorkspaceState &workspace, | ||
| 80 | const QString &profileDirectory, QThreadPool &threadPool, | ||
| 81 | const QString &draftsFolder, const QString &sentFolder, | ||
| 82 | const QHash<qint64, QString> &folderPathById, | ||
| 83 | const QHash<qint64, quint32> &folderEpochById, Deps deps, | ||
| 84 | QObject *parent = nullptr); | ||
| 85 | void connectComposeSource(); | ||
| 86 | void connectServerDrafts(); | ||
| 87 | // Backend transitions: the compose part of an account reload / an account | ||
| 88 | // switch, and the durable teardown at the start of ~QuickMailBackend(). | ||
| 89 | void resetForAccountReload(); | ||
| 90 | void resetForAccountSwitch(); | ||
| 91 | void shutdown(); | ||
| 92 | 31 | bool draftCleanupActive() const { return m_draftCleanup.active(); } | |
| 93 | |||
| 94 | // F3.2: compose opens in its own top-level window when true. | ||
| 95 | 1596 | bool composeWindowed() const { return m_composeWindowed; } | |
| 96 | void setComposeWindowed(bool on); | ||
| 97 | // F-20 (SPRINT-104): the subject of a reply — every reply prefix counts, | ||
| 98 | // so "AW: Termin" becomes "Re: Termin" instead of "Re: AW: Termin", and a | ||
| 99 | // subject without a prefix gets "Re: ". Recognizes the | ||
| 100 | // common German (AW, WG, SV, FW), English (Re, Fwd), French (REF/RE), | ||
| 101 | // Dutch (VB) and Scandinavian (SV, VS) abbreviations, case-insensitive, | ||
| 102 | // repeated prefixes collapse to one "Re:". | ||
| 103 | 5 | Q_INVOKABLE static QString normalizeReplySubject(const QString &subject) { | |
| 104 | 5 | return ComposeReplyPrefill::normalizeReplySubject(subject); | |
| 105 | } | ||
| 106 | |||
| 107 | // Prefill for replies from the selected mail's header: recipient, a | ||
| 108 | // Re:-normalized subject, the quoted original body and the threading | ||
| 109 | // headers for the outgoing mail. With all=true the original recipients | ||
| 110 | // join the recipient field (reply-all). | ||
| 111 | 10 | Q_INVOKABLE QVariantMap replyDraft(bool all = false) const { return m_prefill.replyDraft(all); } | |
| 112 | // Forward prefill: "Fwd:" subject plus the quoted original message. | ||
| 113 | 1 | Q_INVOKABLE QVariantMap forwardDraft() const { return m_prefill.forwardDraft(); } | |
| 114 | // Interactive path: reserve a Compose intent before the read and deliver | ||
| 115 | // its bounded quote only while account, mail and epoch remain current. | ||
| 116 | 30 | Q_INVOKABLE bool requestReplyDraft(int requestToken, bool all = false, | |
| 117 | bool forward = false) { | ||
| 118 | 30 | return m_prefill.requestReplyDraft(requestToken, all, forward); | |
| 119 | } | ||
| 120 | 7 | Q_INVOKABLE void cancelReplyDraft() { m_prefill.cancelReplyDraft(); } | |
| 121 | // S108.7.2 (D-21): the shown draft of the drafts folder as a prefill, | ||
| 122 | // delivered through replyDraftReady like a reply. | ||
| 123 | 2 | Q_INVOKABLE bool requestDraftResume(int requestToken) { return m_prefill.requestDraftResume(requestToken); } | |
| 124 | // A recipient-field entry for a name and address (S106-373/384). | ||
| 125 | 16 | Q_INVOKABLE QString mailbox(const QString &name, const QString &address) const { | |
| 126 | 16 | return ComposeRecipients::mailbox(name, address); | |
| 127 | } | ||
| 128 | // F1: total budget across a message's attachments. Together with the | ||
| 129 | // per-file budget in Rfc2822Builder this bounds worst-case build RAM to | ||
| 130 | // 100 MiB even if every file sits exactly at its limit. Public: | ||
| 131 | // QuickComposeForwardAttachments enforces it on forwards and resumes. | ||
| 132 | static constexpr qint64 MaxComposeAttachmentsBytes = 100LL * 1024 * 1024; | ||
| 133 | |||
| 134 | // ─── Sprint 108: sessions, identities, outbox (QuickComposeBackendSessions.cpp) ─── | ||
| 135 | 765 | QObject *sessionsObject() { return &m_sessionStore; } | |
| 136 | 505 | ComposeSessionStore &sessionStore() { return m_sessionStore; } | |
| 137 | IdentityStore &identityStore() { return m_identityStore; } | ||
| 138 | 818 | AddressPickerModel &addressPicker() { return m_addressPicker; } | |
| 139 | 142 | QObject *addressPickerObject() { return &m_addressPicker; } | |
| 140 | 160 | QObject *previewObject() { return &m_preview; } | |
| 141 | 477 | QObject *spellObject() { return &m_spell; } | |
| 142 | MailOutboxSender *outbox() const { return m_outbox.get(); } | ||
| 143 | 27 | QVariantMap pendingSend() const { return m_pendingSend; } | |
| 144 | 36 | int undoDelaySeconds() const { return m_undoDelaySeconds; } | |
| 145 | void setUndoDelaySeconds(int seconds); | ||
| 146 | 36 | bool markdownTyping() const { return m_markdownTyping; } | |
| 147 | void setMarkdownTyping(bool on); | ||
| 148 | 46 | bool quickReply() const { return m_quickReply; } | |
| 149 | void setQuickReply(bool on); | ||
| 150 | // Whether a mailbox ("Name <address>" or an address) is one of the | ||
| 151 | // user's own addresses (D-26: *Follow up* instead of *Reply*). | ||
| 152 | Q_INVOKABLE bool isOwnAddress(const QString &mailbox) const; | ||
| 153 | 28 | int identitiesRevision() const { return m_identitiesRevision; } | |
| 154 | // S108.5.4/5.6 (D-15, D-16): the identities of one account for its | ||
| 155 | // settings page — {id, name, email, replyTo, formattedByDefault, | ||
| 156 | // wrapPlainText, bccSelf, isDefault, newMailSignatureId, | ||
| 157 | // replySignatureId, signatures: [{id, name, text}]}. | ||
| 158 | Q_INVOKABLE QVariantList identitiesFor(const QString &accountId) const; | ||
| 159 | // Validates and stores one identity of the map's shape (a new one when | ||
| 160 | // its id is empty); {ok, id} or {ok: false, error}. | ||
| 161 | Q_INVOKABLE QVariantMap saveIdentity(const QVariantMap &identity); | ||
| 162 | // Another address of the account; the account's default stays. | ||
| 163 | Q_INVOKABLE bool removeIdentity(const QString &identityId); | ||
| 164 | // S108.9.5 templates (QuickComposeBackendTemplates.cpp): [{id, name, | ||
| 165 | // subject, formatted}] sorted by name; save from a session's new text | ||
| 166 | // (the same name replaces), insert by id or name into a session, rename, | ||
| 167 | // delete. Saving and renaming answer {ok, id} or {ok: false, error}. | ||
| 168 | Q_INVOKABLE QVariantList templates() const; | ||
| 169 | Q_INVOKABLE QVariantMap saveTemplate(QObject *session, const QString &name); | ||
| 170 | Q_INVOKABLE bool insertTemplate(QObject *session, const QString &idOrName); | ||
| 171 | Q_INVOKABLE QVariantMap renameTemplate(const QString &id, const QString &name); | ||
| 172 | Q_INVOKABLE bool removeTemplate(const QString &id); | ||
| 173 | int outboxCount() const; | ||
| 174 | bool outboxHasFailures() const; | ||
| 175 | // A new mail in a tab or window; prefill keys as ComposeSession::prefill | ||
| 176 | // plus "attachments" (file paths copied into the draft). | ||
| 177 | Q_INVOKABLE QObject *openNewMessage(const QVariantMap &prefill, const QString &place = QStringLiteral("tab")); | ||
| 178 | // A reply/forward from a replyDraftReady() map (quote, format, identity | ||
| 179 | // inputs); place inline (with the mail), tab or window. | ||
| 180 | Q_INVOKABLE QObject *openReply(const QVariantMap &draft, const QString &kind, const QString &place, | ||
| 181 | const QVariantMap &mailReference = {}); | ||
| 182 | // A resumed draft (D-21): a new session (new id) that replaces the | ||
| 183 | // folder copy `resumedUid` on its next save and deletes it once sent. | ||
| 184 | Q_INVOKABLE QObject *openResumed(const QVariantMap &draft, const QString &place = QStringLiteral("tab")); | ||
| 185 | // The open session that already continues the draft with this UID. | ||
| 186 | Q_INVOKABLE QObject *sessionForServerDraft(qint64 uid) const; | ||
| 187 | // A message built elsewhere (iMIP replies, S93.5.2) through the outbox: | ||
| 188 | // sent at once from the primary account, retried like any send; the | ||
| 189 | // status line says `doneText` when it left. | ||
| 190 | // `identityId` sends through that identity's account (default: the primary). | ||
| 191 | bool enqueuePrepared(const QStringList &recipients, const QByteArray &message, const QString &doneText, | ||
| 192 | const QString &identityId = {}); | ||
| 193 | // S108.8.8/8.9 (QuickComposeBackendScheduling.cpp): meetings the user | ||
| 194 | // organizes, from the calendar. organizedEvent: {organized, foreign | ||
| 195 | // (someone else organizes it), organizerEmail, sequence, attendees: | ||
| 196 | // [{email, name, partstat, role}]}. newRecipientField: a chip model owned | ||
| 197 | // by `owner` (the event editor's attendees). sendMeetingChange: kind | ||
| 198 | // "update" (REQUEST with SEQUENCE+1 to `attendees`, CANCEL to removed | ||
| 199 | // ones; the stored event takes the new list), "silent" (the stored event | ||
| 200 | // takes the list, no mail), "removed" (the stored event takes the list, | ||
| 201 | // CANCEL to the removed ones only — the invitation composer follows) or | ||
| 202 | // "cancel" (CANCEL to every attendee; the caller deletes the event). | ||
| 203 | Q_INVOKABLE QVariantMap organizedEvent(const QString &uid, const QString &accountId, const QString &calendarPath) const; | ||
| 204 | Q_INVOKABLE QObject *newRecipientField(QObject *owner, const QString &label); | ||
| 205 | Q_INVOKABLE bool sendMeetingChange(const QString &uid, const QString &accountId, const QString &calendarPath, | ||
| 206 | const QString &kind, const QVariantList &attendees = {}); | ||
| 207 | // S108.8.1/8.2 (D-22, QuickComposeBackendMeeting.cpp): free times of the | ||
| 208 | // next 14 days over the shown calendars, within working hours and days — | ||
| 209 | // [{start, end, day, time}] — and the block that offers up to five of them | ||
| 210 | // at the caret, in the message language with the time zone. | ||
| 211 | Q_INVOKABLE QVariantList freeSlots(int durationMinutes, int dayStartHour, int dayEndHour, const QVariantList &workdays, | ||
| 212 | const QStringList &hiddenCalendars) const; | ||
| 213 | Q_INVOKABLE bool insertSuggestedTimes(QObject *session, const QVariantList &chosen); | ||
| 214 | // S108.8.6 (D-25): the scheduling grid. ownBusy: the user's busy times of | ||
| 215 | // the next `days` days ([{start, end}], local time). requestAttendeeBusy: | ||
| 216 | // the attendees' (To and Cc) through the calendar account's free/busy — | ||
| 217 | // attendeeBusyReady(sessionId, [{email, name, known, busy}]) follows. | ||
| 218 | // nextFreeForAll: the first `minutes` long time after `after` within | ||
| 219 | // working hours and days that overlaps none of `busy`; {} when none in 14 days. | ||
| 220 | // daySpans: what one row draws — `busy` within the working hours of | ||
| 221 | // `day`'s date as [{from, to}] fractions of them (FreeSlots::daySpans). | ||
| 222 | Q_INVOKABLE QVariantList ownBusy(const QStringList &hiddenCalendars, int days = 14) const; | ||
| 223 | Q_INVOKABLE QVariantList daySpans(const QVariantList &busy, const QDateTime &day, int dayStartHour, | ||
| 224 | int dayEndHour) const; | ||
| 225 | // §4.11: the same day as words for a screen reader — "busy 13:00 to | ||
| 226 | // 14:00, free otherwise", "free all day" or "busy all day". | ||
| 227 | Q_INVOKABLE QString busyDescription(const QVariantList &busy, const QDateTime &day, int dayStartHour, | ||
| 228 | int dayEndHour) const; | ||
| 229 | Q_INVOKABLE void requestAttendeeBusy(QObject *session, int days = 14); | ||
| 230 | Q_INVOKABLE QVariantMap nextFreeForAll(const QVariantList &busy, const QDateTime &after, int minutes, | ||
| 231 | int dayStartHour, int dayEndHour, const QVariantList &workdays) const; | ||
| 232 | // D-23: the event of an invitation that just left (QuickComposeBackendMeeting.cpp). | ||
| 233 | // `serverSchedules`: the server delivers (D-24) — the event is written | ||
| 234 | // without SCHEDULE-AGENT=CLIENT, so the server sends the invitations. | ||
| 235 | bool writeOrganizedEvent(const OutboxEntry &entry, const QByteArray &sentBytes, bool serverSchedules = false); | ||
| 236 | // S108.8.4 (D-24): who delivers a meeting of `organizer` in a calendar of | ||
| 237 | // `calendarAccount` — "server", "client" or "fallback" (Imip::deliveryMode | ||
| 238 | // with the *Invitations and replies* setting). invitationDelivery adds the | ||
| 239 | // meeting panel's note: {mode, note}. | ||
| 240 | QString invitationDeliveryMode(const QString &calendarAccount, const QString &organizer) const; | ||
| 241 | Q_INVOKABLE QVariantMap invitationDelivery(const QString &calendarAccount, const QString &organizer) const; | ||
| 242 | // *Undo* of a send (0 = the newest message in its undo window). | ||
| 243 | Q_INVOKABLE bool undoSend(qint64 outboxId = 0); | ||
| 244 | Q_INVOKABLE QVariantList outboxEntries() const; | ||
| 245 | // The Outbox view's reading pane: to, subject, text, files, state. | ||
| 246 | Q_INVOKABLE QVariantMap outboxPreview(qint64 id) const; | ||
| 247 | Q_INVOKABLE bool outboxSendNow(qint64 id); | ||
| 248 | Q_INVOKABLE bool outboxReschedule(qint64 id, const QDateTime &when); | ||
| 249 | Q_INVOKABLE bool outboxResendToRefused(qint64 id); | ||
| 250 | // Back to an editable draft (Outbox *Edit*, a refused message). | ||
| 251 | Q_INVOKABLE QObject *outboxEdit(qint64 id); | ||
| 252 | Q_INVOKABLE bool outboxDelete(qint64 id); | ||
| 253 | // Quit: messages in their undo window leave now (D-20). | ||
| 254 | void flushOutboxOnQuit(); | ||
| 255 | |||
| 256 | signals: | ||
| 257 | void pendingSendChanged(); | ||
| 258 | void undoDelayChanged(); | ||
| 259 | void markdownTypingChanged(); | ||
| 260 | void quickReplyChanged(); | ||
| 261 | void identitiesChanged(); | ||
| 262 | void templatesChanged(); | ||
| 263 | void outboxChanged(); | ||
| 264 | // A sent session that came back from the undo window or the outbox. | ||
| 265 | void sessionReturned(QObject *session); | ||
| 266 | void composeWindowedChanged(); | ||
| 267 | void replyDraftReady(int requestToken, const QVariantMap &draft); | ||
| 268 | void attendeeBusyReady(const QString &sessionId, const QVariantList &rows); | ||
| 269 | |||
| 270 | private: | ||
| 271 | // connectServerDrafts() step: completions and failures of the sessions' | ||
| 272 | // journaled Drafts appends. | ||
| 273 | void connectDraftAppendResults(); | ||
| 274 | MailSessionCore &m_session; | ||
| 275 | WorkspaceState &m_workspace; | ||
| 276 | const QString &m_profileDirectory; // owned by QuickMailBackend | ||
| 277 | QThreadPool &m_threadPool; // QuickMailBackend's shared pool | ||
| 278 | const QString &m_draftsFolder; // QuickMailBackend (folder list) | ||
| 279 | const QString &m_sentFolder; // QuickMailBackend (folder list) | ||
| 280 | const QHash<qint64, QString> &m_folderPathById; // QuickMailBackend identity cache | ||
| 281 | const QHash<qint64, quint32> &m_folderEpochById; | ||
| 282 | Deps m_deps; | ||
| 283 | // Per-session server-draft revisions: the newest journaled append token | ||
| 284 | // and its completed UIDPLUS UID (0 = none yet). A newer revision's | ||
| 285 | // completion removes the previous one; sends and closes clean up. | ||
| 286 | QHash<QString, qint64> m_sessionDraftTokens; | ||
| 287 | QHash<QString, qint64> m_sessionDraftUids; | ||
| 288 | QHash<qint64, quint32> m_draftUidValidity; // APPENDUID of Drafts copies | ||
| 289 | // A session's previous server copy goes; its mailbox identity may come | ||
| 290 | // from the draft file (after a restart) instead of this run's APPENDUID. | ||
| 291 | void retireSessionServerCopy(qint64 uid, quint32 uidValidity); | ||
| 292 | // S106-259: appends a newer revision (or a closed composer) superseded | ||
| 293 | // while they were still in flight; their revision is deleted on arrival. | ||
| 294 | QSet<qint64> m_supersededDraftTokens; | ||
| 295 |
1/3✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 26 times.
✗ Branch 3 → 4 not taken.
|
26 | void retireDraftToken(qint64 token) { if (token > 0) m_supersededDraftTokens.insert(token); } |
| 296 | ServerDraftCleanup m_draftCleanup; // Sprint 102 S102.5.1: exact UID EXPUNGE | ||
| 297 | // STORE \Deleted + UID EXPUNGE of one Drafts revision (ServerDraftCleanup). | ||
| 298 | void deleteServerDraftRevision(qint64 draftUid); | ||
| 299 | bool m_composeWindowed = false; | ||
| 300 | ComposeReplyPrefill m_prefill; // S108.0.2: reply/forward prefill unit | ||
| 301 | // Sprint 108 sessions and the outbox (QuickComposeBackendSessions.cpp). | ||
| 302 | void initComposeSessions(); | ||
| 303 | ComposeSession::Deps sessionDeps(); | ||
| 304 | MailOutboxSender::Deps outboxDeps(); | ||
| 305 | void reconcileIdentities(); | ||
| 306 | void migrateOldDrafts(); | ||
| 307 | QList<FreeSlots::Interval> ownBusyIntervals(const QDate &from, int days, const QStringList &hiddenCalendars) const; | ||
| 308 | QStringList ownAddresses() const; | ||
| 309 | bool sendMeetingMail(Imip::Meeting meeting, const QString &method, const QList<Imip::Attendee> &to); | ||
| 310 | IdentityStore m_identityStore; | ||
| 311 | TemplateStore m_templateStore; | ||
| 312 | AddressPickerModel m_addressPicker; | ||
| 313 | ComposePreview m_preview; | ||
| 314 | SpellService m_spell; | ||
| 315 | SpellLanguageMemory m_spellMemory; // D-12: <profile>/spell-languages.json | ||
| 316 | QHash<qint64, QString> m_preparedDoneTexts; // outbox id → status when it left | ||
| 317 | ComposeSessionStore m_sessionStore; | ||
| 318 | std::unique_ptr<MailOutboxStore> m_outboxStore; | ||
| 319 | std::unique_ptr<MailOutboxSender> m_outbox; | ||
| 320 | QVariantMap m_pendingSend; | ||
| 321 | int m_undoDelaySeconds = 10; | ||
| 322 | bool m_markdownTyping = true; | ||
| 323 | bool m_quickReply = true; | ||
| 324 | int m_identitiesRevision = 0; | ||
| 325 | void identitiesEdited(); | ||
| 326 | QHash<QString, qint64> m_sizeLimits; // account id → SMTP SIZE | ||
| 327 | }; | ||
| 328 |