/* * bz-search-bar.c * * Copyright 2026 Zelda Ahmed * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "bz-search-bar.h" struct _BzSearchBar { GtkWidget parent_instance; GtkImage *search_icon; GtkText *search_text; AdwSpinner *search_busy; GtkButton *clear_button; AdwBin *end_widget_bin; gboolean busy; gchar *placeholder; }; static void bz_search_bar_accessible_init (GtkAccessibleInterface *iface); static void bz_search_bar_editable_init (GtkEditableInterface *iface); G_DEFINE_FINAL_TYPE_WITH_CODE (BzSearchBar, bz_search_bar, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, bz_search_bar_editable_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, bz_search_bar_accessible_init)) enum { PROP_0, PROP_BUSY, PROP_PLACEHOLDER, PROP_END_WIDGET, N_PROPS }; enum { SIGNAL_SEARCH_ACTIVATED, SIGNAL_SEARCH_CHANGED, N_SIGNALS }; static guint signals[N_SIGNALS]; static GParamSpec *properties[N_PROPS]; static void text_activated_cb (BzSearchBar *self, GtkText *text) { g_signal_emit (self, signals[SIGNAL_SEARCH_ACTIVATED], 0); } static void text_changed_cb (BzSearchBar *self, GtkText *text) { g_signal_emit (self, signals[SIGNAL_SEARCH_CHANGED], 0); } static gboolean has_text_cb (BzSearchBar *self, GtkButton *clear_button, const gchar *value) { const gchar *text; if (!self->search_text) return FALSE; text = gtk_editable_get_text (GTK_EDITABLE (self->search_text)); return strlen (text) != 0; } static void clear_text_cb (BzSearchBar *self, GtkButton *clear_button) { gtk_editable_set_text (GTK_EDITABLE (self->search_text), ""); } static gboolean bz_search_bar_grab_focus (GtkWidget *widget) { BzSearchBar *self = BZ_SEARCH_BAR (widget); return gtk_widget_grab_focus (GTK_WIDGET (self->search_text)); } static gboolean bz_search_bar_focus (GtkWidget *widget, GtkDirectionType direction) { BzSearchBar *self = BZ_SEARCH_BAR (widget); return gtk_widget_child_focus (GTK_WIDGET (self->search_text), direction); } static void end_widget_focus_enter_cb (BzSearchBar *self) { gtk_widget_add_css_class (GTK_WIDGET (self), "hide-focus"); } static void end_widget_focus_leave_cb (BzSearchBar *self) { gtk_widget_remove_css_class (GTK_WIDGET (self), "hide-focus"); } BzSearchBar * bz_search_bar_new (void) { return g_object_new (BZ_TYPE_SEARCH_BAR, NULL); } static void bz_search_bar_dispose (GObject *object) { BzSearchBar *self = BZ_SEARCH_BAR (object); gtk_editable_finish_delegate (GTK_EDITABLE (self)); gtk_widget_dispose_template (GTK_WIDGET (self), BZ_TYPE_SEARCH_BAR); G_OBJECT_CLASS (bz_search_bar_parent_class)->dispose (object); } static void bz_search_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BzSearchBar *self = BZ_SEARCH_BAR (object); if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) return; switch (prop_id) { case PROP_BUSY: g_value_set_boolean (value, bz_search_bar_get_busy (self)); break; case PROP_END_WIDGET: g_value_set_object (value, bz_search_bar_get_end_widget (self)); break; case PROP_PLACEHOLDER: g_value_set_string (value, bz_search_bar_get_placeholder (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bz_search_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BzSearchBar *self = BZ_SEARCH_BAR (object); if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) { if (prop_id == N_PROPS + GTK_EDITABLE_PROP_EDITABLE) { gtk_accessible_update_property (GTK_ACCESSIBLE (self->search_text), GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !g_value_get_boolean (value), -1); } return; } switch (prop_id) { case PROP_BUSY: bz_search_bar_set_busy (self, g_value_get_boolean (value)); break; case PROP_END_WIDGET: bz_search_bar_set_end_widget (self, GTK_WIDGET (g_value_get_object (value))); break; case PROP_PLACEHOLDER: bz_search_bar_set_placeholder (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bz_search_bar_class_init (BzSearchBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-search-bar.ui"); object_class->dispose = bz_search_bar_dispose; object_class->get_property = bz_search_bar_get_property; object_class->set_property = bz_search_bar_set_property; widget_class->grab_focus = bz_search_bar_grab_focus; widget_class->focus = bz_search_bar_focus; properties[PROP_BUSY] = g_param_spec_boolean ("busy", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_END_WIDGET] = g_param_spec_object ("end-widget", NULL, NULL, GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_PLACEHOLDER] = g_param_spec_string ("placeholder", NULL, NULL, "", G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); signals[SIGNAL_SEARCH_ACTIVATED] = g_signal_new ( "search-activated", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[SIGNAL_SEARCH_CHANGED] = g_signal_new ( "search-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); g_object_class_install_properties (object_class, N_PROPS, properties); gtk_editable_install_properties (object_class, N_PROPS); gtk_widget_class_bind_template_child (widget_class, BzSearchBar, search_icon); gtk_widget_class_bind_template_child (widget_class, BzSearchBar, search_text); gtk_widget_class_bind_template_child (widget_class, BzSearchBar, search_busy); gtk_widget_class_bind_template_child (widget_class, BzSearchBar, clear_button); gtk_widget_class_bind_template_child (widget_class, BzSearchBar, end_widget_bin); gtk_widget_class_bind_template_callback (widget_class, text_activated_cb); gtk_widget_class_bind_template_callback (widget_class, text_changed_cb); gtk_widget_class_bind_template_callback (widget_class, has_text_cb); gtk_widget_class_bind_template_callback (widget_class, clear_text_cb); gtk_widget_class_bind_template_callback (widget_class, end_widget_focus_enter_cb); gtk_widget_class_bind_template_callback (widget_class, end_widget_focus_leave_cb); } static GtkEditable * bz_search_bar_get_delegate (GtkEditable *editable) { return GTK_EDITABLE (BZ_SEARCH_BAR (editable)->search_text); } static void bz_search_bar_editable_init (GtkEditableInterface *iface) { iface->get_delegate = bz_search_bar_get_delegate; } static gboolean bz_search_bar_accessible_get_platform_state (GtkAccessible *self, GtkAccessiblePlatformState state) { return gtk_editable_delegate_get_accessible_platform_state (GTK_EDITABLE (self), state); } static void bz_search_bar_accessible_init (GtkAccessibleInterface *iface) { GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface); iface->get_at_context = parent_iface->get_at_context; iface->get_platform_state = bz_search_bar_accessible_get_platform_state; } static void bz_search_bar_init (BzSearchBar *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_editable_init_delegate (GTK_EDITABLE (self)); } gboolean bz_search_bar_get_busy (BzSearchBar *self) { g_return_val_if_fail (BZ_IS_SEARCH_BAR (self), FALSE); return self->busy; } void bz_search_bar_set_busy (BzSearchBar *self, gboolean busy) { g_return_if_fail (BZ_IS_SEARCH_BAR (self)); if (self->busy == busy) return; self->busy = busy; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BUSY]); } GtkWidget * bz_search_bar_get_end_widget (BzSearchBar *self) { g_return_val_if_fail (BZ_IS_SEARCH_BAR (self), NULL); return adw_bin_get_child (self->end_widget_bin); } void bz_search_bar_set_end_widget (BzSearchBar *self, GtkWidget *widget) { GtkWidget *end_widget; g_return_if_fail (BZ_IS_SEARCH_BAR (self)); g_return_if_fail (!widget || GTK_IS_WIDGET (widget)); end_widget = adw_bin_get_child (self->end_widget_bin); if (end_widget == widget) return; adw_bin_set_child (self->end_widget_bin, widget); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_END_WIDGET]); } const gchar * bz_search_bar_get_placeholder (BzSearchBar *self) { g_return_val_if_fail (BZ_IS_SEARCH_BAR (self), NULL); if (!self->search_text) return NULL; return gtk_text_get_placeholder_text (self->search_text); } void bz_search_bar_set_placeholder (BzSearchBar *self, const gchar *text) { g_return_if_fail (BZ_IS_SEARCH_BAR (self)); if (g_strcmp0 (bz_search_bar_get_placeholder (self), text) == 0) return; gtk_text_set_placeholder_text (self->search_text, text); gtk_accessible_update_property (GTK_ACCESSIBLE (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER, text, -1); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLACEHOLDER]); }