use std::sync::OnceLock;
use glib::{translate::*, GString};
use super::PtrHolder;
use crate::{prelude::*, subclass::prelude::*, EntryBuffer};
pub trait EntryBufferImpl: EntryBufferImplExt + ObjectImpl {
fn delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
self.parent_delete_text(position, n_chars)
}
fn deleted_text(&self, position: u32, n_chars: Option<u32>) {
self.parent_deleted_text(position, n_chars)
}
#[doc(alias = "get_length")]
fn length(&self) -> u32 {
self.parent_length()
}
#[doc(alias = "get_text")]
fn text(&self) -> GString {
self.parent_text()
}
fn insert_text(&self, position: u32, chars: &str) -> u32 {
self.parent_insert_text(position, chars)
}
fn inserted_text(&self, position: u32, chars: &str) {
self.parent_inserted_text(position, chars)
}
}
mod sealed {
pub trait Sealed {}
impl<T: super::EntryBufferImplExt> Sealed for T {}
}
pub trait EntryBufferImplExt: sealed::Sealed + ObjectSubclass {
fn parent_delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
let f = (*parent_class)
.delete_text
.expect("No parent class impl for \"delete_text\"");
f(
self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
position,
n_chars.unwrap_or(u32::MAX),
)
}
}
fn parent_deleted_text(&self, position: u32, n_chars: Option<u32>) {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
if let Some(f) = (*parent_class).deleted_text {
f(
self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
position,
n_chars.unwrap_or(u32::MAX),
)
}
}
}
fn parent_length(&self) -> u32 {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
let f = (*parent_class)
.get_length
.expect("No parent class impl for \"get_length\"");
f(self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0)
}
}
fn parent_text(&self) -> GString {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
let f = (*parent_class)
.get_text
.expect("No parent class impl for \"get_text\"");
let mut n_bytes = 0;
let res = f(
self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
&mut n_bytes,
);
FromGlibContainer::from_glib_none_num(res, n_bytes as _)
}
}
fn parent_insert_text(&self, position: u32, text: &str) -> u32 {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
let f = (*parent_class)
.insert_text
.expect("No parent class impl for \"insert_text\"");
f(
self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
position,
text.to_glib_none().0,
text.chars().count() as u32,
)
}
}
fn parent_inserted_text(&self, position: u32, text: &str) {
unsafe {
let data = Self::type_data();
let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
if let Some(f) = (*parent_class).inserted_text {
f(
self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
position,
text.to_glib_none().0,
text.chars().count() as u32,
)
}
}
}
}
impl<T: EntryBufferImpl> EntryBufferImplExt for T {}
unsafe impl<T: EntryBufferImpl> IsSubclassable<T> for EntryBuffer {
fn class_init(class: &mut glib::Class<Self>) {
Self::parent_class_init::<T>(class);
assert_initialized_main_thread!();
let klass = class.as_mut();
klass.delete_text = Some(entry_buffer_delete_text::<T>);
klass.deleted_text = Some(entry_buffer_deleted_text::<T>);
klass.get_length = Some(entry_buffer_get_length::<T>);
klass.get_text = Some(entry_buffer_get_text::<T>);
klass.insert_text = Some(entry_buffer_insert_text::<T>);
klass.inserted_text = Some(entry_buffer_inserted_text::<T>);
}
}
unsafe extern "C" fn entry_buffer_delete_text<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
position: u32,
n_chars: u32,
) -> u32 {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
let n_chars = if n_chars == u32::MAX {
None
} else {
Some(n_chars)
};
imp.delete_text(position, n_chars)
}
unsafe extern "C" fn entry_buffer_deleted_text<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
position: u32,
n_chars: u32,
) {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
let n_chars = if n_chars == u32::MAX {
None
} else {
Some(n_chars)
};
imp.deleted_text(position, n_chars)
}
unsafe extern "C" fn entry_buffer_get_text<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
n_bytes: *mut usize,
) -> *const libc::c_char {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
let ret = imp.text();
if !n_bytes.is_null() {
*n_bytes = ret.len();
}
static QUARK: OnceLock<glib::Quark> = OnceLock::new();
let quark = *QUARK.get_or_init(|| glib::Quark::from_str("gtk4-rs-subclass-entry-buffer-text"));
let fullptr = ret.into_glib_ptr();
imp.obj().set_qdata(
quark,
PtrHolder(fullptr, |ptr| {
glib::ffi::g_free(ptr as *mut _);
}),
);
fullptr
}
unsafe extern "C" fn entry_buffer_get_length<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
) -> u32 {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
imp.length()
}
unsafe extern "C" fn entry_buffer_insert_text<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
position: u32,
charsptr: *const libc::c_char,
n_chars: u32,
) -> u32 {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
let text: Borrowed<GString> = from_glib_borrow(charsptr);
let chars = text_n_chars(&text, n_chars);
imp.insert_text(position, chars)
}
unsafe extern "C" fn entry_buffer_inserted_text<T: EntryBufferImpl>(
ptr: *mut ffi::GtkEntryBuffer,
position: u32,
charsptr: *const libc::c_char,
length: u32,
) {
let instance = &*(ptr as *mut T::Instance);
let imp = instance.imp();
let text: Borrowed<GString> = from_glib_borrow(charsptr);
let chars = text_n_chars(&text, length);
imp.inserted_text(position, chars)
}
#[doc(alias = "get_text_n_chars")]
fn text_n_chars(text: &str, n_chars: u32) -> &str {
if n_chars != u32::MAX && n_chars > 0 {
let mut iter = text
.char_indices()
.skip((n_chars - 1) as _)
.map(|(pos, _)| pos);
iter
.next()
.expect(
"The passed text to EntryBuffer contains fewer characters than what's passed as a length",
);
let pos_end = iter.next().unwrap_or(text.len());
&text[..pos_end]
} else if n_chars == 0 {
""
} else {
text
}
}
#[cfg(test)]
mod test {
use super::text_n_chars;
#[std::prelude::v1::test]
fn n_chars_max_length_ascii() {
assert_eq!(text_n_chars("gtk-rs bindings", 6), "gtk-rs");
assert_eq!(text_n_chars("gtk-rs bindings", u32::MAX), "gtk-rs bindings");
}
#[std::prelude::v1::test]
#[should_panic]
fn n_chars_max_length_ascii_panic() {
assert_eq!(text_n_chars("gtk-rs", 7), "gtk-rs");
}
#[std::prelude::v1::test]
fn n_chars_max_length_utf8() {
assert_eq!(text_n_chars("👨👩👧👦", 2), "👨👩");
assert_eq!(text_n_chars("👨👩👧👦", 0), "");
assert_eq!(text_n_chars("👨👩👧👦", 4), "👨👩👧👦");
assert_eq!(text_n_chars("👨👩👧👦", u32::MAX), "👨👩👧👦");
assert_eq!(text_n_chars("كتاب", 2), "كت");
}
#[std::prelude::v1::test]
fn n_chars_max_length_utf8_ascii() {
assert_eq!(text_n_chars("👨g👩t👧k👦", 2), "👨g");
assert_eq!(text_n_chars("👨g👩t👧k👦", 5), "👨g👩t👧");
assert_eq!(text_n_chars("كaتاب", 3), "كaت");
}
#[std::prelude::v1::test]
#[should_panic]
fn n_chars_max_length_utf8_panic() {
assert_eq!(text_n_chars("👨👩👧👦", 5), "👨👩");
}
}