presentation/ComposeSession.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "ComposeAttachmentModel.h" | ||
| 4 | #include "ComposeHighlighter.h" | ||
| 5 | #include "ComposeMessage.h" | ||
| 6 | #include "ComposePasteJob.h" | ||
| 7 | #include "RecipientChipModel.h" | ||
| 8 | #include "RichTextController.h" | ||
| 9 | #include "data/IdentityStore.h" | ||
| 10 | |||
| 11 | #include <QDateTime> | ||
| 12 | #include <QJsonObject> | ||
| 13 | #include <QObject> | ||
| 14 | #include <QPointer> | ||
| 15 | #include <QSet> | ||
| 16 | #include <QThreadPool> | ||
| 17 | #include <QTimer> | ||
| 18 | #include <QVariantList> | ||
| 19 | #include <functional> | ||
| 20 | #include <limits> | ||
| 21 | #include <memory> | ||
| 22 | |||
| 23 | |||
| 24 | |||
| 25 | // Sprint 108 (§5.1): one draft — a new mail, a reply, a forward or a resumed | ||
| 26 | // draft — wherever it is shown (a tab, inline above its mail, a window). | ||
| 27 | // Owns its recipients (chips), its editor engine, its attachments and its | ||
| 28 | // draft directory; saves itself (3 s after the last change locally, the | ||
| 29 | // server copy after 2 minutes idle or on close, D-21) and hands a finished | ||
| 30 | // message to the outbox on *Send* (D-20). QML reaches it through | ||
| 31 | // ComposeSessionStore (`backend.compose.sessions`). | ||
| 32 | class ComposeSession : public QObject { | ||
| 33 | 1232 | Q_OBJECT | |
| 34 | Q_PROPERTY(QString sessionId READ sessionId CONSTANT) | ||
| 35 | Q_PROPERTY(QString kind READ kind CONSTANT) // new, reply, replyAll, forward, resumed | ||
| 36 | Q_PROPERTY(QString place READ place NOTIFY placeChanged) // tab, inline, window | ||
| 37 | Q_PROPERTY(QString title READ title NOTIFY subjectChanged) | ||
| 38 | Q_PROPERTY(QString subject READ subject WRITE setSubject NOTIFY subjectChanged) | ||
| 39 | Q_PROPERTY(bool formatted READ formatted NOTIFY formattedChanged) | ||
| 40 | Q_PROPERTY(QString identityId READ identityId NOTIFY identityChanged) | ||
| 41 | Q_PROPERTY(QVariantMap identity READ identityMap NOTIFY identityChanged) | ||
| 42 | Q_PROPERTY(QVariantList identityChoices READ identityChoices NOTIFY identityChoicesChanged) | ||
| 43 | Q_PROPERTY(QString signatureId READ signatureId NOTIFY signatureChanged) | ||
| 44 | Q_PROPERTY(QVariantList signatureChoices READ signatureChoices NOTIFY identityChanged) | ||
| 45 | Q_PROPERTY(QObject *to READ toModel CONSTANT) | ||
| 46 | Q_PROPERTY(QObject *cc READ ccModel CONSTANT) | ||
| 47 | Q_PROPERTY(QObject *bcc READ bccModel CONSTANT) | ||
| 48 | Q_PROPERTY(QObject *attachments READ attachmentModel CONSTANT) | ||
| 49 | Q_PROPERTY(QObject *editor READ editorObject CONSTANT) | ||
| 50 | Q_PROPERTY(bool dirty READ dirty NOTIFY saveStateChanged) | ||
| 51 | Q_PROPERTY(QString saveState READ saveState NOTIFY saveStateChanged) | ||
| 52 | Q_PROPERTY(bool saveFailed READ saveFailed NOTIFY saveStateChanged) | ||
| 53 | Q_PROPERTY(QVariantList checks READ checks NOTIFY checksChanged) | ||
| 54 | Q_PROPERTY(bool sending READ sending NOTIFY sendingChanged) | ||
| 55 | // A large HTML paste or drop is going in (ComposePasteJob): the text is read-only meanwhile. | ||
| 56 | Q_PROPERTY(bool pasting READ pasting NOTIFY pastingChanged) | ||
| 57 | Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY optionsChanged) | ||
| 58 | Q_PROPERTY(bool readReceipt READ readReceipt WRITE setReadReceipt NOTIFY optionsChanged) | ||
| 59 | Q_PROPERTY(bool hasQuote READ hasQuote NOTIFY quoteChanged) | ||
| 60 | Q_PROPERTY(bool quoteInEditor READ quoteInEditor NOTIFY quoteChanged) | ||
| 61 | Q_PROPERTY(QString quoteAttribution READ quoteAttribution NOTIFY quoteChanged) | ||
| 62 | Q_PROPERTY(QVariantMap mailReference READ mailReference NOTIFY placeChanged) | ||
| 63 | Q_PROPERTY(QString notice READ notice NOTIFY noticeChanged) | ||
| 64 | Q_PROPERTY(QString language READ language NOTIFY languageChanged) | ||
| 65 | // D-26: a reply started from the quick reply field — the view shows the | ||
| 66 | // text first, where the field was (runtime only, not part of the draft). | ||
| 67 | Q_PROPERTY(bool quickStart READ quickStart WRITE setQuickStart NOTIFY placeChanged) | ||
| 68 | // D-23: the meeting this message invites to — {active, start (QDateTime), | ||
| 69 | // durationMinutes, location, calendarAccount, calendarPath, uid, and for a | ||
| 70 | // stored event's invitation rrule and sequence}; empty for a plain mail. | ||
| 71 | Q_PROPERTY(QVariantMap meeting READ meeting NOTIFY meetingChanged) | ||
| 72 | // Phase 4 (D-11): spelling — available when Sonnet has a dictionary; | ||
| 73 | // spellLanguage is the checked language ("" = from the message language, | ||
| 74 | // "off" = no checking for this message). | ||
| 75 | Q_PROPERTY(bool spellAvailable READ spellAvailable CONSTANT) | ||
| 76 | Q_PROPERTY(QString spellLanguage READ spellLanguage WRITE setSpellLanguage NOTIFY languageChanged) | ||
| 77 | Q_PROPERTY(QString checkedLanguage READ checkedLanguage NOTIFY languageChanged) | ||
| 78 | // D-4 (CJ18): a format key in a plain-text mail changes nothing and shows | ||
| 79 | // the quiet *Switch* line, once per mail; switching to Formatted then | ||
| 80 | // applies the key to the text it was pressed on. | ||
| 81 | Q_PROPERTY(bool formatHint READ formatHint NOTIFY formatHintChanged) | ||
| 82 | |||
| 83 | public: | ||
| 84 | struct Deps { | ||
| 85 | QString profileDirectory; | ||
| 86 | std::function<QList<Identity>()> identities; | ||
| 87 | std::function<bool(const QString &address)> knownAddress; | ||
| 88 | std::function<qint64(const QString &accountId)> serverSizeLimit; | ||
| 89 | std::function<QStringList()> ownDomains; | ||
| 90 | std::function<QStringList()> attachmentWords; | ||
| 91 | std::function<QString()> uiLanguage; | ||
| 92 | // Hands a finished message to the outbox; false when it could not be queued. | ||
| 93 | std::function<bool(ComposeSession *, const ComposeMessage &, const QDateTime &due, bool scheduled)> enqueue; | ||
| 94 | // Queues the server copy of the draft (journaled APPEND to Drafts). | ||
| 95 | std::function<void(ComposeSession *, const ComposeMessage &)> saveServerDraft; | ||
| 96 | int localSaveDelayMs = 3000; | ||
| 97 | int serverSaveIdleMs = 120000; | ||
| 98 | // Settings → Writing *Undo send* delay (0–30 s; D-20 default 10). | ||
| 99 | 4 | std::function<int()> undoDelaySeconds = [] { return 10; }; | |
| 100 | // D-7: Markdown-style typing in formatted mode (Settings → Writing). | ||
| 101 | std::function<bool()> markdownTyping; | ||
| 102 | // Phase 4: Sonnet's spellers (nullptr or unavailable: no spelling). | ||
| 103 | class SpellService *spell = nullptr; | ||
| 104 | // D-12: the spelling language last used with an address, and the | ||
| 105 | // remembering of it when a message leaves (SpellLanguageMemory). | ||
| 106 | std::function<QString(const QString &address)> rememberedLanguage; | ||
| 107 | std::function<void(const QString &address, const QString &language)> rememberLanguage; | ||
| 108 | }; | ||
| 109 | struct Quote { | ||
| 110 | QString attribution; // in the message language | ||
| 111 | QString text; // the original's text (unquoted) | ||
| 112 | QString html; // the original's HTML body fragment (MailHtmlWriter output), "" = plain | ||
| 113 | }; | ||
| 114 | |||
| 115 | ComposeSession(const QString &sessionId, const QString &kind, Deps deps, QObject *parent = nullptr); | ||
| 116 | ~ComposeSession() override; | ||
| 117 | |||
| 118 | 3969 | QString sessionId() const { return m_id; } | |
| 119 | 619 | QString kind() const { return m_kind; } | |
| 120 | 1964 | QString place() const { return m_place; } | |
| 121 | void setPlace(const QString &place, const QVariantMap &mailReference = {}); | ||
| 122 | QString title() const; | ||
| 123 | 339 | QString subject() const { return m_subject; } | |
| 124 | void setSubject(const QString &subject); | ||
| 125 | 484 | bool formatted() const { return m_formatted; } | |
| 126 | 55 | QString identityId() const { return m_identityId; } | |
| 127 | QVariantMap identityMap() const; | ||
| 128 | QVariantList identityChoices() const; | ||
| 129 | 226 | QString signatureId() const { return m_signatureId; } | |
| 130 | QVariantList signatureChoices() const; | ||
| 131 | 2089 | QObject *toModel() { return &m_to; } | |
| 132 | 478 | QObject *ccModel() { return &m_cc; } | |
| 133 | 439 | QObject *bccModel() { return &m_bcc; } | |
| 134 | 144 | RecipientChipModel &to() { return m_to; } | |
| 135 | 37 | RecipientChipModel &cc() { return m_cc; } | |
| 136 | 14 | RecipientChipModel &bcc() { return m_bcc; } | |
| 137 | 204 | QObject *attachmentModel() { return m_attachments.get(); } | |
| 138 | 289 | ComposeAttachmentModel &attachments() { return *m_attachments; } | |
| 139 | 463 | QObject *editorObject() { return &m_editor; } | |
| 140 | 186 | RichTextController &editor() { return m_editor; } | |
| 141 | 399 | QTextDocument *document() const { return m_document; } | |
| 142 | // The text as plain text, also while no view shows the session. | ||
| 143 | QString plainText() const; | ||
| 144 | 162 | bool dirty() const { return m_revision != m_savedRevision; } | |
| 145 | 929 | QString saveState() const { return m_saveState; } | |
| 146 | 1003 | bool saveFailed() const { return m_saveFailed; } | |
| 147 | 1063 | QVariantList checks() const { return m_checks; } | |
| 148 | 2025 | bool sending() const { return m_sending; } | |
| 149 |
4/4✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 6 taken 205 times.
✓ Branch 4 → 5 taken 14 times.
✓ Branch 4 → 6 taken 7 times.
|
226 | bool pasting() const { return m_pasteJob && m_pasteJob->running(); } |
| 150 | 2 | int priority() const { return m_priority; } | |
| 151 | void setPriority(int priority); | ||
| 152 | 1 | bool readReceipt() const { return m_readReceipt; } | |
| 153 | void setReadReceipt(bool on); | ||
| 154 |
3/4✓ Branch 3 → 4 taken 306 times.
✓ Branch 3 → 6 taken 47 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 306 times.
|
353 | bool hasQuote() const { return !m_quote.text.isEmpty() || !m_quote.html.isEmpty(); } |
| 155 | 205 | bool quoteInEditor() const { return m_quoteInEditor; } | |
| 156 | 204 | QString quoteAttribution() const { return m_quote.attribution; } | |
| 157 | 454 | QVariantMap mailReference() const { return m_mailReference; } | |
| 158 | 214 | QString notice() const { return m_notice; } | |
| 159 | 472 | QString language() const { return m_language; } | |
| 160 | // The UID of this draft's newest copy in the Drafts folder (0: none yet), | ||
| 161 | // kept with the draft so a restart still replaces it instead of adding one. | ||
| 162 | 12 | qint64 serverDraftUid() const { return m_serverDraftUid; } | |
| 163 | 7 | quint32 serverDraftUidValidity() const { return m_serverDraftUidValidity; } | |
| 164 | 4 | void setServerDraftUid(qint64 uid, quint32 uidValidity) { | |
| 165 | 4 | m_serverDraftUid = uid; | |
| 166 | 4 | m_serverDraftUidValidity = uidValidity; | |
| 167 | 4 | m_localTimer.start(); // written with the draft, not a change of it | |
| 168 | 4 | } | |
| 169 | |||
| 170 | bool spellAvailable() const; | ||
| 171 | 1 | QString spellLanguage() const { return m_spellLanguage; } | |
| 172 | void setSpellLanguage(const QString &language); | ||
| 173 | // The language actually checked ("" when spelling is off or unavailable). | ||
| 174 | QString checkedLanguage() const; | ||
| 175 | // The misspelled word at a document position: {start, length, word, | ||
| 176 | // suggestions}; empty when there is none. | ||
| 177 | Q_INVOKABLE QVariantMap misspellingAt(int position) const; | ||
| 178 | // The next misspelled word after `position` (wrapping once): {start, length}. | ||
| 179 | Q_INVOKABLE QVariantMap nextMisspelling(int position) const; | ||
| 180 | Q_INVOKABLE void replaceRange(int start, int length, const QString &text); | ||
| 181 | Q_INVOKABLE void ignoreWord(const QString &word); // this message only | ||
| 182 | Q_INVOKABLE void addToDictionary(const QString &word); // the personal dictionary | ||
| 183 | // The editor reports the caret while typing: its word is judged after a | ||
| 184 | // 400 ms pause (D-12). | ||
| 185 | Q_INVOKABLE void caretMoved(int position, bool typing); | ||
| 186 | 910 | QVariantMap meeting() const { return m_meeting; } | |
| 187 |
2/2✓ Branch 6 → 7 taken 160 times.
✓ Branch 7 → 8 taken 160 times.
|
320 | bool isInvitation() const { return m_meeting.value(QStringLiteral("active")).toBool(); } |
| 188 | // Turns the message into an invitation (D-23) or updates it; the uid is | ||
| 189 | // kept across edits so the event and every later update share it. | ||
| 190 | Q_INVOKABLE void setMeeting(const QVariantMap &meeting); | ||
| 191 | Q_INVOKABLE void clearMeeting(); | ||
| 192 | // The meeting as an iMIP REQUEST of this draft (organizer = the identity, | ||
| 193 | // To = required, Cc = optional attendees, the new text as description). | ||
| 194 | QByteArray meetingRequest() const; | ||
| 195 | 65 | bool quickStart() const { return m_quickStart; } | |
| 196 | 2 | void setQuickStart(bool on) { | |
| 197 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
|
2 | if (m_quickStart == on) return; |
| 198 | 2 | m_quickStart = on; | |
| 199 | 2 | emit placeChanged(); | |
| 200 | } | ||
| 201 | // D-26: the characters typed into the quick reply field go to the start | ||
| 202 | // of the text (now, or when the editor attaches); takeStartCaret() then | ||
| 203 | // answers the caret after them once (-1: nothing pending). | ||
| 204 | Q_INVOKABLE void startWithText(const QString &text); | ||
| 205 | 2 | Q_INVOKABLE int takeStartCaret() { return std::exchange(m_startCaret, -1); } | |
| 206 | // The editor's document: QML passes TextEdit.textDocument once. | ||
| 207 | // Identities or signatures were edited in the settings. | ||
| 208 | 9 | void identitiesChanged() { | |
| 209 | 9 | emit identityChoicesChanged(); | |
| 210 | 9 | emit identityChanged(); | |
| 211 | 9 | } | |
| 212 | Q_INVOKABLE void attachEditor(QObject *quickTextDocument); | ||
| 213 | // The view is going away: the session keeps the text (see keepTextOf). | ||
| 214 | Q_INVOKABLE void detachEditor(QObject *quickTextDocument); | ||
| 215 | // Tests and windows without QML: a plain document owned by the session. | ||
| 216 | void attachDocument(QTextDocument *document); | ||
| 217 | // The display layer (headings, code, dark-theme contrast, spelling): | ||
| 218 | // the view passes the token palette (text, secondary, surface, | ||
| 219 | // codeBackground, spellError, dark) and the base font size. | ||
| 220 | Q_INVOKABLE void setDisplayPalette(const QVariantMap &palette, qreal basePointSize); | ||
| 221 | 1 | ComposeHighlighter *highlighter() const { return m_highlighter; } | |
| 222 | // Paste from the clipboard (D-9, S108.3.3): files are attached, an image | ||
| 223 | // is placed (formatted) or attached (plain), HTML goes through | ||
| 224 | // ComposeHtmlImport, text is inserted as text. Returns the caret position | ||
| 225 | // after the inserted text, or -1 when nothing went into the text. | ||
| 226 | Q_INVOKABLE int paste(bool plainTextOnly = false); | ||
| 227 | // Dropped content (DropArea): URLs, HTML or text at the caret. | ||
| 228 | Q_INVOKABLE int dropContent(const QList<QUrl> &urls, const QString &html, const QString &text, bool placeImages); | ||
| 229 | // Places an attached image in the text at the caret (formatted mode). | ||
| 230 | Q_INVOKABLE void placeImage(const QString &attachmentId); | ||
| 231 | |||
| 232 | // Prefill of a new composer: to, cc, bcc, subject, body (plain), html | ||
| 233 | // (formatted), inReplyTo, references, identityId, formatted, quote. | ||
| 234 | void prefill(const QVariantMap &draft, const Quote "e = {}); | ||
| 235 | Q_INVOKABLE void setIdentity(const QString &identityId); | ||
| 236 | Q_INVOKABLE void setSignature(const QString &signatureId, bool replaceEdited = false); | ||
| 237 | Q_INVOKABLE void cycleSignature(); | ||
| 238 | // D-4: Formatted ↔ Plain text. Going plain with losses needs `confirmed`. | ||
| 239 | Q_INVOKABLE QStringList formatLosses() const; | ||
| 240 | Q_INVOKABLE void setFormatted(bool formatted, bool confirmed = false); | ||
| 241 | // The format keys of D-28 by name: bold, italic, underline, strike, code, | ||
| 242 | // bullets, numbered, quote (applied), link (formatted mode: the view's). | ||
| 243 | Q_INVOKABLE void format(const QString &action); | ||
| 244 | 214 | bool formatHint() const { return m_formatHint; } | |
| 245 | Q_INVOKABLE void dismissFormatHint(); | ||
| 246 | // §4.3a: the full quote into the editor (*Edit quote*); the hidden quote ends. | ||
| 247 | Q_INVOKABLE void includeQuote(); | ||
| 248 | |||
| 249 | // *Send*: runs the checks (D-19). Returns "checks" when the check bar | ||
| 250 | // shows findings, "queued" when the message is in the outbox, "error". | ||
| 251 | Q_INVOKABLE QString send(bool anyway = false); | ||
| 252 | Q_INVOKABLE QString sendLater(const QDateTime &when, bool anyway = false); | ||
| 253 | // *Undo* took the message back from the outbox: editable again. | ||
| 254 | void reopenAfterUndo(); | ||
| 255 | Q_INVOKABLE void dismissCheck(int index); | ||
| 256 | Q_INVOKABLE void clearChecks(); | ||
| 257 | // The *Move to Bcc* fix (ManyWithoutBcc): every To/Cc chip moves to Bcc; | ||
| 258 | // the own identity stays in To so the mail has a visible recipient. | ||
| 259 | Q_INVOKABLE void moveRecipientsToBcc(); | ||
| 260 | // Explicit save (Ctrl+S): local now, server copy queued. | ||
| 261 | Q_INVOKABLE bool save(); | ||
| 262 | // Close keeping the draft (local save now, server copy queued when dirty). | ||
| 263 | Q_INVOKABLE bool closeKeepingDraft(); | ||
| 264 | // Discard: the draft directory goes (the store keeps an undo copy). | ||
| 265 | Q_INVOKABLE void discard(); | ||
| 266 | |||
| 267 | // The message as it would leave now (Preview, outbox, server draft). | ||
| 268 | ComposeMessage message(ComposeMessage::Form form) const; | ||
| 269 | QJsonObject snapshot() const; | ||
| 270 | bool restore(const QJsonObject &draft); | ||
| 271 | // The new text only (no quote, no signature) — the attachment-word check. | ||
| 272 | QString newText(int limit = std::numeric_limits<int>::max()) const; // stops after about `limit` characters | ||
| 273 | // S108.9.5 templates: {subject, html ("" in plain mode), text} of the new | ||
| 274 | // text — without signature, quote and images (an image keeps its name). | ||
| 275 | Q_INVOKABLE QVariantMap templateContent() const; | ||
| 276 | // Inserts a template at the caret (its HTML in formatted mode, else its | ||
| 277 | // text); an empty subject takes the template's. | ||
| 278 | Q_INVOKABLE bool insertTemplate(const QVariantMap &content); | ||
| 279 | void markClean() { m_savedRevision = m_revision; emit saveStateChanged(); } | ||
| 280 | |||
| 281 | signals: | ||
| 282 | void placeChanged(); | ||
| 283 | void subjectChanged(); | ||
| 284 | void formattedChanged(); | ||
| 285 | void identityChanged(); | ||
| 286 | void identityChoicesChanged(); | ||
| 287 | void signatureChanged(); | ||
| 288 | void saveStateChanged(); | ||
| 289 | void checksChanged(); | ||
| 290 | void sendingChanged(); | ||
| 291 | void pastingChanged(); | ||
| 292 | // A paste in parts is in: the view puts the caret after it. | ||
| 293 | void caretRequested(int position); | ||
| 294 | void optionsChanged(); | ||
| 295 | void quoteChanged(); | ||
| 296 | void noticeChanged(); | ||
| 297 | void languageChanged(); | ||
| 298 | void meetingChanged(); | ||
| 299 | void formatHintChanged(); | ||
| 300 | // The composer leaves its place (sent, discarded or closed). | ||
| 301 | void finished(const QString &reason); // "sent", "scheduled", "discarded", "closed" | ||
| 302 | // A signature swap would replace an edited one: the view asks (D-16). | ||
| 303 | void signatureReplaceRequested(const QString &signatureId); | ||
| 304 | |||
| 305 | private: | ||
| 306 | QString m_id; | ||
| 307 | QString m_kind; | ||
| 308 | QString m_place = QStringLiteral("tab"); | ||
| 309 | Deps m_deps; | ||
| 310 | RecipientChipModel m_to; | ||
| 311 | RecipientChipModel m_cc; | ||
| 312 | RecipientChipModel m_bcc; | ||
| 313 | std::unique_ptr<ComposeAttachmentModel> m_attachments; | ||
| 314 | RichTextController m_editor; | ||
| 315 | QPointer<QTextDocument> m_document; | ||
| 316 | std::unique_ptr<QTextDocument> m_ownDocument; | ||
| 317 | QString m_subject; | ||
| 318 | bool m_formatted = false; | ||
| 319 | QString m_identityId; | ||
| 320 | QString m_signatureId; | ||
| 321 | QString m_placedSignatureText; | ||
| 322 | QString m_language; | ||
| 323 | int m_priority = 3; | ||
| 324 | bool m_readReceipt = false; | ||
| 325 | QString m_inReplyTo; | ||
| 326 | QStringList m_references; | ||
| 327 | QString m_originalSentTo; | ||
| 328 | QVariantMap m_mailReference; | ||
| 329 | Quote m_quote; | ||
| 330 | bool m_quoteInEditor = false; | ||
| 331 | bool m_quickStart = false; | ||
| 332 | QVariantMap m_meeting; | ||
| 333 | bool m_formatHint = false; | ||
| 334 | bool m_formatHintShown = false; // once per mail | ||
| 335 | QString m_pendingFormat; // the key pressed in plain text | ||
| 336 | int m_pendingFormatStart = 0; | ||
| 337 | int m_pendingFormatEnd = 0; | ||
| 338 | void showFormatHint(); | ||
| 339 | bool applyFormat(const QString &action); | ||
| 340 | QString m_spellLanguage; | ||
| 341 | QString m_guessedLanguage; // D-12: from the text (its first two words on) | ||
| 342 | QString m_guessedFrom; // the text (first 1 000 characters) the guess was made from | ||
| 343 | QString firstRecipient() const; | ||
| 344 | QSet<QString> m_ignoredWords; // lower case | ||
| 345 | QTimer m_typingPause; | ||
| 346 | void applySpellCheck(); | ||
| 347 | QString m_startText; // quick reply characters waiting for the editor | ||
| 348 | int m_startCaret = -1; | ||
| 349 | bool m_closed = false; // closed keeping the draft: not reopened after a restart | ||
| 350 | QJsonObject m_pendingRestore; // applied when the editor attaches | ||
| 351 | quint64 m_revision = 0; | ||
| 352 | quint64 m_savedRevision = 0; | ||
| 353 | quint64 m_serverRevision = 0; | ||
| 354 | QString m_saveState; | ||
| 355 | bool m_saveFailed = false; | ||
| 356 | QVariantList m_checks; | ||
| 357 | bool m_anyway = false; | ||
| 358 | bool m_sending = false; | ||
| 359 | QString m_notice; | ||
| 360 | QPointer<ComposeHighlighter> m_highlighter; | ||
| 361 | int insertHtml(const QByteArray &html); | ||
| 362 | // Paste and drop: HTML over ComposePasteJob::AsyncBytes goes in parts | ||
| 363 | // (returns -1 while pasting()); `text` is the fallback when the import | ||
| 364 | // refuses it. finishPaste inserts what is still waiting at once. | ||
| 365 | int insertPastedHtml(const QByteArray &html, const QString &text); | ||
| 366 | void adoptImported(const ComposeHtmlImport::Result &imported); | ||
| 367 | void finishPaste(); | ||
| 368 | ComposePasteJob *m_pasteJob = nullptr; | ||
| 369 | QString m_pasteText; | ||
| 370 | int insertImageData(const QString &name, const QByteArray &data, const QString &mimeType); | ||
| 371 | QTimer m_localTimer; | ||
| 372 | QTimer m_serverTimer; | ||
| 373 | QThreadPool m_savePool; | ||
| 374 | |||
| 375 | std::optional<Identity> currentIdentity() const; | ||
| 376 | QString submit(bool anyway, const QDateTime &due, bool scheduled); | ||
| 377 | void changed(); | ||
| 378 | void fieldsMayHaveChanged(); | ||
| 379 | QString m_fieldsKey; // recipients and files as last seen (fieldsMayHaveChanged) | ||
| 380 | qint64 m_serverDraftUid = 0; | ||
| 381 | quint32 m_serverDraftUidValidity = 0; | ||
| 382 | void saveLocalNow(); | ||
| 383 | bool saveLocalSync(); | ||
| 384 | void queueServerDraft(); | ||
| 385 | void setNotice(const QString ¬ice); | ||
| 386 | QString checkSentence(const QVariantMap &finding) const; | ||
| 387 | void placeSignature(const QString &signatureId); | ||
| 388 | void applyPendingRestore(); | ||
| 389 | void keepTextOf(QTextDocument *document); | ||
| 390 | QJsonObject snapshotText() const; | ||
| 391 | }; | ||
| 392 |