presentation/ComposePasteJob.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "data/ComposeHtmlImport.h" | ||
| 4 | |||
| 5 | #include <QFutureWatcher> | ||
| 6 | #include <QList> | ||
| 7 | #include <QObject> | ||
| 8 | #include <QPointer> | ||
| 9 | #include <QTextCursor> | ||
| 10 | #include <QTextDocumentFragment> | ||
| 11 | #include <atomic> | ||
| 12 | #include <memory> | ||
| 13 | |||
| 14 | class RichTextController; | ||
| 15 | |||
| 16 | // Sprint 108 S108.10.4 (G11): a large HTML paste or drop into a formatted | ||
| 17 | // message without a frozen window. The import (ComposeHtmlImport, the | ||
| 18 | // bounded parser of untrusted HTML) and the split into parts run on a | ||
| 19 | // worker; the GUI thread then inserts one part per turn of the event loop, | ||
| 20 | // so frames keep coming. Parts end only at top-level paragraph boundaries | ||
| 21 | // outside tables and lists, each with the paragraph break after it, so the | ||
| 22 | // result equals one whole insertion (test_compose_paste). The first part | ||
| 23 | // replaces the selection and opens the undo step; later parts join it — the | ||
| 24 | // paste is one undo step. ComposeSession keeps the editor read-only while a | ||
| 25 | // job runs and finishes it at once before a format switch or a send. | ||
| 26 | class ComposePasteJob : public QObject { | ||
| 27 | Q_OBJECT | ||
| 28 | public: | ||
| 29 | // HTML up to this size is inserted at once (the stall stays small). | ||
| 30 | static constexpr qsizetype AsyncBytes = 64 * 1024; | ||
| 31 | // About this many characters per part (one part ≈ 1–3 ms on the GUI | ||
| 32 | // thread); parts go in until a turn of the event loop has used | ||
| 33 | // TurnBudgetMs, then a frame comes before the next ones. | ||
| 34 | static constexpr int PartCharacters = 4 * 1024; | ||
| 35 | static constexpr int TurnBudgetMs = 20; | ||
| 36 | |||
| 37 | struct Prepared { | ||
| 38 | ComposeHtmlImport::Result imported; // outcome, images and notices; `fragment` is split into `parts` | ||
| 39 | QList<QTextDocumentFragment> parts; | ||
| 40 | }; | ||
| 41 | // The worker half: import, then split. Any thread; `cancelled` stops early. | ||
| 42 | static Prepared prepare(const QByteArray &html, const ComposeHtmlImport::Options &options, | ||
| 43 | const std::atomic_bool *cancelled = nullptr, int partCharacters = PartCharacters); | ||
| 44 | |||
| 45 | explicit ComposePasteJob(RichTextController &editor, QObject *parent = nullptr); | ||
| 46 | ~ComposePasteJob() override; | ||
| 47 | |||
| 48 | // Starts the import of `html` for the editor's current caret or selection. | ||
| 49 | void start(const QByteArray &html, const ComposeHtmlImport::Options &options); | ||
| 50 | // Inserts every part still waiting right now (blocking for a running | ||
| 51 | // import). Used before anything reads or converts the whole document. | ||
| 52 | void finishNow(); | ||
| 53 | 21 | bool running() const { return m_running; } | |
| 54 | |||
| 55 | signals: | ||
| 56 | // Before the first part goes in: the caller registers the images and | ||
| 57 | // tells about remote images left out. | ||
| 58 | void imported(const ComposeHtmlImport::Result &imported); | ||
| 59 | // The last part is in, `caret` after it (-1 when the editor's document | ||
| 60 | // went away meanwhile). | ||
| 61 | void finished(int caret); | ||
| 62 | // The import refused the HTML: the caller inserts the clipboard text. | ||
| 63 | void failed(); | ||
| 64 | |||
| 65 | private: | ||
| 66 | void onPrepared(); | ||
| 67 | void insertNext(); | ||
| 68 | void finish(int caret); | ||
| 69 | |||
| 70 | RichTextController &m_editor; | ||
| 71 | QFutureWatcher<Prepared> m_watcher; | ||
| 72 | std::shared_ptr<std::atomic_bool> m_cancelled; | ||
| 73 | QList<QTextDocumentFragment> m_parts; | ||
| 74 | QTextCursor m_at; | ||
| 75 | int m_inserted = 0; | ||
| 76 | bool m_running = false; | ||
| 77 | bool m_prepared = false; // onPrepared ran for the current job | ||
| 78 | qint64 m_startedMs = 0; | ||
| 79 | double m_longestPartMs = 0; | ||
| 80 | }; | ||
| 81 |