Skip to main content

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