/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * Copyright © 2019 Jan-Michael Brummer * * This file is part of Epiphany. * * Epiphany 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. * * Epiphany 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 Epiphany. If not, see . */ #include "config.h" #include "ephy-desktop-utils.h" #include "ephy-embed.h" #include "ephy-embed-utils.h" #include "ephy-settings.h" #include "ephy-tab-label.h" #define TAB_WIDTH_N_CHARS 15 struct _EphyTabLabel { GtkBox parent_instance; GtkWidget *spinner; GtkWidget *icon; GtkWidget *label; GtkWidget *close_button; GtkWidget *audio_box; GtkWidget *audio_image; char *uri; gboolean is_pinned; gboolean is_loading; gboolean has_icon; }; enum { CLOSE_CLICKED, AUDIO_CLICKED, LAST_SIGNAL }; enum { PROP_0, PROP_LABEL_TEXT, PROP_LABEL_URI, PROP_ICON_BUF, PROP_SPINNING, PROP_AUDIO, PROP_AUDIO_MUTED, PROP_PINNED, LAST_PROP }; static GParamSpec *obj_properties[LAST_PROP]; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (EphyTabLabel, ephy_tab_label, GTK_TYPE_BOX) static void ephy_tab_label_set_spinning (EphyTabLabel *tab_label, gboolean is_spinning) { g_object_set (tab_label->spinner, "active", is_spinning, NULL); g_object_set (tab_label->icon, "visible", !is_spinning, NULL); g_object_set (tab_label->spinner, "visible", is_spinning, NULL); tab_label->is_loading = is_spinning; } static void ephy_tab_label_update_icon (EphyTabLabel *self) { if (!self->has_icon) { const char *favicon_name = ephy_get_fallback_favicon_name (self->uri, EPHY_FAVICON_TYPE_NO_MISSING_PLACEHOLDER); gtk_image_set_from_icon_name (GTK_IMAGE (self->icon), favicon_name, GTK_ICON_SIZE_MENU); } } static void ephy_tab_label_update_audio_muted (GtkWidget *image, gboolean muted) { gtk_image_set_from_icon_name (GTK_IMAGE (image), muted ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic", GTK_ICON_SIZE_MENU); } static void ephy_tab_label_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EphyTabLabel *self = EPHY_TAB_LABEL (object); const gchar *str; switch (prop_id) { case PROP_LABEL_TEXT: str = g_value_get_string (value); if (str && strlen (str) != 0) { gtk_label_set_text (GTK_LABEL (self->label), str); gtk_widget_set_tooltip_text (GTK_WIDGET (self), str); } break; case PROP_LABEL_URI: g_clear_pointer (&self->uri, g_free); self->uri = g_strdup (g_value_get_string (value)); if (self->is_loading && !ephy_embed_utils_is_no_show_address (self->uri)) { gtk_label_set_text (GTK_LABEL (self->label), self->uri); gtk_widget_set_tooltip_text (GTK_WIDGET (self), self->uri); } break; case PROP_ICON_BUF: if (g_value_get_object (value)) { cairo_surface_t *surface; surface = gdk_cairo_surface_create_from_pixbuf (g_value_get_object (value), 0, gtk_widget_get_window (GTK_WIDGET (self))); gtk_image_set_from_surface (GTK_IMAGE (self->icon), surface); cairo_surface_destroy (surface); self->has_icon = TRUE; } else { gtk_image_set_from_surface (GTK_IMAGE (self->icon), NULL); self->has_icon = FALSE; } ephy_tab_label_update_icon (self); break; case PROP_SPINNING: ephy_tab_label_set_spinning (self, g_value_get_boolean (value)); break; case PROP_AUDIO: gtk_widget_set_visible (self->audio_box, g_value_get_boolean (value)); break; case PROP_AUDIO_MUTED: ephy_tab_label_update_audio_muted (self->audio_image, g_value_get_boolean (value)); break; case PROP_PINNED: self->is_pinned = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ephy_tab_label_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EphyTabLabel *self = EPHY_TAB_LABEL (object); gboolean spinning = FALSE; switch (prop_id) { case PROP_LABEL_TEXT: g_value_set_string (value, gtk_label_get_text (GTK_LABEL (self->label))); break; case PROP_ICON_BUF: g_value_set_object (value, gtk_image_get_pixbuf (GTK_IMAGE (self->icon))); break; case PROP_SPINNING: g_object_get (self->spinner, "active", &spinning, NULL); g_value_set_boolean (value, spinning); break; case PROP_AUDIO: g_value_set_boolean (value, gtk_widget_get_visible (self->audio_box)); break; case PROP_AUDIO_MUTED: break; case PROP_PINNED: g_value_set_boolean (value, self->is_pinned); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean is_layout_reversed (void) { GtkSettings *settings; g_autofree char *layout = NULL; g_auto (GStrv) parts = NULL; settings = gtk_settings_get_default (); g_object_get (settings, "gtk-decoration-layout", &layout, NULL); parts = g_strsplit (layout, ":", 2); /* Invalid layout, don't even try */ if (g_strv_length (parts) < 2) return FALSE; return !!g_strrstr (parts[0], "close"); } static void update_layout (EphyTabLabel *self) { GtkWidget *widget = GTK_WIDGET (self); GtkContainer *container = GTK_CONTAINER (self); PangoFontMetrics *metrics; PangoContext *context; GtkStyleContext *style; PangoFontDescription *font_desc; gboolean expanded, reversed; int char_width, h, w; if (self->is_pinned) { gtk_widget_set_hexpand (self->spinner, FALSE); gtk_widget_set_hexpand (self->icon, FALSE); gtk_widget_set_halign (self->icon, GTK_ALIGN_FILL); gtk_widget_set_size_request (widget, -1, -1); return; } context = gtk_widget_get_pango_context (widget); style = gtk_widget_get_style_context (widget); gtk_style_context_get (style, gtk_style_context_get_state (style), "font", &font_desc, NULL); metrics = pango_context_get_metrics (context, font_desc, pango_context_get_language (context)); pango_font_description_free (font_desc); char_width = pango_font_metrics_get_approximate_digit_width (metrics); pango_font_metrics_unref (metrics); gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h); gtk_widget_set_size_request (widget, TAB_WIDTH_N_CHARS * PANGO_PIXELS (char_width) + 2 * w, -1); gtk_widget_set_size_request (self->close_button, w + 2, h + 2); expanded = !is_desktop_pantheon () && g_settings_get_boolean (EPHY_SETTINGS_UI, EPHY_PREFS_UI_EXPAND_TABS_BAR); reversed = is_layout_reversed (); gtk_widget_set_hexpand (self->icon, expanded); gtk_widget_set_hexpand (self->spinner, expanded); if (expanded) { gtk_widget_set_halign (self->icon, reversed ? GTK_ALIGN_START : GTK_ALIGN_END); gtk_widget_set_halign (self->spinner, reversed ? GTK_ALIGN_START : GTK_ALIGN_END); } else { gtk_widget_set_halign (self->icon, GTK_ALIGN_FILL); gtk_widget_set_halign (self->spinner, GTK_ALIGN_FILL); } gtk_container_child_set (container, self->spinner, "pack-type", reversed ? GTK_PACK_END : GTK_PACK_START, NULL); gtk_container_child_set (container, self->icon, "pack-type", reversed ? GTK_PACK_END : GTK_PACK_START, NULL); gtk_container_child_set (container, self->close_button, "pack-type", reversed ? GTK_PACK_START : GTK_PACK_END, "position", reversed ? 0 : 3, NULL); } static void notify_decoration_layout_cb (GtkSettings *settings, GParamSpec *spec, EphyTabLabel *self) { update_layout (self); } static void ephy_tab_label_init (EphyTabLabel *self) { GtkSettings *settings; gtk_widget_init_template (GTK_WIDGET (self)); self->has_icon = FALSE; settings = gtk_settings_get_default (); g_signal_connect_object (settings, "notify::gtk-decoration-layout", G_CALLBACK (notify_decoration_layout_cb), self, 0); update_layout (self); } static void close_button_clicked_cb (GtkWidget *widget, EphyTabLabel *tab_label) { g_signal_emit (tab_label, signals[CLOSE_CLICKED], 0, NULL); } static void audio_box_button_release_event_cb (GtkWidget *widget, GdkEvent *event, EphyTabLabel *tab_label) { g_signal_emit (tab_label, signals[AUDIO_CLICKED], 0, NULL); } static void style_updated_cb (GtkWidget *widget, gpointer user_data) { EphyTabLabel *self = EPHY_TAB_LABEL (widget); update_layout (self); } static void ephy_tab_label_dispose (GObject *object) { EphyTabLabel *self = EPHY_TAB_LABEL (object); g_clear_pointer (&self->uri, g_free); G_OBJECT_CLASS (ephy_tab_label_parent_class)->dispose (object); } static void ephy_tab_label_class_init (EphyTabLabelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->set_property = ephy_tab_label_set_property; object_class->get_property = ephy_tab_label_get_property; object_class->dispose = ephy_tab_label_dispose; obj_properties[PROP_LABEL_TEXT] = g_param_spec_string ("label-text", "Label Text", "The displayed text", _("New Tab"), G_PARAM_READWRITE | G_PARAM_CONSTRUCT); obj_properties[PROP_LABEL_URI] = g_param_spec_string ("label-uri", "Label URI", "The displayed uri", "", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); obj_properties[PROP_ICON_BUF] = g_param_spec_object ("icon-buf", "Icon Buffer", "Buffer of the icon to be displayed", GDK_TYPE_PIXBUF, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); obj_properties[PROP_SPINNING] = g_param_spec_boolean ("spinning", "Spinning", "Is the spinner spinning", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); obj_properties[PROP_AUDIO] = g_param_spec_boolean ("audio", "Audio", "Is audio playing", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); obj_properties[PROP_AUDIO_MUTED] = g_param_spec_boolean ("audio-muted", "Audio Muted", "Is audio muted", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); obj_properties[PROP_PINNED] = g_param_spec_boolean ("pinned", "Pinned", "Is tab pinned", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_properties (object_class, LAST_PROP, obj_properties); signals[CLOSE_CLICKED] = g_signal_new ("close-clicked", EPHY_TYPE_TAB_LABEL, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[AUDIO_CLICKED] = g_signal_new ("audio-clicked", EPHY_TYPE_TAB_LABEL, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /* Bind class to template */ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/tab-label.ui"); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, spinner); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, icon); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, label); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, audio_box); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, audio_image); gtk_widget_class_bind_template_child (widget_class, EphyTabLabel, close_button); gtk_widget_class_bind_template_callback (widget_class, close_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, audio_box_button_release_event_cb); gtk_widget_class_bind_template_callback (widget_class, style_updated_cb); } GtkWidget * ephy_tab_label_new (void) { return g_object_new (EPHY_TYPE_TAB_LABEL, "homogeneous", FALSE, NULL); } const gchar * ephy_tab_label_get_text (GtkWidget *widget) { EphyTabLabel *self = EPHY_TAB_LABEL (widget); return gtk_label_get_text (GTK_LABEL (self->label)); } static void update_label (EphyTabLabel *self) { gtk_widget_set_visible (self->close_button, !self->is_pinned); gtk_widget_set_visible (self->label, !self->is_pinned); gtk_widget_set_halign (GTK_WIDGET (self), self->is_pinned ? GTK_ALIGN_CENTER : GTK_ALIGN_FILL); g_signal_emit_by_name (self, "style-updated", G_TYPE_NONE); } void ephy_tab_label_set_pinned (GtkWidget *widget, gboolean is_pinned) { EphyTabLabel *self = EPHY_TAB_LABEL (widget); self->is_pinned = is_pinned; ephy_tab_label_update_icon (self); update_label (self); g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_PINNED]); } gboolean ephy_tab_label_is_pinned (GtkWidget *widget) { EphyTabLabel *self = EPHY_TAB_LABEL (widget); return self->is_pinned; } void ephy_tab_label_set_needs_attention (GtkWidget *widget, gboolean attention) { if (attention) gtk_style_context_add_class (gtk_widget_get_style_context (widget), "tab-attention"); else gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "tab-attention"); }