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