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}