diff --git a/src/bazaar.gresource.xml b/src/bazaar.gresource.xml
index 60a0a3b2..8e1fdd2c 100644
--- a/src/bazaar.gresource.xml
+++ b/src/bazaar.gresource.xml
@@ -60,6 +60,7 @@
bz-screenshot-page.ui
bz-screenshots-carousel.ui
bz-search-filter-popover.ui
+ bz-search-bar.ui
bz-search-page.ui
bz-section-view.ui
bz-stats-dialog.ui
diff --git a/src/bz-library-page.blp b/src/bz-library-page.blp
index 7c6e2f59..4a5152ac 100644
--- a/src/bz-library-page.blp
+++ b/src/bz-library-page.blp
@@ -8,53 +8,15 @@ template $BzLibraryPage: Adw.Bin {
Adw.Clamp search_box_clamp {
margin-start: 12;
margin-end: 12;
+ margin-top: 10;
+ margin-bottom: 10;
maximum-size: 375;
-
visible: bind template.has-apps;
- child: Adw.Bin {
- halign: fill;
- margin-top: 10;
- margin-bottom: 10;
-
- child: Box {
- orientation: horizontal;
- spacing: 8;
- height-request: 40;
-
- Image {
- icon-name: "system-search-symbolic";
- }
-
- Text search_bar {
- hexpand: true;
- max-length: 50;
- placeholder-text: _("Search installed apps");
- notify::text => $search_text_changed(template);
- activate => $search_text_activate(template);
- }
-
- Button clear_button {
- icon-name: "edit-clear-symbolic";
- visible: bind $not($is_empty_string(search_bar.text) as ) as ;
-
- styles [
- "flat",
- "circular",
- "searchbar-button",
- ]
-
- clicked => $reset_search_cb(template);
-
- accessibility {
- label: _("Clear search");
- }
- }
-
- styles [
- "search-box",
- ]
- };
+ child: $BzSearchBar search_bar {
+ placeholder: _("Search installed apps");
+ search-activated => $search_text_activate(template);
+ search-changed => $search_text_changed(template);
};
}
diff --git a/src/bz-library-page.c b/src/bz-library-page.c
index fa1d9e34..80ca3fe1 100644
--- a/src/bz-library-page.c
+++ b/src/bz-library-page.c
@@ -23,6 +23,7 @@
#include "bz-entry-group.h"
#include "bz-installed-tile.h"
#include "bz-library-page.h"
+#include "bz-search-bar.h"
#include "bz-section-view.h"
#include "bz-template-callbacks.h"
#include "bz-transaction-tile.h"
@@ -39,7 +40,7 @@ struct _BzLibraryPage
/* Template widgets */
AdwViewStack *stack;
- GtkText *search_bar;
+ BzSearchBar *search_bar;
GtkScrolledWindow *scroll;
GtkFilterListModel *filter_model;
GtkCustomFilter *filter;
@@ -248,8 +249,7 @@ tile_activated_cb (BzListTile *tile)
static void
search_text_changed (BzLibraryPage *self,
- GParamSpec *pspec,
- GtkText *entry)
+ BzSearchBar *search_bar)
{
gtk_filter_changed (GTK_FILTER (self->filter),
GTK_FILTER_CHANGE_DIFFERENT);
@@ -258,7 +258,7 @@ search_text_changed (BzLibraryPage *self,
static void
search_text_activate (BzLibraryPage *self,
- GtkText *entry)
+ BzSearchBar *search_bar)
{
const char *text = NULL;
@@ -273,7 +273,7 @@ static void
reset_search_cb (BzLibraryPage *self,
GtkButton *button)
{
- gtk_text_set_buffer (self->search_bar, NULL);
+ gtk_editable_set_text (GTK_EDITABLE (self->search_bar), "");
}
static void
@@ -567,7 +567,7 @@ bz_library_page_reset_search (BzLibraryPage *self)
vadjustment = gtk_scrolled_window_get_vadjustment (self->scroll);
gtk_adjustment_set_value (vadjustment, 0.0);
- gtk_text_set_buffer (self->search_bar, NULL);
+ gtk_editable_set_text (GTK_EDITABLE (self->search_bar), "");
}
static void
@@ -592,9 +592,13 @@ has_transactions_changed (BzLibraryPage *self,
static void
set_page (BzLibraryPage *self)
{
- guint n_apps = 0;
- guint n_filtered = 0;
- gboolean has_transactions = FALSE;
+ guint n_apps = 0;
+ guint n_filtered = 0;
+ gboolean has_transactions = FALSE;
+ const char *message = NULL;
+ const char *search_text = NULL;
+
+ search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_bar));
if (self->model != NULL)
{
@@ -619,6 +623,13 @@ set_page (BzLibraryPage *self)
adw_view_stack_set_visible_child_name (self->stack, "no-results");
else
adw_view_stack_set_visible_child_name (self->stack, "content");
+
+ if (search_text && *search_text)
+ {
+ message = n_filtered == 0 ? _ ("No applications found") : g_strdup_printf (ngettext ("One application found", "%u applications found", n_filtered), n_filtered);
+
+ gtk_accessible_announce (GTK_ACCESSIBLE (self), message, GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH);
+ }
}
static gboolean
diff --git a/src/bz-search-bar.blp b/src/bz-search-bar.blp
new file mode 100644
index 00000000..950441e1
--- /dev/null
+++ b/src/bz-search-bar.blp
@@ -0,0 +1,53 @@
+using Gtk 4.0;
+using Adw 1;
+
+template $BzSearchBar: Widget {
+ height-request: 40;
+ accessible-role: search_box;
+
+ styles [
+ "search-box",
+ ]
+
+ layout-manager: BoxLayout {
+ spacing: 8;
+ };
+
+ Image search_icon {
+ icon-name: "system-search-symbolic";
+ accessible-role: presentation;
+ }
+
+ Text search_text {
+ hexpand: true;
+ placeholder-text: bind template.placeholder;
+ activate => $text_activated_cb(template);
+ changed => $text_changed_cb(template);
+ }
+
+ Adw.Spinner search_busy {
+ visible: bind template.busy;
+ width-request: 16;
+ height-request: 16;
+ }
+
+ Adw.Bin end_widget_bin {}
+
+ Button clear_button {
+ icon-name: "edit-clear";
+ valign: center;
+ focusable: false;
+ visible: bind $has_text_cb(search_text.text);
+ clicked => $clear_text_cb(template);
+
+ styles [
+ "flat",
+ "circular",
+ "searchbar-button",
+ ]
+
+ accessibility {
+ label: _("Clear Search");
+ }
+ }
+}
diff --git a/src/bz-search-bar.c b/src/bz-search-bar.c
new file mode 100644
index 00000000..2f0cf3c8
--- /dev/null
+++ b/src/bz-search-bar.c
@@ -0,0 +1,361 @@
+/*
+ * 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));
+}
+
+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;
+
+ 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);
+}
+
+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]);
+}
diff --git a/src/bz-search-bar.h b/src/bz-search-bar.h
new file mode 100644
index 00000000..af4124d2
--- /dev/null
+++ b/src/bz-search-bar.h
@@ -0,0 +1,56 @@
+/*
+ * bz-search-bar.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#include
+
+G_BEGIN_DECLS
+
+#define BZ_TYPE_SEARCH_BAR (bz_search_bar_get_type ())
+
+G_DECLARE_FINAL_TYPE (BzSearchBar, bz_search_bar, BZ, SEARCH_BAR, GtkWidget)
+
+BzSearchBar *
+bz_search_bar_new (void);
+
+gboolean
+bz_search_bar_get_busy (BzSearchBar *self);
+
+void
+bz_search_bar_set_busy (BzSearchBar *self,
+ gboolean busy);
+
+GtkWidget *
+bz_search_bar_get_end_widget (BzSearchBar *self);
+
+void
+bz_search_bar_set_end_widget (BzSearchBar *self,
+ GtkWidget *widget);
+
+const gchar *
+bz_search_bar_get_placeholder (BzSearchBar *self);
+
+void
+bz_search_bar_set_placeholder (BzSearchBar *self,
+ const gchar *text);
+
+G_END_DECLS
diff --git a/src/bz-search-page.blp b/src/bz-search-page.blp
index 5385310a..da32165c 100644
--- a/src/bz-search-page.blp
+++ b/src/bz-search-page.blp
@@ -37,73 +37,34 @@ template $BzSearchPage: Adw.Bin {
Adw.Clamp search_box_clamp {
margin-start: 12;
margin-end: 12;
+ margin-top: 10;
+ margin-bottom: 10;
maximum-size: 375;
- child: Adw.Bin {
- halign: fill;
- margin-top: 10;
- margin-bottom: 10;
+ child: $BzSearchBar search_bar {
+ placeholder: _("Search Apps, Games, Software");
+ search-activated => $search_activate(template);
+ search-changed => $search_changed(template);
- child: Box {
- spacing: 8;
- height-request: 40;
-
- Image {
- icon-name: "system-search-symbolic";
- }
-
- Text search_bar {
- hexpand: true;
- max-length: 50;
- placeholder-text: _("Search Apps, Games, Software");
- }
-
- Adw.Spinner search_busy {
- visible: false;
- width-request: 16;
- height-request: 16;
- }
-
- MenuButton filter_button {
- visible: bind $is_valid_string(search_bar.text) as ;
- icon-name: "sliders-horizontal-symbolic";
- tooltip-text: _("Search Filters");
-
- popover: $BzSearchFilterPopover filter_popover {
- notify::has-active-filters => $has_active_filters_cb();
- };
-
- styles [
- "flat",
- "circular",
- "searchbar-button",
- ]
-
- accessibility {
- label: _("Filters");
- }
- }
-
- Button clear_button {
- visible: bind $is_valid_string(search_bar.text) as ;
- icon-name: "edit-clear-symbolic";
-
- styles [
- "flat",
- "circular",
- "searchbar-button",
- ]
-
- clicked => $reset_search_cb(template);
+ end-widget: MenuButton filter_button {
+ visible: bind $is_valid_string(search_bar.text) as ;
+ icon-name: "sliders-horizontal-symbolic";
+ tooltip-text: _("Search Filters");
+ valign: center;
- accessibility {
- label: _("Clear Search");
- }
- }
+ popover: $BzSearchFilterPopover filter_popover {
+ notify::has-active-filters => $has_active_filters_cb();
+ };
styles [
- "search-box",
+ "flat",
+ "circular",
+ "searchbar-button",
]
+
+ accessibility {
+ label: _("Filters");
+ }
};
};
}
diff --git a/src/bz-search-page.c b/src/bz-search-page.c
index 7529deea..b15db58c 100644
--- a/src/bz-search-page.c
+++ b/src/bz-search-page.c
@@ -30,6 +30,7 @@
#include "bz-rich-app-tile.h"
#include "bz-screenshot.h"
#include "bz-search-filter-popover.h"
+#include "bz-search-bar.h"
#include "bz-search-page.h"
#include "bz-search-pill-list.h"
#include "bz-search-result.h"
@@ -54,8 +55,7 @@ struct _BzSearchPage
DexFuture *search_query;
/* Template widgets */
- GtkText *search_bar;
- AdwSpinner *search_busy;
+ BzSearchBar *search_bar;
GtkBox *content_box;
GtkStack *search_stack;
GtkGridView *grid_view;
@@ -75,15 +75,8 @@ enum
LAST_PROP
};
-static GParamSpec *props[LAST_PROP] = { 0 };
-
-static void
-search_changed (GtkEditable *editable,
- BzSearchPage *self);
-static void
-search_activate (GtkText *text,
- BzSearchPage *self);
+static GParamSpec *props[LAST_PROP] = { 0 };
static void
grid_activate (GtkGridView *grid_view,
@@ -114,6 +107,14 @@ emit_idx (BzSearchPage *self,
GListModel *model,
guint selected_idx);
+static gboolean
+bz_search_page_grab_focus (GtkWidget *widget)
+{
+ BzSearchPage *self = BZ_SEARCH_PAGE (widget);
+
+ return gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
+}
+
static void
bz_search_page_dispose (GObject *object)
{
@@ -299,6 +300,48 @@ bind_category_tile_cb (BzSearchPage *self,
g_signal_connect_swapped (tile, "clicked", G_CALLBACK (category_clicked), category);
}
+static void
+search_changed (BzSearchPage *self,
+ GtkEditable *editable)
+{
+ g_clear_handle_id (&self->search_update_timeout, g_source_remove);
+ self->search_update_timeout = g_timeout_add_once (
+ 150, (GSourceOnceFunc) update_filter, self);
+ bz_search_bar_set_busy (BZ_SEARCH_BAR (editable), TRUE);
+}
+
+static void
+search_activate (BzSearchPage *self,
+ BzSearchBar *search_bar)
+{
+ GtkSelectionModel *model = NULL;
+ guint n_items = 0;
+ g_autoptr (BzSearchResult) result = NULL;
+ BzEntryGroup *group = NULL;
+
+ model = gtk_grid_view_get_model (self->grid_view);
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (model));
+
+ bz_search_bar_set_busy (self->search_bar, FALSE);
+
+ if (n_items > 0)
+ {
+ result = g_list_model_get_item (G_LIST_MODEL (model), 0);
+ group = bz_search_result_get_group (result);
+
+ if (bz_entry_group_get_removable_and_available (group) > 0)
+ {
+ gtk_widget_activate_action (GTK_WIDGET (self), "window.remove-group", "(sb)",
+ bz_entry_group_get_id (group), FALSE);
+ }
+ else if (bz_entry_group_get_installable_and_available (group) > 0)
+ {
+ gtk_widget_activate_action (GTK_WIDGET (self), "window.install-group", "(sb)",
+ bz_entry_group_get_id (group), FALSE);
+ }
+ }
+}
+
static void
unbind_category_tile_cb (BzSearchPage *self,
BzCategoryTile *tile,
@@ -386,6 +429,8 @@ bz_search_page_class_init (BzSearchPageClass *klass)
object_class->get_property = bz_search_page_get_property;
object_class->set_property = bz_search_page_set_property;
+ widget_class->grab_focus = bz_search_page_grab_focus;
+
props[PROP_STATE] =
g_param_spec_object (
"state",
@@ -422,7 +467,6 @@ bz_search_page_class_init (BzSearchPageClass *klass)
bz_widget_class_bind_all_util_callbacks (widget_class);
gtk_widget_class_bind_template_child (widget_class, BzSearchPage, search_bar);
- gtk_widget_class_bind_template_child (widget_class, BzSearchPage, search_busy);
gtk_widget_class_bind_template_child (widget_class, BzSearchPage, content_box);
gtk_widget_class_bind_template_child (widget_class, BzSearchPage, search_stack);
gtk_widget_class_bind_template_child (widget_class, BzSearchPage, grid_view);
@@ -444,6 +488,8 @@ bz_search_page_class_init (BzSearchPageClass *klass)
gtk_widget_class_bind_template_callback (widget_class, tile_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, copy_id_cb);
gtk_widget_class_bind_template_callback (widget_class, debug_id_inspect_cb);
+ gtk_widget_class_bind_template_callback (widget_class, search_activate);
+ gtk_widget_class_bind_template_callback (widget_class, search_changed);
}
static void
@@ -459,8 +505,6 @@ bz_search_page_init (BzSearchPage *self)
gtk_no_selection_set_model (GTK_NO_SELECTION (self->selection_model), G_LIST_MODEL (self->search_model));
gtk_grid_view_set_model (self->grid_view, self->selection_model);
- g_signal_connect (self->search_bar, "changed", G_CALLBACK (search_changed), self);
- g_signal_connect (self->search_bar, "activate", G_CALLBACK (search_activate), self);
g_signal_connect (self->grid_view, "activate", G_CALLBACK (grid_activate), self);
g_signal_connect_swapped (self->filter_popover, "notify::selected-categories",
@@ -618,62 +662,11 @@ gboolean
bz_search_page_ensure_active (BzSearchPage *self,
const char *initial)
{
- const char *text = NULL;
-
g_return_val_if_fail (BZ_IS_SEARCH_PAGE (self), FALSE);
- text = gtk_editable_get_text (GTK_EDITABLE (self->search_bar));
- if (text != NULL && *text != '\0' &&
- gtk_widget_has_focus (GTK_WIDGET (self->search_bar)))
- return FALSE;
-
- gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
bz_search_page_set_text (self, initial);
- return TRUE;
-}
-
-static void
-search_changed (GtkEditable *editable,
- BzSearchPage *self)
-{
- g_clear_handle_id (&self->search_update_timeout, g_source_remove);
- self->search_update_timeout = g_timeout_add_once (
- 150, (GSourceOnceFunc) update_filter, self);
- gtk_widget_set_visible (GTK_WIDGET (self->search_busy), TRUE);
-}
-
-static void
-search_activate (GtkText *text,
- BzSearchPage *self)
-{
- GtkSelectionModel *model = NULL;
- guint n_items = 0;
- g_autoptr (BzSearchResult) result = NULL;
- BzEntryGroup *group = NULL;
-
- model = gtk_grid_view_get_model (self->grid_view);
- n_items = g_list_model_get_n_items (G_LIST_MODEL (model));
-
- if (gtk_widget_get_visible (GTK_WIDGET (self->search_busy)))
- return;
-
- if (n_items > 0)
- {
- result = g_list_model_get_item (G_LIST_MODEL (model), 0);
- group = bz_search_result_get_group (result);
-
- if (bz_entry_group_get_removable_and_available (group) > 0)
- {
- gtk_widget_activate_action (GTK_WIDGET (self), "window.remove-group", "(sb)",
- bz_entry_group_get_id (group), FALSE);
- }
- else if (bz_entry_group_get_installable_and_available (group) > 0)
- {
- gtk_widget_activate_action (GTK_WIDGET (self), "window.install-group", "(sb)",
- bz_entry_group_get_id (group), FALSE);
- }
- }
+ return gtk_widget_grab_focus (GTK_WIDGET (self));
}
static void
@@ -720,6 +713,7 @@ search_query_then (DexFuture *future,
gboolean only_free = FALSE;
gboolean only_non_eol = FALSE;
gboolean only_mobile = FALSE;
+ const char *search_text = NULL;
bz_weak_get_or_return_reject (self, wr);
@@ -732,6 +726,7 @@ search_query_then (DexFuture *future,
only_mobile = bz_search_filter_popover_get_only_mobile (self->filter_popover);
filtered = g_ptr_array_new_with_free_func (g_object_unref);
+ search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_bar));
for (guint i = 0; i < results->len; i++)
{
@@ -766,7 +761,7 @@ search_query_then (DexFuture *future,
self->search_model,
0, old_length,
(gpointer *) filtered->pdata, filtered->len);
- gtk_widget_set_visible (GTK_WIDGET (self->search_busy), FALSE);
+ bz_search_bar_set_busy (self->search_bar, FALSE);
if (filtered->len > 0)
{
@@ -775,12 +770,20 @@ search_query_then (DexFuture *future,
}
else
{
- const char *search_text = NULL;
-
- search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_bar));
- page_name = (search_text && *search_text) ? "no-results" : "empty";
+ page_name = (search_text && *search_text) ? "no-results" : "empty";
}
+ if (search_text && *search_text) {
+ const char *message = NULL;
+
+ message = filtered->len == 0 ? _("No applications found") : g_strdup_printf (ngettext ("One application found",
+ "%u applications found",
+ filtered->len),
+ filtered->len);
+
+ gtk_accessible_announce (GTK_ACCESSIBLE (self), message, GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_MEDIUM);
+ }
+
self->current_query = g_object_ref (finished);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_QUERY]);
@@ -808,7 +811,7 @@ update_filter (BzSearchPage *self)
self->current_query = bz_finished_search_query_new ();
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_QUERY]);
- gtk_widget_set_visible (GTK_WIDGET (self->search_busy), FALSE);
+ bz_search_bar_set_busy (self->search_bar, FALSE);
if (self->state == NULL)
return;
@@ -853,9 +856,8 @@ update_filter (BzSearchPage *self)
future = bz_search_engine_query (
engine,
(const char *const *) terms);
- gtk_widget_set_visible (
- GTK_WIDGET (self->search_busy),
- dex_future_is_pending (future));
+
+ bz_search_bar_set_busy (self->search_bar, dex_future_is_pending (future));
future = dex_future_then (
future,
diff --git a/src/bz-window.c b/src/bz-window.c
index a08a5fe6..569c513e 100644
--- a/src/bz-window.c
+++ b/src/bz-window.c
@@ -37,6 +37,7 @@
#include "bz-io.h"
#include "bz-library-page.h"
#include "bz-progress-bar.h"
+#include "bz-search-bar.h"
#include "bz-search-page.h"
#include "bz-template-callbacks.h"
#include "bz-transaction-dialog.h"
@@ -688,6 +689,7 @@ bz_window_class_init (BzWindowClass *klass)
g_object_class_install_properties (object_class, LAST_PROP, props);
+ g_type_ensure (BZ_TYPE_SEARCH_BAR);
g_type_ensure (BZ_TYPE_SEARCH_PAGE);
g_type_ensure (BZ_TYPE_PROGRESS_BAR);
g_type_ensure (BZ_TYPE_CURATED_VIEW);
diff --git a/src/meson.build b/src/meson.build
index 4345049c..95ab7dc5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -138,6 +138,7 @@ bz_sources = files(
'bz-screenshots-carousel.c',
'bz-search-engine.c',
'bz-search-filter-popover.c',
+ 'bz-search-bar.c',
'bz-search-page.c',
'bz-search-pill-list.c',
'bz-section-view.c',
@@ -310,6 +311,7 @@ blueprints = custom_target('blueprints',
'bz-screenshot-page.blp',
'bz-screenshots-carousel.blp',
'bz-search-filter-popover.blp',
+ 'bz-search-bar.blp',
'bz-search-page.blp',
'bz-section-view.blp',
'bz-stats-dialog.blp',
diff --git a/src/style.css b/src/style.css
index b91a8343..e8e6ceeb 100644
--- a/src/style.css
+++ b/src/style.css
@@ -70,7 +70,7 @@
outline-offset: 6px;
transition-property: outline, outline-offset;
transition-duration: 200ms;
- padding: 8px 12px;
+ padding: 0px 12px;
border-radius: 9999px;
background-color: var(--card-bg-color);
}