diff --git a/build-aux/flatpak/org.gnome.Gtranslator.json b/build-aux/flatpak/org.gnome.Gtranslator.json index 2197323a7cfb78405fa1241f41dcbb2d2ac22345..acd3a887add274ddbd3fa6f616c7bba9cc988b05 100644 --- a/build-aux/flatpak/org.gnome.Gtranslator.json +++ b/build-aux/flatpak/org.gnome.Gtranslator.json @@ -39,6 +39,24 @@ "*.a" ], "modules" : [ + { + "name" : "libdazzle", + "config-opts" : [ + "--libdir=/app/lib", + "--buildtype=debugoptimized" + ], + "buildsystem" : "meson", + "builddir" : true, + "cleanup" : [ + "/bin" + ], + "sources" : [ + { + "type" : "git", + "url" : "https://gitlab.gnome.org/GNOME/libdazzle.git" + } + ] + }, { "name" : "gspell", "config-opts" : [ diff --git a/meson.build b/meson.build index d98ba7350d384a26c70ad25017f3e82709031648..b71c152808616f2e13c962fa336e74b69b15cdfe 100644 --- a/meson.build +++ b/meson.build @@ -107,6 +107,7 @@ gtk_dep = dependency('gtk+-3.0', version: '>= 3.22.20') gtr_deps = [ glib_dep, gtk_dep, + dependency('libdazzle-1.0', version: '>= 3.33.90'), dependency('libgda-5.0'), dependency('gio-2.0', version: '>= 2.36.0'), dependency('gsettings-desktop-schemas'), diff --git a/src/gtr-search-bar.c b/src/gtr-search-bar.c new file mode 100644 index 0000000000000000000000000000000000000000..ce6ae47c8acdc0d9503c9bf4e2e8e5111c9dd98c --- /dev/null +++ b/src/gtr-search-bar.c @@ -0,0 +1,361 @@ +#define G_LOG_DOMAIN "gtr-search-bar" + +#include "config.h" + +#include +#include + +#include "gtr-search-bar.h" + +struct _GtrSearchBar +{ + DzlBin parent_instance; + + DzlSignalGroup *search_signals; + DzlBindingGroup *search_bindings; + GtkSearchEntry *search; + + GObject *search_entry_tag; + + GtkCheckButton *case_sensitive; + GtkButton *replace_all_button; + GtkButton *replace_button; + GtkSearchEntry *replace_entry; + GtkEntry *search_entry; + GtkGrid *search_options; + GtkCheckButton *use_regex; + GtkCheckButton *whole_word; + GtkLabel *search_text_error; + + guint match_source; + + guint show_options : 1; + guint replace_mode : 1; +}; + +enum { + PROP_0, + PROP_REPLACE_MODE, + PROP_SHOW_OPTIONS, + N_PROPS +}; + +enum { + STOP_SEARCH, + N_SIGNALS +}; + +G_DEFINE_TYPE (GtrSearchBar, gtr_search_bar, DZL_TYPE_BIN) + +static GParamSpec *properties [N_PROPS]; +static guint signals [N_SIGNALS]; + + +static void +search_entry_populate_popup (GtrSearchBar *self, + GtkWidget *widget, + GtkEntry *entry) +{ + g_assert (GTR_IS_SEARCH_BAR (self)); + g_assert (GTK_IS_MENU (widget)); + g_assert (GTK_IS_ENTRY (entry)); + + if (GTK_IS_MENU (widget)) + { + g_autoptr(DzlPropertiesGroup) group = NULL; + + GtkWidget *item; + GtkWidget *sep; + guint pos = 0; + + item = gtk_check_menu_item_new_with_label (_("Regular ")); + gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "search-settings.regex-enabled"); + gtk_menu_shell_insert (GTK_MENU_SHELL (widget), item, pos++); + gtk_widget_show (item); + + item = gtk_check_menu_item_new_with_label (_("Case sensitive")); + gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "search-settings.case-sensitive"); + gtk_menu_shell_insert (GTK_MENU_SHELL (widget), item, pos++); + gtk_widget_show (item); + + item = gtk_check_menu_item_new_with_label (_("Match whole word only")); + gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "search-settings.at-word-boundaries"); + gtk_menu_shell_insert (GTK_MENU_SHELL (widget), item, pos++); + gtk_widget_show (item); + + sep = gtk_separator_menu_item_new (); + gtk_menu_shell_insert (GTK_MENU_SHELL (widget), sep, pos++); + gtk_widget_show (sep); + + if (self->search != NULL) + { + group = dzl_properties_group_new (G_OBJECT (self->search)); + dzl_properties_group_add_all_properties (group); + } + + gtk_widget_insert_action_group (widget, "search-settings", G_ACTION_GROUP (group)); + } +} + +static void +gtr_search_bar_real_stop_search (GtrSearchBar *self) +{ + g_assert (GTR_IS_SEARCH_BAR (self)); +} + +static gboolean +pacify_null_text (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + g_assert (from_value != NULL); + g_assert (to_value != NULL); + g_assert (G_VALUE_HOLDS_STRING (from_value)); + g_assert (G_VALUE_HOLDS_STRING (to_value)); + + if (g_value_get_string (from_value) == NULL) + g_value_set_static_string (to_value, ""); + else + g_value_copy (from_value, to_value); + + return TRUE; +} + + +static gboolean +maybe_escape_regex (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + GtrSearchBar *self = user_data; + const gchar *entry_text; + + g_assert (GTR_IS_SEARCH_BAR (self)); + g_assert (from_value != NULL); + g_assert (to_value != NULL); + + entry_text = g_value_get_string (from_value); + + if (entry_text == NULL) + { + g_value_set_static_string (to_value, ""); + } + else + { + g_autofree gchar *unescaped = NULL; + + if (self->search != NULL //&& !ide_editor_search_get_regex_enabled (self->search) + ) + entry_text = unescaped = gtk_source_utils_unescape_search_text (entry_text); + + g_value_set_string (to_value, entry_text); + } + + return TRUE; +} + +gboolean +gtr_search_bar_get_show_options (GtrSearchBar *self) +{ + g_return_val_if_fail (GTR_IS_SEARCH_BAR (self), FALSE); + + return self->show_options; +} + +gboolean +gtr_search_bar_get_replace_mode (GtrSearchBar *self) +{ + g_return_val_if_fail (GTK_IS_SEARCH_BAR (self), FALSE); + + return self->replace_mode; +} + +static void +gtr_search_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtrSearchBar *self = GTR_SEARCH_BAR (object); + + switch (prop_id) + { + case PROP_REPLACE_MODE: + g_value_set_boolean (value, gtr_search_bar_get_replace_mode (self)); + break; + + case PROP_SHOW_OPTIONS: + g_value_set_boolean (value, gtr_search_bar_get_show_options (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +void +gtr_search_bar_set_replace_mode (GtrSearchBar *self, + gboolean replace_mode) +{ + g_return_if_fail (GTR_IS_SEARCH_BAR (self)); + + replace_mode = !!replace_mode; + + if (replace_mode != self->replace_mode) + { + self->replace_mode = replace_mode; + gtk_widget_set_visible (GTK_WIDGET (self->replace_entry), replace_mode); + gtk_widget_set_visible (GTK_WIDGET (self->replace_button), replace_mode); + gtk_widget_set_visible (GTK_WIDGET (self->replace_all_button), replace_mode); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_REPLACE_MODE]); + } +} + + +void +gtr_search_bar_set_show_options (GtrSearchBar *self, + gboolean show_options) +{ + g_return_if_fail (GTR_IS_SEARCH_BAR (self)); + + show_options = !!show_options; + + if (self->show_options != show_options) + { + self->show_options = show_options; + gtk_widget_set_visible (GTK_WIDGET (self->search_options), show_options); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_OPTIONS]); + } +} + + +static void +gtr_search_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtrSearchBar *self = GTR_SEARCH_BAR (object); + + switch (prop_id) + { + case PROP_REPLACE_MODE: + gtr_search_bar_set_replace_mode (self, g_value_get_boolean (value)); + break; + + case PROP_SHOW_OPTIONS: + gtr_search_bar_set_show_options (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtr_search_bar_class_init (GtrSearchBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gtr_search_bar_get_property; + object_class->set_property = gtr_search_bar_set_property; + + properties [PROP_REPLACE_MODE] = + g_param_spec_boolean ("replace-mode", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties [PROP_SHOW_OPTIONS] = + g_param_spec_boolean ("show-options", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals [STOP_SEARCH] = + g_signal_new_class_handler ("stop-search", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_CALLBACK (gtr_search_bar_real_stop_search), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/translator/gtr-search-bar.ui"); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, case_sensitive); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, replace_all_button); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, replace_button); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, replace_entry); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, search_entry); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, search_options); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, search_text_error); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, use_regex); + gtk_widget_class_bind_template_child (widget_class, GtrSearchBar, whole_word); + + gtk_widget_class_set_css_name (widget_class, "gtrsearchbar"); +} + +static void +gtr_search_bar_init (GtrSearchBar *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->search_signals = dzl_signal_group_new (GTK_TYPE_SEARCH_ENTRY); + + self->search_bindings = dzl_binding_group_new (); + + dzl_binding_group_bind_full (self->search_bindings, "search-text", + self->search_entry, "text", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL, + maybe_escape_regex, pacify_null_text, self, NULL); + + dzl_binding_group_bind_full (self->search_bindings, "replacement-text", + self->replace_entry, "text", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL, + pacify_null_text, pacify_null_text, NULL, NULL); + + dzl_binding_group_bind (self->search_bindings, "regex-enabled", + self->use_regex, "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + + dzl_binding_group_bind (self->search_bindings, "case-sensitive", + self->case_sensitive, "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + + dzl_binding_group_bind (self->search_bindings, "at-word-boundaries", + self->whole_word, "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + + + g_signal_connect_swapped (self->search_entry, + "populate-popup", + G_CALLBACK (search_entry_populate_popup), + self); + +} + + +GtkSearchEntry * +gtr_search_bar_get_search (GtrSearchBar *self) +{ + g_return_val_if_fail (GTR_IS_SEARCH_BAR (self), NULL); + + return self->search; +} + +void +gtr_search_bar_set_search (GtrSearchBar *self, + GtkSearchEntry *search) +{ + g_return_if_fail (GTR_IS_SEARCH_BAR (self)); + + if (g_set_object (&self->search, search)) + { + dzl_signal_group_set_target (self->search_signals, search); + dzl_binding_group_set_source (self->search_bindings, search); + } +} + + diff --git a/src/gtr-search-bar.h b/src/gtr-search-bar.h new file mode 100644 index 0000000000000000000000000000000000000000..2a62d766466f055eef32df56861a7c69108de875 --- /dev/null +++ b/src/gtr-search-bar.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define GTR_TYPE_SEARCH_BAR (gtr_search_bar_get_type()) + + +G_DECLARE_FINAL_TYPE (GtrSearchBar, gtr_search_bar, GTR, SEARCH_BAR, DzlBin) + + +GtkSearchEntry *gtr_search_bar_get_search (GtrSearchBar *self); +void gtr_search_bar_set_search (GtrSearchBar *self, + GtkSearchEntry *search); +gboolean gtr_search_bar_get_show_options (GtrSearchBar *self); +void gtr_search_bar_set_show_options (GtrSearchBar *self, + gboolean show_options); +gboolean gtr_search_bar_get_replace_mode (GtrSearchBar *self); +void gtr_search_bar_set_replace_mode (GtrSearchBar *self, + gboolean replace_mode); + +G_END_DECLS + diff --git a/src/gtr-search-bar.ui b/src/gtr-search-bar.ui new file mode 100644 index 0000000000000000000000000000000000000000..4fedfa19bf897e7f06eeac75af11c994fe15f9dc --- /dev/null +++ b/src/gtr-search-bar.ui @@ -0,0 +1,261 @@ + + + + + diff --git a/src/gtr-tab.c b/src/gtr-tab.c index 94b65249d1612971f841eae8dcdb81940c5aca68..d88afd5d7451bd502d57e7d68e60513cc505187d 100644 --- a/src/gtr-tab.c +++ b/src/gtr-tab.c @@ -46,6 +46,7 @@ #include "gtr-debug.h" #include "gtr-window.h" #include "gtr-progress.h" +#include "gtr-search-bar.h" #include #include @@ -113,6 +114,12 @@ typedef struct guint tab_realized : 1; guint dispose_has_run : 1; + + /*Search Bar*/ + GtkOverlay *overlay; + GtkRevealer *search_revealer; + GtrSearchBar *search_bar; + GtkSearchEntry *search; } GtrTabPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GtrTab, gtr_tab, GTK_TYPE_BOX) @@ -138,6 +145,49 @@ static guint signals[LAST_SIGNAL]; static gboolean gtr_tab_autosave (GtrTab * tab); +//---------------------------Search Bar Revealer------------------// + +static void +gtr_page_stop_search (GtrTab *tab, + GtrSearchBar *search_bar) +{ + GtrTabPrivate *priv; + + priv = gtr_tab_get_instance_private (tab); + g_assert (GTR_IS_TAB (tab)); + g_assert (GTR_IS_SEARCH_BAR (priv->search_bar)); + + gtk_revealer_set_reveal_child (priv->search_revealer, FALSE); + +} + +static void +gtr_page_notify_child_revealed (GtrTab *tab, + GParamSpec *pspec, + GtkRevealer *revealer) +{ + GtrTabPrivate *priv; + + priv = gtr_tab_get_instance_private (tab); + g_assert (GTR_IS_TAB (tab)); + g_assert (GTK_IS_REVEALER (revealer)); + + if (gtk_revealer_get_child_revealed (revealer)) + { + GtkWidget *toplevel = gtk_widget_get_ancestor (GTK_WIDGET (revealer), GTK_TYPE_WINDOW); + GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); + + /* Only focus the search bar if it doesn't already have focus, + * as it can reselect the search text. + */ + if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (revealer))) + gtk_widget_grab_focus (GTK_WIDGET (priv->search_bar)); + } +} + +//----------------------------------------------------------------// + + static gboolean show_hide_revealer (GtkWidget *widget, GdkEvent *ev, GtrTab *tab) { @@ -867,10 +917,16 @@ gtr_tab_class_init (GtrTabClass * klass) gtk_widget_class_bind_template_child_private (widget_class, GtrTab, progress_fuzzy); gtk_widget_class_bind_template_child_private (widget_class, GtrTab, progress_untrans); gtk_widget_class_bind_template_child_private (widget_class, GtrTab, progress_percentage); + gtk_widget_class_bind_template_child_private (widget_class, GtrTab, overlay); + gtk_widget_class_bind_template_child_private (widget_class, GtrTab, search_bar); + gtk_widget_class_bind_template_child_private (widget_class, GtrTab, search_revealer); + gtk_widget_class_bind_template_callback (widget_class, gtr_page_notify_child_revealed); + gtk_widget_class_bind_template_callback (widget_class, gtr_page_stop_search); g_type_ensure (gtr_view_get_type ()); g_type_ensure (gtr_context_panel_get_type ()); g_type_ensure (gtr_message_table_get_type ()); + g_type_ensure (gtr_search_bar_get_type ()); } /***************************** Public funcs ***********************************/ diff --git a/src/gtr-tab.ui b/src/gtr-tab.ui index b40ba37ccc30d3e9ecd6e2849d2f869fc49ee5ad..0cce2093a0ad9ac4a336c27815f891cb6098a2dd 100644 --- a/src/gtr-tab.ui +++ b/src/gtr-tab.ui @@ -1,301 +1,325 @@ - - diff --git a/src/gtranslator.gresource.xml b/src/gtranslator.gresource.xml index 04d6c0dbbad2edc15767f91a6f5676bf14e0b1ec..b490a4fdc105a512f6670414956590e8c126567e 100644 --- a/src/gtranslator.gresource.xml +++ b/src/gtranslator.gresource.xml @@ -20,5 +20,6 @@ gtr-dl-teams.ui gtr-filter-selection.ui help-overlay.ui + gtr-search-bar.ui diff --git a/src/meson.build b/src/meson.build index be64e2d0df2a3b42c213a752634a7187bf9ec6b6..1c69190e5ee7fe2d04295c6d13afff9e92f725c1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,6 +20,7 @@ enum_headers = files( 'gtr-view.h', 'gtr-window-activatable.h', 'gtr-window.h', + 'gtr-search-bar.h', ) sources = files( @@ -68,6 +69,7 @@ sources = files( 'gtr-lang-button.c', 'gtr-progress.c', 'gtr-window.c', + 'gtr-search-bar.c', ) marshal = 'gtr-marshal' @@ -107,7 +109,8 @@ resource_data = files( 'gtr-search-dialog.ui', 'gtr-statusbar.ui', 'gtr-tab.ui', - 'gtr-window.ui' + 'gtr-window.ui', + 'gtr-search-bar.ui', ) sources += gnome.compile_resources( diff --git a/src/styles.css b/src/styles.css index 0abd42b3728e0102692454f82cc9ba15e0e12c52..ab0bb79442f9a54e536c202ebd36293193d261b1 100644 --- a/src/styles.css +++ b/src/styles.css @@ -39,3 +39,54 @@ #info_label.info { color: @theme_fg_color; } + +/* This file contains styling for various search widgets */ +.search-frame { + background-image: linear-gradient(shade(@theme_bg_color,1.05), @theme_bg_color); + padding: 6px; + border-style: solid; + border-color: @borders; + border-left-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + box-shadow: 0px 0 3px @borders; + margin: 0 10px 20px 10px; +} +frame.search-frame border { + border: none; +} +.search-frame > box > grid:first-child > button.close:disabled, +.search-frame > box > grid:first-child > button.close { + background: none; + border: none; + box-shadow: none; + padding-left: 4px; + padding-right: 4px; +} +.search-frame > box > grid:first-child > button.close image { + color: @theme_fg_color; + opacity: 0.3; + margin: 2px; + border: 1px solid transparent; + border-radius: 3px; +} +.search-frame > box > grid:first-child > button.close:hover image { + opacity: 0.75; + transition-duration: 250ms; + border: 1px solid @borders; +} +.search-frame > box > grid:first-child > button.close:active image { + opacity: 0.8; + background-image: linear-gradient(shade(@theme_bg_color, 0.9), @theme_bg_color); +} +.search-frame > box > grid:first-child > button.close:backdrop image { + opacity: 0.1; +} +.search-occurrences-tag { + color: shade (@theme_unfocused_fg_color, 0.8); + border: 0px; + margin: 2px; + padding: 2px; +}