Skip to main content

common/
event.rs

1// Generated by Qleany v1.7.3 from common_event.tera
2
3use crate::types::EntityId;
4use flume::{Receiver, Sender, unbounded};
5use parking_lot::Mutex;
6use serde::Serialize;
7use std::{sync::Arc, thread};
8
9#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
10pub enum EntityEvent {
11    Created,
12    Updated,
13    Removed,
14}
15
16#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
17pub enum AllEvent {
18    Reset,
19}
20
21#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
22pub enum UndoRedoEvent {
23    Undone,
24    Redone,
25    /// A stack's history changed other than by undoing or redoing it — a
26    /// command pushed, a command merged into the one below it, a stack cleared.
27    ///
28    /// Without this a UI cannot know that "can undo" just became true: the
29    /// manager announced only the *consumption* of history, never its creation,
30    /// so an Undo menu row had nothing to react to and had to be polled.
31    /// Carries the stack id in `Event::data`, like every other variant here, so
32    /// a process holding several stacks can tell whose history moved.
33    StackChanged,
34    BeginComposite,
35    EndComposite,
36    CancelComposite,
37}
38
39#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
40pub enum LongOperationEvent {
41    Started,
42    Progress,
43    Cancelled,
44    Completed,
45    Failed,
46}
47
48#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
49pub enum DirectAccessEntity {
50    All(AllEvent),
51
52    Root(EntityEvent),
53    Document(EntityEvent),
54    Frame(EntityEvent),
55    Block(EntityEvent),
56    List(EntityEvent),
57    Resource(EntityEvent),
58    Table(EntityEvent),
59    TableCell(EntityEvent),
60}
61
62#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
63pub enum DocumentEditingEvent {
64    InsertText,
65    DeleteText,
66    InsertBlock,
67    InsertImage,
68    InsertFrame,
69    InsertFormattedText,
70    CreateList,
71    InsertList,
72    AddBlockToList,
73    RemoveBlockFromList,
74    InsertFragment,
75    InsertHtmlAtPosition,
76    InsertMarkdownAtPosition,
77    InsertDjotAtPosition,
78    InsertTable,
79    RemoveTable,
80    InsertTableRow,
81    InsertTableColumn,
82    RemoveTableRow,
83    RemoveTableColumn,
84    MergeTableCells,
85    SplitTableCell,
86    WrapBlocksInFrame,
87    UnwrapFrame,
88    UnwrapBlockFromFrame,
89}
90
91#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
92pub enum DocumentFormattingEvent {
93    SetTextFormat,
94    MergeTextFormat,
95    SetBlockFormat,
96    SetFrameFormat,
97    SetTableFormat,
98    SetTableCellFormat,
99    SetListFormat,
100}
101
102#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
103pub enum DocumentIoEvent {
104    ImportPlainText,
105    ExportPlainText,
106    ImportMarkdown,
107    ExportMarkdown,
108    ImportHtml,
109    ExportHtml,
110    ImportDjot,
111    ExportDjot,
112    ExportLatex,
113    ExportDocx,
114}
115
116#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
117pub enum DocumentSearchEvent {
118    FindText,
119    FindAll,
120    ReplaceText,
121    ReplaceRanges,
122    AddressableText,
123}
124
125#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
126pub enum DocumentInspectionEvent {
127    GetDocumentStats,
128    GetTextAtPosition,
129    GetBlockAtPosition,
130    ExtractFragment,
131}
132
133#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
134pub enum Origin {
135    DirectAccess(DirectAccessEntity),
136    UndoRedo(UndoRedoEvent),
137    LongOperation(LongOperationEvent),
138
139    DocumentEditing(DocumentEditingEvent),
140    DocumentFormatting(DocumentFormattingEvent),
141    DocumentIo(DocumentIoEvent),
142    DocumentSearch(DocumentSearchEvent),
143    DocumentInspection(DocumentInspectionEvent),
144}
145
146#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
147pub struct Event {
148    pub origin: Origin,
149    pub ids: Vec<EntityId>,
150    pub data: Option<String>,
151}
152
153impl Event {
154    pub fn origin_string(&self) -> String {
155        match &self.origin {
156            Origin::DirectAccess(entity) => match entity {
157                DirectAccessEntity::All(event) => format!("direct_access_all_{:?}", event),
158                // entities
159                DirectAccessEntity::Root(event) => format!("direct_access_root_{:?}", event),
160                DirectAccessEntity::Document(event) => {
161                    format!("direct_access_document_{:?}", event)
162                }
163                DirectAccessEntity::Frame(event) => format!("direct_access_frame_{:?}", event),
164                DirectAccessEntity::Block(event) => format!("direct_access_block_{:?}", event),
165                DirectAccessEntity::List(event) => format!("direct_access_list_{:?}", event),
166                DirectAccessEntity::Resource(event) => {
167                    format!("direct_access_resource_{:?}", event)
168                }
169                DirectAccessEntity::Table(event) => format!("direct_access_table_{:?}", event),
170                DirectAccessEntity::TableCell(event) => {
171                    format!("direct_access_table_cell_{:?}", event)
172                }
173            },
174            Origin::UndoRedo(event) => format!("undo_redo_{:?}", event),
175            Origin::LongOperation(event) => format!("long_operation_{:?}", event),
176            // features
177            Origin::DocumentEditing(event) => format!("document_editing_{:?}", event),
178            Origin::DocumentFormatting(event) => format!("document_formatting_{:?}", event),
179            Origin::DocumentIo(event) => format!("document_io_{:?}", event),
180            Origin::DocumentSearch(event) => format!("document_search_{:?}", event),
181            Origin::DocumentInspection(event) => format!("document_inspection_{:?}", event),
182        }
183        .to_lowercase()
184    }
185}
186/// Thread-safe event buffer for deferring event emissions during transactions.
187///
188/// Repositories push events into this buffer instead of sending them directly
189/// to the EventHub. On commit(), the UoW drains the buffer and sends all events.
190/// On rollback(), the buffer is discarded. This prevents the UI from seeing
191/// phantom state from failed transactions.
192///
193/// This is the Rust equivalent of SignalBuffer in the C++/Qt target.
194pub struct EventBuffer {
195    buffering: bool,
196    pending: Vec<Event>,
197}
198
199impl EventBuffer {
200    pub fn new() -> Self {
201        Self {
202            buffering: false,
203            pending: Vec::new(),
204        }
205    }
206
207    /// Start buffering. Clears any stale events from a previous cycle.
208    pub fn begin_buffering(&mut self) {
209        self.buffering = true;
210        self.pending.clear();
211    }
212
213    /// Queue an event for deferred delivery.
214    ///
215    /// If buffering is not active, the event is silently dropped.
216    /// (Callers should only push during an active transaction.)
217    pub fn push(&mut self, event: Event) {
218        if self.buffering {
219            self.pending.push(event);
220        }
221    }
222
223    /// Drain all pending events and stop buffering.
224    /// The caller is responsible for sending them to the EventHub.
225    pub fn flush(&mut self) -> Vec<Event> {
226        self.buffering = false;
227        std::mem::take(&mut self.pending)
228    }
229
230    /// Discard all pending events and stop buffering.
231    pub fn discard(&mut self) {
232        self.buffering = false;
233        self.pending.clear();
234    }
235
236    pub fn is_buffering(&self) -> bool {
237        self.buffering
238    }
239}
240
241impl Default for EventBuffer {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247pub type Queue = Arc<Mutex<Vec<Event>>>;
248
249/// Central event hub for managing subscriptions and dispatching events
250#[derive(Debug)]
251pub struct EventHub {
252    sender: Sender<Event>,
253    receiver: Receiver<Event>,
254    queue: Queue,
255}
256
257impl Default for EventHub {
258    fn default() -> Self {
259        Self::new()
260    }
261}
262
263impl EventHub {
264    /// Create a new event hub
265    pub fn new() -> Self {
266        let (sender, receiver) = unbounded();
267        EventHub {
268            sender,
269            receiver,
270            queue: Arc::new(Mutex::new(Vec::new())),
271        }
272    }
273
274    /// Start the event processing loop.
275    ///
276    /// Returns a `JoinHandle` so the caller can join the thread on
277    /// shutdown. The thread blocks on a `flume::Selector` that wakes
278    /// only when an event arrives or `shutdown_rx` disconnects — zero
279    /// CPU while idle. Drop the matching `Sender<()>` to stop the
280    /// thread.
281    pub fn start_event_loop(&self, shutdown_rx: Receiver<()>) -> thread::JoinHandle<()> {
282        let receiver = self.receiver.clone();
283        let queue = self.queue.clone();
284        thread::spawn(move || {
285            loop {
286                let outcome: Result<Option<Event>, ()> = flume::Selector::new()
287                    .recv(&receiver, |r| r.map(Some).map_err(|_| ()))
288                    .recv(&shutdown_rx, |_| Ok(None))
289                    .wait();
290                match outcome {
291                    Ok(Some(event)) => {
292                        let mut queue = queue.lock();
293                        queue.push(event);
294                    }
295                    Ok(None) | Err(()) => break,
296                }
297            }
298        })
299    }
300
301    /// Send an event to the queue
302    pub fn send_event(&self, event: Event) {
303        if let Err(e) = self.sender.send(event) {
304            eprintln!("EventHub: failed to send event (receiver dropped): {e}");
305        }
306    }
307
308    pub fn get_queue(&self) -> Queue {
309        self.queue.clone()
310    }
311
312    /// Get a direct event receiver.
313    ///
314    /// The receiver blocks on `recv()` until an event arrives — no polling needed.
315    /// **Important**: flume uses MPMC semantics — each event is delivered to exactly
316    /// one receiver. Multiple cloned receivers compete for events rather than each
317    /// receiving a copy. Ensure only one consumer calls `subscribe_receiver()`.
318    pub fn subscribe_receiver(&self) -> Receiver<Event> {
319        self.receiver.clone()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_event_hub_send_and_receive() {
329        let event_hub = EventHub::new();
330        let (shutdown_tx, shutdown_rx) = flume::bounded::<()>(1);
331        let handle = event_hub.start_event_loop(shutdown_rx);
332
333        let event = Event {
334            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
335            ids: vec![EntityId::default()],
336            data: Some("test_data".to_string()),
337        };
338
339        event_hub.send_event(event.clone());
340
341        thread::sleep(std::time::Duration::from_millis(100));
342
343        let queue = event_hub.get_queue();
344        let queue = queue.lock();
345        assert_eq!(queue.len(), 1);
346        assert_eq!(queue[0], event);
347
348        // Drop the sender to wake the loop thread and exit cleanly.
349        drop(shutdown_tx);
350        handle.join().unwrap();
351    }
352}