gtk4/
text_buffer.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{boxed::Box as Box_, mem::transmute, slice, str};
4
5use glib::{
6    signal::{connect_raw, SignalHandlerId},
7    translate::*,
8};
9use libc::{c_char, c_int};
10
11#[cfg(feature = "v4_16")]
12use crate::TextBufferNotifyFlags;
13use crate::{ffi, prelude::*, TextBuffer, TextIter, TextTag};
14
15mod sealed {
16    pub trait Sealed {}
17    impl<T: super::IsA<super::TextBuffer>> Sealed for T {}
18}
19
20// rustdoc-stripper-ignore-next
21/// Trait containing manually implemented methods of
22/// [`TextBuffer`](crate::TextBuffer).
23pub trait TextBufferExtManual: sealed::Sealed + IsA<TextBuffer> + 'static {
24    // rustdoc-stripper-ignore-next
25    /// # Panics
26    ///
27    /// If the properties don't exists or are not writeable.
28    // rustdoc-stripper-ignore-next-stop
29    /// Creates a tag and adds it to the tag table for @self.
30    ///
31    /// Equivalent to calling [`TextTag::new()`][crate::TextTag::new()] and then adding the
32    /// tag to the buffer’s tag table. The returned tag is owned by
33    /// the buffer’s tag table, so the ref count will be equal to one.
34    ///
35    /// If @tag_name is [`None`], the tag is anonymous.
36    ///
37    /// If @tag_name is non-[`None`], a tag called @tag_name must not already
38    /// exist in the tag table for this buffer.
39    ///
40    /// The @first_property_name argument and subsequent arguments are a list
41    /// of properties to set on the tag, as with g_object_set().
42    /// ## `tag_name`
43    /// name of the new tag
44    /// ## `first_property_name`
45    /// name of first property to set
46    ///
47    /// # Returns
48    ///
49    /// a new tag
50    #[doc(alias = "gtk_text_buffer_create_tag")]
51    fn create_tag(
52        &self,
53        tag_name: Option<&str>,
54        properties: &[(&str, &dyn ToValue)],
55    ) -> Option<TextTag> {
56        let tag = TextTag::new(tag_name);
57        tag.set_properties(properties);
58        if self.as_ref().tag_table().add(&tag) {
59            Some(tag)
60        } else {
61            None
62        }
63    }
64
65    /// Inserts @text into @self at @iter, applying the list of tags to
66    /// the newly-inserted text.
67    ///
68    /// The last tag specified must be [`None`] to terminate the list.
69    /// Equivalent to calling [`TextBufferExt::insert()`][crate::prelude::TextBufferExt::insert()],
70    /// then [`TextBufferExt::apply_tag()`][crate::prelude::TextBufferExt::apply_tag()] on the inserted text;
71    /// this is just a convenience function.
72    /// ## `iter`
73    /// an iterator in @self
74    /// ## `text`
75    /// UTF-8 text
76    /// ## `len`
77    /// length of @text, or -1
78    /// ## `first_tag`
79    /// first tag to apply to @text
80    #[doc(alias = "gtk_text_buffer_insert_with_tags")]
81    fn insert_with_tags(&self, iter: &mut TextIter, text: &str, tags: &[&TextTag]) {
82        let start_offset = iter.offset();
83        self.as_ref().insert(iter, text);
84        let start_iter = self.as_ref().iter_at_offset(start_offset);
85        tags.iter().for_each(|tag| {
86            self.as_ref().apply_tag(&(*tag).clone(), &start_iter, iter);
87        });
88    }
89
90    /// Inserts @text into @self at @iter, applying the list of tags to
91    /// the newly-inserted text.
92    ///
93    /// Same as [`insert_with_tags()`][Self::insert_with_tags()], but allows you
94    /// to pass in tag names instead of tag objects.
95    /// ## `iter`
96    /// position in @self
97    /// ## `text`
98    /// UTF-8 text
99    /// ## `len`
100    /// length of @text, or -1
101    /// ## `first_tag_name`
102    /// name of a tag to apply to @text
103    #[doc(alias = "gtk_text_buffer_insert_with_tags_by_name")]
104    fn insert_with_tags_by_name(&self, iter: &mut TextIter, text: &str, tags_names: &[&str]) {
105        let start_offset = iter.offset();
106        self.as_ref().insert(iter, text);
107        let start_iter = self.as_ref().iter_at_offset(start_offset);
108        let tag_table = self.as_ref().tag_table();
109        tags_names.iter().for_each(|tag_name| {
110            if let Some(tag) = tag_table.lookup(tag_name) {
111                self.as_ref().apply_tag(&tag, &start_iter, iter);
112            } else {
113                glib::g_warning!("TextBuffer", "No tag with name {}!", tag_name);
114            }
115        });
116    }
117
118    /// Emitted to insert text in a [`TextBuffer`][crate::TextBuffer].
119    ///
120    /// Insertion actually occurs in the default handler.
121    ///
122    /// Note that if your handler runs before the default handler
123    /// it must not invalidate the @location iter (or has to
124    /// revalidate it). The default signal handler revalidates
125    /// it to point to the end of the inserted text.
126    ///
127    /// See also: [`TextBufferExt::insert()`][crate::prelude::TextBufferExt::insert()],
128    /// [`TextBufferExt::insert_range()`][crate::prelude::TextBufferExt::insert_range()].
129    /// ## `location`
130    /// position to insert @text in @textbuffer
131    /// ## `text`
132    /// the UTF-8 text to be inserted
133    /// ## `len`
134    /// length of the inserted text in bytes
135    fn connect_insert_text<F: Fn(&Self, &mut TextIter, &str) + 'static>(
136        &self,
137        f: F,
138    ) -> SignalHandlerId {
139        unsafe {
140            unsafe extern "C" fn insert_text_trampoline<
141                T,
142                F: Fn(&T, &mut TextIter, &str) + 'static,
143            >(
144                this: *mut ffi::GtkTextBuffer,
145                location: *mut ffi::GtkTextIter,
146                text: *mut c_char,
147                len: c_int,
148                f: glib::ffi::gpointer,
149            ) where
150                T: IsA<TextBuffer>,
151            {
152                let mut location_copy = from_glib_none(location);
153                let f: &F = &*(f as *const F);
154                let text = if len <= 0 {
155                    &[]
156                } else {
157                    slice::from_raw_parts(text as *const u8, len as usize)
158                };
159
160                f(
161                    TextBuffer::from_glib_borrow(this).unsafe_cast_ref(),
162                    &mut location_copy,
163                    str::from_utf8(text).unwrap(),
164                )
165            }
166            let f: Box_<F> = Box_::new(f);
167            connect_raw(
168                self.to_glib_none().0 as *mut _,
169                b"insert-text\0".as_ptr() as *mut _,
170                Some(transmute::<usize, unsafe extern "C" fn()>(
171                    insert_text_trampoline::<Self, F> as usize,
172                )),
173                Box_::into_raw(f),
174            )
175        }
176    }
177
178    /// Adds a `callback::Gtk::TextBufferCommitNotify to be called when a change
179    /// is to be made to the [type@Gtk.TextBuffer].
180    ///
181    /// Functions are explicitly forbidden from making changes to the
182    /// [type@Gtk.TextBuffer] from this callback. It is intended for tracking
183    /// changes to the buffer only.
184    ///
185    /// It may be advantageous to use `callback::Gtk::TextBufferCommitNotify over
186    /// connecting to the [`insert-text`][struct@crate::TextBuffer#insert-text] or
187    /// [`delete-range`][struct@crate::TextBuffer#delete-range] signals to avoid ordering issues with
188    /// other signal handlers which may further modify the [type@Gtk.TextBuffer].
189    /// ## `flags`
190    /// which notifications should be dispatched to @callback
191    /// ## `commit_notify`
192    /// a
193    ///   `callback::Gtk::TextBufferCommitNotify to call for commit notifications
194    ///
195    /// # Returns
196    ///
197    /// a handler id which may be used to remove the commit notify
198    ///   callback using [`TextBufferExt::remove_commit_notify()`][crate::prelude::TextBufferExt::remove_commit_notify()].
199    #[cfg(feature = "v4_16")]
200    #[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
201    #[doc(alias = "gtk_text_buffer_add_commit_notify")]
202    fn add_commit_notify<P: Fn(&TextBuffer, TextBufferNotifyFlags, u32, u32) + 'static>(
203        &self,
204        flags: TextBufferNotifyFlags,
205        commit_notify: P,
206    ) -> u32 {
207        let commit_notify_data: Box_<P> = Box_::new(commit_notify);
208        unsafe extern "C" fn commit_notify_func<
209            P: Fn(&TextBuffer, TextBufferNotifyFlags, u32, u32) + 'static,
210        >(
211            buffer: *mut ffi::GtkTextBuffer,
212            flags: ffi::GtkTextBufferNotifyFlags,
213            position: std::ffi::c_uint,
214            length: std::ffi::c_uint,
215            user_data: glib::ffi::gpointer,
216        ) {
217            let buffer = from_glib_borrow(buffer);
218            let flags = from_glib(flags);
219            let callback = &*(user_data as *mut P);
220            (*callback)(&buffer, flags, position, length)
221        }
222        let commit_notify = Some(commit_notify_func::<P> as _);
223        unsafe extern "C" fn destroy_func<
224            P: Fn(&TextBuffer, TextBufferNotifyFlags, u32, u32) + 'static,
225        >(
226            data: glib::ffi::gpointer,
227        ) {
228            let _callback = Box_::from_raw(data as *mut P);
229        }
230        let destroy_call4 = Some(destroy_func::<P> as _);
231        let super_callback0: Box_<P> = commit_notify_data;
232        unsafe {
233            ffi::gtk_text_buffer_add_commit_notify(
234                self.as_ref().to_glib_none().0,
235                flags.into_glib(),
236                commit_notify,
237                Box_::into_raw(super_callback0) as *mut _,
238                destroy_call4,
239            )
240        }
241    }
242}
243
244impl<O: IsA<TextBuffer>> TextBufferExtManual for O {}
245
246impl std::fmt::Write for TextBuffer {
247    fn write_str(&mut self, s: &str) -> std::fmt::Result {
248        let mut iter = self.end_iter();
249        self.insert(&mut iter, s);
250        Ok(())
251    }
252}