From 74c410bd8523a0258f0de77b65ba0b61821c08bc Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 20 Jan 2022 23:07:34 -0300 Subject: [PATCH] Port search subsystem to GTK4 --- src/gui/gcal-search-button.c | 278 +++++++++++++++++---------- src/gui/gcal-search-button.h | 4 +- src/gui/gcal-search-button.ui | 124 ++++++++++++ src/gui/gcal-search-hit-row.c | 193 +++++++++++++++++++ src/gui/gcal-search-hit-row.h | 36 ++++ src/gui/gcal-search-hit-row.ui | 53 +++++ src/gui/gui.gresource.xml | 2 + src/gui/meson.build | 1 + src/search/gcal-search-engine.c | 2 - src/search/gcal-search-hit-event.c | 65 ++----- src/search/gcal-search-hit-event.h | 5 +- src/search/gcal-search-hit.c | 299 ++++++++++++++++++++++++++++- src/search/gcal-search-hit.h | 30 ++- src/search/gcal-search-model.c | 6 +- src/theme/Adwaita.css | 9 + 15 files changed, 939 insertions(+), 168 deletions(-) create mode 100644 src/gui/gcal-search-button.ui create mode 100644 src/gui/gcal-search-hit-row.c create mode 100644 src/gui/gcal-search-hit-row.h create mode 100644 src/gui/gcal-search-hit-row.ui diff --git a/src/gui/gcal-search-button.c b/src/gui/gcal-search-button.c index 33b3b791..7f8c9301 100644 --- a/src/gui/gcal-search-button.c +++ b/src/gui/gcal-search-button.c @@ -21,8 +21,10 @@ #define G_LOG_DOMAIN "GcalSearchButton" #include "gcal-context.h" +#include "gcal-debug.h" #include "gcal-search-button.h" #include "gcal-search-hit.h" +#include "gcal-search-hit-row.h" #include @@ -30,14 +32,22 @@ struct _GcalSearchButton { - DzlSuggestionButton parent; + AdwBin parent; + + GtkEditable *entry; + GtkWidget *popover; + GtkListBox *results_listbox; + GtkRevealer *results_revealer; + GtkStack *stack; GCancellable *cancellable; + gint max_width_chars; + GListModel *model; GcalContext *context; }; -G_DEFINE_TYPE (GcalSearchButton, gcal_search_button, DZL_TYPE_SUGGESTION_BUTTON) +G_DEFINE_TYPE (GcalSearchButton, gcal_search_button, ADW_TYPE_BIN) enum { @@ -49,29 +59,94 @@ enum static GParamSpec *properties [N_PROPS]; +/* + * Auxiliary methods + */ + +static void +show_suggestions (GcalSearchButton *self) +{ + gtk_popover_popup (GTK_POPOVER (self->popover)); + gtk_revealer_set_reveal_child (self->results_revealer, TRUE); +} + +static void +hide_suggestions (GcalSearchButton *self) +{ + gtk_revealer_set_reveal_child (self->results_revealer, FALSE); + gtk_editable_set_text (self->entry, ""); +} + +static GtkWidget * +create_widget_func (gpointer item, + gpointer user_data) +{ + return gcal_search_hit_row_new (item); +} + +static void +set_model (GcalSearchButton *self, + GListModel *model) +{ + GCAL_ENTRY; + + gtk_list_box_bind_model (self->results_listbox, + model, + create_widget_func, + self, + NULL); + + if (model) + show_suggestions (self); + else + hide_suggestions (self); + + GCAL_EXIT; +} + + /* * Callbacks */ static void -position_suggestion_popover_func (DzlSuggestionEntry *entry, - GdkRectangle *area, - gboolean *is_absolute, - gpointer user_data) +on_button_clicked_cb (GtkButton *button, + GcalSearchButton *self) { - gint new_width; + gint max_width_chars; -#define RIGHT_MARGIN 6 - dzl_suggestion_entry_window_position_func (entry, area, is_absolute, NULL); + max_width_chars = gtk_editable_get_max_width_chars (self->entry); - new_width = MAX (area->width * 2 / 5, MIN_WIDTH); - area->x += area->width - new_width; - area->width = new_width - RIGHT_MARGIN; - area->y -= 3; + if (max_width_chars) + self->max_width_chars = max_width_chars; -#undef RIGHT_MARGIN + gtk_editable_set_width_chars (self->entry, 1); + gtk_editable_set_max_width_chars (self->entry, self->max_width_chars ?: 20); + gtk_stack_set_visible_child_name (self->stack, "entry"); + gtk_widget_grab_focus (GTK_WIDGET (self->entry)); +} +static void +on_focus_controller_leave_cb (GtkEventControllerFocus *focus_controller, + GcalSearchButton *self) +{ + gtk_editable_set_width_chars (self->entry, 0); + gtk_editable_set_max_width_chars (self->entry, 0); + gtk_stack_set_visible_child_name (self->stack, "button"); + + hide_suggestions (self); + + gtk_editable_set_text (self->entry, ""); +} + +static void +on_entry_icon_pressed_cb (GtkEntry *entry, + GtkEntryIconPosition position, + GcalSearchButton *self) +{ + if (position == GTK_ENTRY_ICON_PRIMARY) + gtk_stack_set_visible_child_name (self->stack, "button"); } static void @@ -81,36 +156,34 @@ on_search_finished_cb (GObject *source_object, { g_autoptr (GListModel) model = NULL; g_autoptr (GError) error = NULL; - DzlSuggestionEntry *entry; GcalSearchButton *self; self = GCAL_SEARCH_BUTTON (user_data); model = gcal_search_engine_search_finish (GCAL_SEARCH_ENGINE (source_object), result, &error); - entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); - dzl_suggestion_entry_set_model (entry, model); + set_model (self, model); } static void -on_search_entry_changed_cb (GcalSearchButton *self) +on_entry_text_changed_cb (GtkEntry *entry, + GParamSpec *pspec, + GcalSearchButton *self) { g_autofree gchar *sexp_query = NULL; - DzlSuggestionEntry *entry; GcalSearchEngine *search_engine; - const gchar *typed_text; + const gchar *text; - entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); - typed_text = dzl_suggestion_entry_get_typed_text (entry); + text = gtk_editable_get_text (self->entry); g_cancellable_cancel (self->cancellable); - if (dzl_str_empty0 (typed_text)) + if (!text || *text == '\0') { - dzl_suggestion_entry_set_model (entry, NULL); + set_model (self, NULL); return; } - sexp_query = g_strdup_printf ("(contains? \"summary\" \"%s\")", typed_text); + sexp_query = g_strdup_printf ("(contains? \"summary\" \"%s\")", text); search_engine = gcal_context_get_search_engine (self->context); gcal_search_engine_search (search_engine, sexp_query, @@ -121,43 +194,63 @@ on_search_entry_changed_cb (GcalSearchButton *self) } static void -on_search_entry_suggestion_activated_cb (DzlSuggestionEntry *entry, - GcalSearchHit *search_hit, - GcalSearchButton *self) +on_popover_closed_cb (GtkPopover *popover, + GcalSearchButton *self) { + gtk_editable_set_width_chars (self->entry, 0); + gtk_editable_set_max_width_chars (self->entry, 0); + gtk_editable_set_text (self->entry, ""); + gtk_stack_set_visible_child_name (self->stack, "button"); +} + +static void +on_results_listbox_row_activated_cb (GtkListBox *listbox, + GcalSearchHitRow *row, + GcalSearchButton *self) +{ + GcalSearchHit *search_hit; + + search_hit = gcal_search_hit_row_get_search_hit (row); gcal_search_hit_activate (search_hit, GTK_WIDGET (self)); + + hide_suggestions (self); } static void -on_unfocus_action_activated_cb (GSimpleAction *action, - GVariant *param, - gpointer user_data) +on_results_revealer_child_reveal_state_changed_cb (GtkRevealer *revealer, + GParamSpec *pspec, + GcalSearchButton *self) { - DzlSuggestionEntry *entry; - GcalSearchButton *self; - GtkWidget *toplevel; - - g_assert (GCAL_IS_SEARCH_BUTTON (user_data)); - g_assert (G_IS_SIMPLE_ACTION (action)); - - g_debug ("Unfocusing search button"); - - self = GCAL_SEARCH_BUTTON (user_data); - entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); - g_signal_emit_by_name (entry, "hide-suggestions"); - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); - gtk_widget_grab_focus (toplevel); - gtk_entry_set_text (GTK_ENTRY (entry), ""); + if (!gtk_revealer_get_child_revealed (revealer) && !gtk_revealer_get_reveal_child (revealer)) + gtk_popover_popdown (GTK_POPOVER (self->popover)); } -static void -on_shortcut_grab_focus_cb (GtkWidget *widget, - gpointer user_data) -{ - g_debug ("Focusing search button"); - gtk_widget_grab_focus (GTK_WIDGET (user_data)); +/* + * GtkWidget overrides + */ + +static gboolean +gcal_search_button_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GcalSearchButton *self = GCAL_SEARCH_BUTTON (widget); + + if (!gtk_widget_get_visible (GTK_WIDGET (self->popover))) + return gtk_widget_child_focus (GTK_WIDGET (self->stack), direction); + + if (direction == GTK_DIR_DOWN) + { + GtkListBoxRow *first_row = gtk_list_box_get_row_at_index (self->results_listbox, 0); + + if (!first_row) + return gtk_widget_child_focus (GTK_WIDGET (self->stack), direction); + + gtk_widget_grab_focus (GTK_WIDGET (first_row)); + return TRUE; + } + + return gtk_widget_child_focus (GTK_WIDGET (self->stack), direction); } @@ -165,6 +258,15 @@ on_shortcut_grab_focus_cb (GtkWidget *widget, * GObject overrides */ +static void +gcal_search_button_dispose (GObject *object) +{ + GcalSearchButton *self = (GcalSearchButton *)object; + + g_clear_pointer (&self->popover, gtk_widget_unparent); + + G_OBJECT_CLASS (gcal_search_button_parent_class)->dispose (object); +} static void gcal_search_button_finalize (GObject *object) @@ -222,11 +324,15 @@ static void gcal_search_button_class_init (GcalSearchButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + object_class->dispose = gcal_search_button_dispose; object_class->finalize = gcal_search_button_finalize; object_class->get_property = gcal_search_button_get_property; object_class->set_property = gcal_search_button_set_property; + widget_class->focus = gcal_search_button_focus; + /** * GcalSearchButton::context: * @@ -239,56 +345,30 @@ gcal_search_button_class_init (GcalSearchButtonClass *klass) G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/gcal-search-button.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalSearchButton, entry); + gtk_widget_class_bind_template_child (widget_class, GcalSearchButton, popover); + gtk_widget_class_bind_template_child (widget_class, GcalSearchButton, results_listbox); + gtk_widget_class_bind_template_child (widget_class, GcalSearchButton, results_revealer); + gtk_widget_class_bind_template_child (widget_class, GcalSearchButton, stack); + + gtk_widget_class_bind_template_callback (widget_class, on_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_focus_controller_leave_cb); + gtk_widget_class_bind_template_callback (widget_class, on_entry_icon_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_entry_text_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_popover_closed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_results_listbox_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_results_revealer_child_reveal_state_changed_cb); + + gtk_widget_class_set_css_name (widget_class, "searchbutton"); } static void gcal_search_button_init (GcalSearchButton *self) { - g_autoptr (GSimpleActionGroup) group = NULL; - DzlShortcutController *controller; - DzlSuggestionEntry *entry; + gtk_widget_init_template (GTK_WIDGET (self)); - static GActionEntry actions[] = { - { "unfocus", on_unfocus_action_activated_cb }, - }; - - group = g_simple_action_group_new (); - g_action_map_add_action_entries (G_ACTION_MAP (group), - actions, - G_N_ELEMENTS (actions), - self); - - gtk_widget_insert_action_group (GTK_WIDGET (self), "search", G_ACTION_GROUP (group)); - - entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); - g_signal_connect_object (entry, - "changed", - G_CALLBACK (on_search_entry_changed_cb), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (entry, - "suggestion-activated", - G_CALLBACK (on_search_entry_suggestion_activated_cb), - self, - 0); - - dzl_suggestion_entry_set_position_func (entry, - position_suggestion_popover_func, - self, - NULL); - - controller = dzl_shortcut_controller_find (GTK_WIDGET (entry)); - dzl_shortcut_controller_add_command_callback (controller, - "org.gnome.calendar.search", - "f", - DZL_SHORTCUT_PHASE_CAPTURE | DZL_SHORTCUT_PHASE_GLOBAL, - on_shortcut_grab_focus_cb, - self, - NULL); - - dzl_shortcut_controller_add_command_action (controller, - "org.gnome.calendar.search-button.unfocus", - "Escape", - DZL_SHORTCUT_PHASE_CAPTURE, - "search.unfocus"); + gtk_widget_set_parent (GTK_WIDGET (self->popover), GTK_WIDGET (self)); } diff --git a/src/gui/gcal-search-button.h b/src/gui/gcal-search-button.h index c9f3fd97..c50d8e85 100644 --- a/src/gui/gcal-search-button.h +++ b/src/gui/gcal-search-button.h @@ -20,11 +20,11 @@ #pragma once -#include +#include G_BEGIN_DECLS #define GCAL_TYPE_SEARCH_BUTTON (gcal_search_button_get_type()) -G_DECLARE_FINAL_TYPE (GcalSearchButton, gcal_search_button, GCAL, SEARCH_BUTTON, DzlSuggestionButton) +G_DECLARE_FINAL_TYPE (GcalSearchButton, gcal_search_button, GCAL, SEARCH_BUTTON, AdwBin) G_END_DECLS diff --git a/src/gui/gcal-search-button.ui b/src/gui/gcal-search-button.ui new file mode 100644 index 00000000..b6363521 --- /dev/null +++ b/src/gui/gcal-search-button.ui @@ -0,0 +1,124 @@ + + + + + + bottom + False + results_listbox + + + + + slide-down + + + + + + 450 + 400 + True + True + never + + + + True + natural + natural + + + + none + + + + + + + + + + + + + + + diff --git a/src/gui/gcal-search-hit-row.c b/src/gui/gcal-search-hit-row.c new file mode 100644 index 00000000..b6f5d6f7 --- /dev/null +++ b/src/gui/gcal-search-hit-row.c @@ -0,0 +1,193 @@ +/* gcal-search-hit-row.c + * + * Copyright 2022 Georges Basile Stavracas Neto + * + * 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 "gcal-search-hit-row.h" + +struct _GcalSearchHitRow +{ + GtkListBoxRow parent_instance; + + GtkImage *image; + GtkWidget *separator; + GtkLabel *subtitle; + GtkLabel *title; + + GcalSearchHit *search_hit; +}; + +G_DEFINE_FINAL_TYPE (GcalSearchHitRow, gcal_search_hit_row, GTK_TYPE_LIST_BOX_ROW) + +enum +{ + PROP_0, + PROP_SEARCH_HIT, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + + +/* + * Auxiliary methods + */ + +static void +update_search_hit (GcalSearchHitRow *self) +{ + g_autofree gchar *escaped_title = NULL; + const gchar *subtitle; + + gtk_image_set_from_paintable (self->image, gcal_search_hit_get_primary_icon (self->search_hit)); + + escaped_title = g_markup_escape_text (gcal_search_hit_get_title (self->search_hit), -1); + gtk_label_set_label (self->title, escaped_title); + + subtitle = gcal_search_hit_get_subtitle (self->search_hit); + if (subtitle) + { + g_autofree gchar *escaped_subtitle = NULL; + + escaped_subtitle = g_markup_escape_text (subtitle, -1); + escaped_subtitle = g_strstrip (escaped_subtitle); + gtk_label_set_label (self->subtitle, escaped_subtitle); + + gtk_widget_set_visible (self->separator, escaped_subtitle && *escaped_subtitle != '\0'); + } + else + { + gtk_widget_hide (self->separator); + } +} + + +/* + * Callbacks + */ + +static void +on_search_hit_changed_cb (GcalSearchHit *search_hit, + GParamSpec *pspec, + GcalSearchHitRow *self) +{ + update_search_hit (self); +} + +/* + * GObject overrides + */ + +static void +gcal_search_hit_row_finalize (GObject *object) +{ + GcalSearchHitRow *self = (GcalSearchHitRow *)object; + + g_clear_object (&self->search_hit); + + G_OBJECT_CLASS (gcal_search_hit_row_parent_class)->finalize (object); +} + +static void +gcal_search_hit_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalSearchHitRow *self = GCAL_SEARCH_HIT_ROW (object); + + switch (prop_id) + { + case PROP_SEARCH_HIT: + g_value_set_object (value, self->search_hit); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalSearchHitRow *self = GCAL_SEARCH_HIT_ROW (object); + + switch (prop_id) + { + case PROP_SEARCH_HIT: + g_assert (self->search_hit == NULL); + self->search_hit = g_value_dup_object (value); + g_signal_connect_object (self->search_hit, "notify", G_CALLBACK (on_search_hit_changed_cb), self, 0); + update_search_hit (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_row_class_init (GcalSearchHitRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_search_hit_row_finalize; + object_class->get_property = gcal_search_hit_row_get_property; + object_class->set_property = gcal_search_hit_row_set_property; + + properties[PROP_SEARCH_HIT] = g_param_spec_object ("search-hit", + NULL, + NULL, + GCAL_TYPE_SEARCH_HIT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/gcal-search-hit-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalSearchHitRow, image); + gtk_widget_class_bind_template_child (widget_class, GcalSearchHitRow, separator); + gtk_widget_class_bind_template_child (widget_class, GcalSearchHitRow, subtitle); + gtk_widget_class_bind_template_child (widget_class, GcalSearchHitRow, title); +} + +static void +gcal_search_hit_row_init (GcalSearchHitRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +gcal_search_hit_row_new (GcalSearchHit *search_hit) +{ + return g_object_new (GCAL_TYPE_SEARCH_HIT_ROW, + "search-hit", search_hit, + NULL); +} + +GcalSearchHit * +gcal_search_hit_row_get_search_hit (GcalSearchHitRow *self) +{ + g_return_val_if_fail (GCAL_IS_SEARCH_HIT_ROW (self), NULL); + + return self->search_hit; +} diff --git a/src/gui/gcal-search-hit-row.h b/src/gui/gcal-search-hit-row.h new file mode 100644 index 00000000..947c964c --- /dev/null +++ b/src/gui/gcal-search-hit-row.h @@ -0,0 +1,36 @@ +/* gcal-search-hit-row.h + * + * Copyright 2022 Georges Basile Stavracas Neto + * + * 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 + +#include "gcal-search-hit.h" + +G_BEGIN_DECLS + +#define GCAL_TYPE_SEARCH_HIT_ROW (gcal_search_hit_row_get_type()) +G_DECLARE_FINAL_TYPE (GcalSearchHitRow, gcal_search_hit_row, GCAL, SEARCH_HIT_ROW, GtkListBoxRow) + +GtkWidget * gcal_search_hit_row_new (GcalSearchHit *search_hit); + +GcalSearchHit * gcal_search_hit_row_get_search_hit (GcalSearchHitRow *self); + +G_END_DECLS diff --git a/src/gui/gcal-search-hit-row.ui b/src/gui/gcal-search-hit-row.ui new file mode 100644 index 00000000..23b0c0ea --- /dev/null +++ b/src/gui/gcal-search-hit-row.ui @@ -0,0 +1,53 @@ + + + + diff --git a/src/gui/gui.gresource.xml b/src/gui/gui.gresource.xml index 1ecfe3ca..3a2e2ae9 100644 --- a/src/gui/gui.gresource.xml +++ b/src/gui/gui.gresource.xml @@ -6,6 +6,8 @@ gcal-event-widget.ui gcal-meeting-row.ui gcal-quick-add-popover.ui + gcal-search-button.ui + gcal-search-hit-row.ui gcal-weather-settings.ui gcal-window.ui diff --git a/src/gui/meson.build b/src/gui/meson.build index afc13be0..a0689764 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -21,6 +21,7 @@ sources += files( 'gcal-meeting-row.c', 'gcal-quick-add-popover.c', 'gcal-search-button.c', + 'gcal-search-hit-row.c', 'gcal-weather-settings.c', 'gcal-window.c', ) diff --git a/src/search/gcal-search-engine.c b/src/search/gcal-search-engine.c index d0818c77..253daff8 100644 --- a/src/search/gcal-search-engine.c +++ b/src/search/gcal-search-engine.c @@ -27,8 +27,6 @@ #include "gcal-timeline.h" #include "gcal-timeline-subscriber.h" -#include - typedef struct { GcalSearchEngine *engine; diff --git a/src/search/gcal-search-hit-event.c b/src/search/gcal-search-hit-event.c index 807c9c5a..a53d4251 100644 --- a/src/search/gcal-search-hit-event.c +++ b/src/search/gcal-search-hit-event.c @@ -26,15 +26,12 @@ struct _GcalSearchHitEvent { - DzlSuggestion parent; + GcalSearchHit parent; GcalEvent *event; }; -static void gcal_search_hit_interface_init (GcalSearchHitInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (GcalSearchHitEvent, gcal_search_hit_event, DZL_TYPE_SUGGESTION, - G_IMPLEMENT_INTERFACE (GCAL_TYPE_SEARCH_HIT, gcal_search_hit_interface_init)) +G_DEFINE_TYPE (GcalSearchHitEvent, gcal_search_hit_event, GCAL_TYPE_SEARCH_HIT) enum { @@ -52,49 +49,31 @@ static GParamSpec *properties [N_PROPS]; static void set_event (GcalSearchHitEvent *self, GcalEvent *event) -{ - g_autofree gchar *date_string = NULL; - DzlSuggestion *suggestion; - - self->event = g_object_ref (event); - - suggestion = DZL_SUGGESTION (self); - dzl_suggestion_set_id (suggestion, gcal_event_get_uid (event)); - dzl_suggestion_set_title (suggestion, gcal_event_get_summary (event)); - - date_string = gcal_event_format_date (event); - dzl_suggestion_set_subtitle (suggestion, date_string); -} - - -/* - * DzlSuggestion overrides - */ - -static GdkPaintable* -gcal_search_hit_event_get_paintable (DzlSuggestion *suggestion, - GtkWidget *widget) { g_autoptr (GdkPaintable) paintable = NULL; - GcalSearchHitEvent *self; + g_autofree gchar *date_string = NULL; + GcalSearchHit *search_hit; const GdkRGBA *color; GcalCalendar *calendar; - self = GCAL_SEARCH_HIT_EVENT (suggestion); - calendar = gcal_event_get_calendar (self->event); + self->event = g_object_ref (event); + search_hit = GCAL_SEARCH_HIT (self); + gcal_search_hit_set_id (search_hit, gcal_event_get_uid (event)); + gcal_search_hit_set_title (search_hit, gcal_event_get_summary (event)); + + date_string = gcal_event_format_date (event); + gcal_search_hit_set_subtitle (search_hit, date_string); + + calendar = gcal_event_get_calendar (self->event); color = gcal_calendar_get_color (calendar); paintable = get_circle_paintable_from_color (color, 16); - - /* Inject our custom style class into the given widget */ - gtk_style_context_add_class (gtk_widget_get_style_context (widget), "calendar-color-image"); - - return g_steal_pointer (&paintable); + gcal_search_hit_set_primary_icon (search_hit, paintable); } /* - * GcalSearchHit interface + * GcalSearchHit overrides */ static void @@ -140,14 +119,6 @@ gcal_search_hit_event_compare (GcalSearchHit *a, return -gcal_event_compare_with_current (event_a, event_b, now_utc); } -static void -gcal_search_hit_interface_init (GcalSearchHitInterface *iface) -{ - iface->activate = gcal_search_hit_event_activate; - iface->get_priority = gcal_search_hit_event_get_priority; - iface->compare = gcal_search_hit_event_compare; -} - /* * GObject overrides @@ -206,13 +177,15 @@ static void gcal_search_hit_event_class_init (GcalSearchHitEventClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - DzlSuggestionClass *suggestion_class = DZL_SUGGESTION_CLASS (klass); + GcalSearchHitClass *search_hit_class = GCAL_SEARCH_HIT_CLASS (klass); object_class->finalize = gcal_search_hit_event_finalize; object_class->get_property = gcal_search_hit_event_get_property; object_class->set_property = gcal_search_hit_event_set_property; - suggestion_class->get_icon_surface = gcal_search_hit_event_get_icon_surface; + search_hit_class->activate = gcal_search_hit_event_activate; + search_hit_class->get_priority = gcal_search_hit_event_get_priority; + search_hit_class->compare = gcal_search_hit_event_compare; properties[PROP_EVENT] = g_param_spec_object ("event", "Event", diff --git a/src/search/gcal-search-hit-event.h b/src/search/gcal-search-hit-event.h index 1eb9c001..a2da8b2a 100644 --- a/src/search/gcal-search-hit-event.h +++ b/src/search/gcal-search-hit-event.h @@ -21,13 +21,12 @@ #pragma once #include "gcal-event.h" - -#include +#include "gcal-search-hit.h" G_BEGIN_DECLS #define GCAL_TYPE_SEARCH_HIT_EVENT (gcal_search_hit_event_get_type()) -G_DECLARE_FINAL_TYPE (GcalSearchHitEvent, gcal_search_hit_event, GCAL, SEARCH_HIT_EVENT, DzlSuggestion) +G_DECLARE_FINAL_TYPE (GcalSearchHitEvent, gcal_search_hit_event, GCAL, SEARCH_HIT_EVENT, GcalSearchHit) GcalSearchHitEvent* gcal_search_hit_event_new (GcalEvent *event); diff --git a/src/search/gcal-search-hit.c b/src/search/gcal-search-hit.c index fe1bc305..d4e72993 100644 --- a/src/search/gcal-search-hit.c +++ b/src/search/gcal-search-hit.c @@ -20,30 +20,313 @@ #include "gcal-search-hit.h" -G_DEFINE_INTERFACE (GcalSearchHit, gcal_search_hit, DZL_TYPE_SUGGESTION) +typedef struct +{ + gchar *id; + gchar *title; + gchar *subtitle; + + GdkPaintable *primary_icon; +} GcalSearchHitPrivate; + + +G_DEFINE_TYPE_WITH_PRIVATE (GcalSearchHit, gcal_search_hit, G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_ID, + PROP_SUBTITLE, + PROP_TITLE, + PROP_PRIMARY_ICON, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +/* + * GcalSearchHit overrides + */ static void -gcal_search_hit_default_init (GcalSearchHitInterface *iface) +gcal_search_hit_real_activate (GcalSearchHit *self, + GtkWidget *for_widget) { } +static gint +gcal_search_hit_real_compare (GcalSearchHit *a, + GcalSearchHit *b) +{ + return 0; +} + +static gint +gcal_search_hit_real_get_priority (GcalSearchHit *self) +{ + return 0; +} + + +/* + * GObject overrides + */ + +static void +gcal_search_hit_finalize (GObject *object) +{ + GcalSearchHit *self = (GcalSearchHit *)object; + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_clear_pointer (&priv->id, g_free); + g_clear_pointer (&priv->title, g_free); + g_clear_pointer (&priv->subtitle, g_free); + g_clear_object (&priv->primary_icon); + + G_OBJECT_CLASS (gcal_search_hit_parent_class)->finalize (object); +} + +static void +gcal_search_hit_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalSearchHit *self = GCAL_SEARCH_HIT (object); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, gcal_search_hit_get_id (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, gcal_search_hit_get_title (self)); + break; + + case PROP_SUBTITLE: + g_value_set_string (value, gcal_search_hit_get_subtitle (self)); + break; + + case PROP_PRIMARY_ICON: + g_value_take_object (value, gcal_search_hit_get_primary_icon (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalSearchHit *self = GCAL_SEARCH_HIT (object); + + switch (prop_id) + { + case PROP_ID: + gcal_search_hit_set_id (self, g_value_get_string (value)); + break; + + case PROP_TITLE: + gcal_search_hit_set_title (self, g_value_get_string (value)); + break; + + case PROP_SUBTITLE: + gcal_search_hit_set_subtitle (self, g_value_get_string (value)); + break; + + case PROP_PRIMARY_ICON: + gcal_search_hit_set_primary_icon (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_class_init (GcalSearchHitClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_search_hit_finalize; + object_class->get_property = gcal_search_hit_get_property; + object_class->set_property = gcal_search_hit_set_property; + + klass->activate = gcal_search_hit_real_activate; + klass->compare = gcal_search_hit_real_compare; + klass->get_priority = gcal_search_hit_real_get_priority; + + properties [PROP_ID] = + g_param_spec_string ("id", + "Id", + "The suggestion identifier", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties [PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "The title of the suggestion", + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SUBTITLE] = + g_param_spec_string ("subtitle", + "Subtitle", + "The subtitle of the suggestion", + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_PRIMARY_ICON] = + g_param_spec_object ("primary-icon", + "Primary icon", + "The primary icon for the suggestion", + GDK_TYPE_PAINTABLE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + +} + +static void +gcal_search_hit_init (GcalSearchHit *self) +{ +} + +/** + * gcal_search_hit_new: + * + * Create a new #GcalSearchHit. + * + * Returns: (transfer full): a newly created #GcalSearchHit + */ +GcalSearchHit * +gcal_search_hit_new (void) +{ + return g_object_new (GCAL_TYPE_SEARCH_HIT, NULL); +} + +const gchar * +gcal_search_hit_get_id (GcalSearchHit *self) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), NULL); + + return priv->id; +} + +void +gcal_search_hit_set_id (GcalSearchHit *self, + const gchar *id) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_if_fail (GCAL_IS_SEARCH_HIT (self)); + + if (g_strcmp0 (priv->id, id) != 0) + { + g_free (priv->id); + priv->id = g_strdup (id); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]); + } +} + +const gchar * +gcal_search_hit_get_title (GcalSearchHit *self) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), NULL); + + return priv->title; +} + +void +gcal_search_hit_set_title (GcalSearchHit *self, + const gchar *title) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_if_fail (GCAL_IS_SEARCH_HIT (self)); + + if (g_strcmp0 (priv->title, title) != 0) + { + g_clear_pointer (&priv->title, g_free); + priv->title = g_strdup (title); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); + } +} + +const gchar * +gcal_search_hit_get_subtitle (GcalSearchHit *self) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), NULL); + + return priv->subtitle; +} + +void +gcal_search_hit_set_subtitle (GcalSearchHit *self, + const gchar *subtitle) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_if_fail (GCAL_IS_SEARCH_HIT (self)); + + if (g_strcmp0 (priv->subtitle, subtitle) != 0) + { + g_clear_pointer (&priv->subtitle, g_free); + priv->subtitle = g_strdup (subtitle); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SUBTITLE]); + } +} + +GdkPaintable * +gcal_search_hit_get_primary_icon (GcalSearchHit *self) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), NULL); + + return priv->primary_icon; +} + +void +gcal_search_hit_set_primary_icon (GcalSearchHit *self, + GdkPaintable *primary_icon) +{ + GcalSearchHitPrivate *priv = gcal_search_hit_get_instance_private (self); + + g_return_if_fail (GCAL_IS_SEARCH_HIT (self)); + g_return_if_fail (!primary_icon || GDK_IS_PAINTABLE (primary_icon)); + + if (g_set_object (&priv->primary_icon, primary_icon)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIMARY_ICON]); +} + void gcal_search_hit_activate (GcalSearchHit *self, GtkWidget *for_widget) { g_return_if_fail (GCAL_IS_SEARCH_HIT (self)); - g_return_if_fail (GCAL_SEARCH_HIT_GET_IFACE (self)->activate); - GCAL_SEARCH_HIT_GET_IFACE (self)->activate (self, for_widget); + GCAL_SEARCH_HIT_GET_CLASS (self)->activate (self, for_widget); } gint gcal_search_hit_get_priority (GcalSearchHit *self) { g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), 0); - g_return_val_if_fail (GCAL_SEARCH_HIT_GET_IFACE (self)->get_priority, 0); - return GCAL_SEARCH_HIT_GET_IFACE (self)->get_priority (self); + return GCAL_SEARCH_HIT_GET_CLASS (self)->get_priority (self); } gint @@ -51,7 +334,7 @@ gcal_search_hit_compare (GcalSearchHit *a, GcalSearchHit *b) { g_return_val_if_fail (GCAL_IS_SEARCH_HIT (a), 0); - g_return_val_if_fail (GCAL_SEARCH_HIT_GET_IFACE (a)->compare, 0); + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (b), 0); - return GCAL_SEARCH_HIT_GET_IFACE (a)->compare (a, b); + return GCAL_SEARCH_HIT_GET_CLASS (a)->compare (a, b); } diff --git a/src/search/gcal-search-hit.h b/src/search/gcal-search-hit.h index 101b5cb4..46b9ff65 100644 --- a/src/search/gcal-search-hit.h +++ b/src/search/gcal-search-hit.h @@ -20,16 +20,16 @@ #pragma once -#include +#include G_BEGIN_DECLS #define GCAL_TYPE_SEARCH_HIT (gcal_search_hit_get_type ()) -G_DECLARE_INTERFACE (GcalSearchHit, gcal_search_hit, GCAL, SEARCH_HIT, DzlSuggestion) +G_DECLARE_DERIVABLE_TYPE (GcalSearchHit, gcal_search_hit, GCAL, SEARCH_HIT, GObject) -struct _GcalSearchHitInterface +struct _GcalSearchHitClass { - GTypeInterface parent; + GObjectClass parent_class; void (*activate) (GcalSearchHit *self, GtkWidget *for_widget); @@ -40,6 +40,28 @@ struct _GcalSearchHitInterface GcalSearchHit *b); }; +GcalSearchHit * gcal_search_hit_new (void); + +const gchar * gcal_search_hit_get_id (GcalSearchHit *self); + +void gcal_search_hit_set_id (GcalSearchHit *self, + const gchar *id); + +const gchar * gcal_search_hit_get_title (GcalSearchHit *self); + +void gcal_search_hit_set_title (GcalSearchHit *self, + const gchar *title); + +const gchar * gcal_search_hit_get_subtitle (GcalSearchHit *self); + +void gcal_search_hit_set_subtitle (GcalSearchHit *self, + const gchar *subtitle); + +GdkPaintable * gcal_search_hit_get_primary_icon (GcalSearchHit *self); + +void gcal_search_hit_set_primary_icon (GcalSearchHit *self, + GdkPaintable *paintable); + void gcal_search_hit_activate (GcalSearchHit *self, GtkWidget *for_widget); diff --git a/src/search/gcal-search-model.c b/src/search/gcal-search-model.c index ba359250..e1f2817d 100644 --- a/src/search/gcal-search-model.c +++ b/src/search/gcal-search-model.c @@ -29,8 +29,6 @@ #include "gcal-search-model.h" #include "gcal-utils.h" -#include - #define MIN_RESULTS 5 #define WAIT_FOR_RESULTS_MS 0.150 @@ -155,7 +153,7 @@ gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface) static GType gcal_search_model_get_item_type (GListModel *model) { - return DZL_TYPE_SUGGESTION; + return GCAL_TYPE_SEARCH_HIT; } static guint @@ -212,7 +210,7 @@ gcal_search_model_class_init (GcalSearchModelClass *klass) static void gcal_search_model_init (GcalSearchModel *self) { - self->model = (GListModel*) g_list_store_new (DZL_TYPE_SUGGESTION); + self->model = (GListModel*) g_list_store_new (GCAL_TYPE_SEARCH_HIT); g_signal_connect_object (self->model, "items-changed", G_CALLBACK (on_model_items_changed_cb), self, 0); } diff --git a/src/theme/Adwaita.css b/src/theme/Adwaita.css index 9b3826da..2c422331 100644 --- a/src/theme/Adwaita.css +++ b/src/theme/Adwaita.css @@ -507,3 +507,12 @@ monthpopover > box { .notes-section box > textview > text { background: none; } + +/* + * Search + */ + +searchbutton > popover > arrow { + background: none; + border: none; +}