forked from AuroraMiddleware/gtk
9d3e9c9375
The function is now safe to use. https://bugzilla.gnome.org/show_bug.cgi?id=735341
485 lines
16 KiB
C
485 lines
16 KiB
C
/*
|
|
* Copyright (c) 2013 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include "css-editor.h"
|
|
|
|
#include "gtkcssprovider.h"
|
|
#include "gtkstyleprovider.h"
|
|
#include "gtkstylecontext.h"
|
|
#include "gtktextview.h"
|
|
#include "gtkmessagedialog.h"
|
|
#include "gtkfilechooserdialog.h"
|
|
#include "gtktogglebutton.h"
|
|
#include "gtklabel.h"
|
|
|
|
#define GTK_INSPECTOR_CSS_EDITOR_TEXT "inspector-css-editor-text"
|
|
#define GTK_INSPECTOR_CSS_EDITOR_PROVIDER "inspector-css-editor-provider"
|
|
|
|
enum
|
|
{
|
|
COLUMN_ENABLED,
|
|
COLUMN_NAME,
|
|
COLUMN_USER
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_GLOBAL
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gboolean enabled;
|
|
gboolean user;
|
|
} GtkInspectorCssEditorByContext;
|
|
|
|
struct _GtkInspectorCssEditorPrivate
|
|
{
|
|
GtkWidget *view;
|
|
GtkWidget *object_title;
|
|
GtkTextBuffer *text;
|
|
GtkCssProvider *provider;
|
|
gboolean global;
|
|
GtkStyleContext *context;
|
|
GtkToggleButton *disable_button;
|
|
guint timeout;
|
|
};
|
|
|
|
static void gtk_inspector_css_editor_remove_dead_object (gpointer data,
|
|
GObject *dead_object);
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorCssEditor, gtk_inspector_css_editor, GTK_TYPE_BOX)
|
|
|
|
static void
|
|
set_initial_text (GtkInspectorCssEditor *ce)
|
|
{
|
|
const gchar *text = NULL;
|
|
|
|
if (ce->priv->context)
|
|
text = g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_TEXT);
|
|
if (text)
|
|
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (ce->priv->text), text, -1);
|
|
else
|
|
{
|
|
gchar *initial_text;
|
|
if (ce->priv->global)
|
|
initial_text = g_strconcat ("/*\n",
|
|
_("You can type here any CSS rule recognized by GTK+."), "\n",
|
|
_("You can temporarily disable this custom CSS by clicking on the “Pause” button above."), "\n\n",
|
|
_("Changes are applied instantly and globally, for the whole application."), "\n",
|
|
"*/\n\n", NULL);
|
|
else
|
|
initial_text = g_strconcat ("/*\n",
|
|
_("You can type here any CSS rule recognized by GTK+."), "\n",
|
|
_("You can temporarily disable this custom CSS by clicking on the “Pause” button above."), "\n\n",
|
|
_("Changes are applied instantly, only for this selected widget."), "\n",
|
|
"*/\n\n", NULL);
|
|
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (ce->priv->text), initial_text, -1);
|
|
g_free (initial_text);
|
|
}
|
|
}
|
|
|
|
static void
|
|
disable_toggled (GtkToggleButton *button,
|
|
GtkInspectorCssEditor *ce)
|
|
{
|
|
if (gtk_toggle_button_get_active (button))
|
|
{
|
|
if (ce->priv->global)
|
|
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
|
|
GTK_STYLE_PROVIDER (ce->priv->provider));
|
|
else if (ce->priv->context)
|
|
gtk_style_context_remove_provider (ce->priv->context,
|
|
GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_PROVIDER)));
|
|
}
|
|
else
|
|
{
|
|
if (ce->priv->global)
|
|
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
|
GTK_STYLE_PROVIDER (ce->priv->provider),
|
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
else if (ce->priv->context)
|
|
gtk_style_context_add_provider (ce->priv->context,
|
|
GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_PROVIDER)),
|
|
G_MAXUINT);
|
|
}
|
|
}
|
|
|
|
static gchar *
|
|
get_current_text (GtkTextBuffer *buffer)
|
|
{
|
|
GtkTextIter start, end;
|
|
|
|
gtk_text_buffer_get_start_iter (buffer, &start);
|
|
gtk_text_buffer_get_end_iter (buffer, &end);
|
|
gtk_text_buffer_remove_all_tags (buffer, &start, &end);
|
|
|
|
return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
|
}
|
|
|
|
static void
|
|
save_to_file (GtkInspectorCssEditor *ce,
|
|
const gchar *filename)
|
|
{
|
|
gchar *text;
|
|
GError *error = NULL;
|
|
|
|
text = get_current_text (ce->priv->text);
|
|
|
|
if (!g_file_set_contents (filename, text, -1, &error))
|
|
{
|
|
GtkWidget *dialog;
|
|
|
|
dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (ce))),
|
|
GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_INFO,
|
|
GTK_BUTTONS_OK,
|
|
_("Saving CSS failed"));
|
|
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
|
|
"%s", error->message);
|
|
g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
|
|
gtk_widget_show (dialog);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (text);
|
|
}
|
|
|
|
static void
|
|
save_response (GtkWidget *dialog,
|
|
gint response,
|
|
GtkInspectorCssEditor *ce)
|
|
{
|
|
gtk_widget_hide (dialog);
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
gchar *filename;
|
|
|
|
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
|
save_to_file (ce, filename);
|
|
g_free (filename);
|
|
}
|
|
|
|
gtk_widget_destroy (dialog);
|
|
}
|
|
|
|
static void
|
|
save_clicked (GtkButton *button,
|
|
GtkInspectorCssEditor *ce)
|
|
{
|
|
GtkWidget *dialog;
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("",
|
|
GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (ce))),
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_Save"), GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), "custom.css");
|
|
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
|
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
|
|
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
|
|
g_signal_connect (dialog, "response", G_CALLBACK (save_response), ce);
|
|
gtk_widget_show (dialog);
|
|
}
|
|
|
|
static void
|
|
update_style (GtkInspectorCssEditor *ce)
|
|
{
|
|
GtkCssProvider *provider;
|
|
gchar *text;
|
|
|
|
if (ce->priv->global)
|
|
provider = ce->priv->provider;
|
|
else if (ce->priv->context)
|
|
provider = g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_PROVIDER);
|
|
else
|
|
return;
|
|
|
|
text = get_current_text (ce->priv->text);
|
|
gtk_css_provider_load_from_data (provider, text, -1, NULL);
|
|
g_free (text);
|
|
|
|
gtk_style_context_reset_widgets (gdk_screen_get_default ());
|
|
}
|
|
|
|
static gboolean
|
|
update_timeout (gpointer data)
|
|
{
|
|
GtkInspectorCssEditor *ce = data;
|
|
|
|
ce->priv->timeout = 0;
|
|
|
|
update_style (ce);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
text_changed (GtkTextBuffer *buffer,
|
|
GtkInspectorCssEditor *ce)
|
|
{
|
|
if (ce->priv->timeout != 0)
|
|
g_source_remove (ce->priv->timeout);
|
|
|
|
ce->priv->timeout = g_timeout_add (100, update_timeout, ce);
|
|
}
|
|
|
|
static void
|
|
show_parsing_error (GtkCssProvider *provider,
|
|
GtkCssSection *section,
|
|
const GError *error,
|
|
GtkInspectorCssEditor *ce)
|
|
{
|
|
GtkTextIter start, end;
|
|
const char *tag_name;
|
|
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (ce->priv->text);
|
|
|
|
gtk_text_buffer_get_iter_at_line_index (buffer,
|
|
&start,
|
|
gtk_css_section_get_start_line (section),
|
|
gtk_css_section_get_start_position (section));
|
|
gtk_text_buffer_get_iter_at_line_index (buffer,
|
|
&end,
|
|
gtk_css_section_get_end_line (section),
|
|
gtk_css_section_get_end_position (section));
|
|
|
|
if (g_error_matches (error, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_DEPRECATED))
|
|
tag_name = "warning";
|
|
else
|
|
tag_name = "error";
|
|
|
|
gtk_text_buffer_apply_tag_by_name (buffer, tag_name, &start, &end);
|
|
}
|
|
|
|
static void
|
|
create_provider (GtkInspectorCssEditor *ce)
|
|
{
|
|
GtkCssProvider *provider = gtk_css_provider_new ();
|
|
|
|
if (ce->priv->global)
|
|
{
|
|
ce->priv->provider = provider;
|
|
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
|
GTK_STYLE_PROVIDER (ce->priv->provider),
|
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
}
|
|
else if (ce->priv->context)
|
|
{
|
|
gtk_style_context_add_provider (ce->priv->context,
|
|
GTK_STYLE_PROVIDER (provider),
|
|
G_MAXUINT);
|
|
g_object_set_data_full (G_OBJECT (ce->priv->context),
|
|
GTK_INSPECTOR_CSS_EDITOR_PROVIDER, provider,
|
|
g_object_unref);
|
|
}
|
|
|
|
g_signal_connect (provider, "parsing-error",
|
|
G_CALLBACK (show_parsing_error), ce);
|
|
}
|
|
|
|
static void
|
|
destroy_provider (GtkInspectorCssEditor *ce)
|
|
{
|
|
if (ce->priv->global)
|
|
{
|
|
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
|
|
GTK_STYLE_PROVIDER (ce->priv->provider));
|
|
g_object_unref (ce->priv->provider);
|
|
ce->priv->provider = NULL;
|
|
}
|
|
else if (ce->priv->context)
|
|
{
|
|
GtkStyleProvider *provider;
|
|
|
|
provider = g_object_get_data (G_OBJECT (ce->priv->context),
|
|
GTK_INSPECTOR_CSS_EDITOR_PROVIDER);
|
|
gtk_style_context_remove_provider (ce->priv->context, provider);
|
|
g_object_set_data (G_OBJECT (ce->priv->context),
|
|
GTK_INSPECTOR_CSS_EDITOR_PROVIDER,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gtk_inspector_css_editor_init (GtkInspectorCssEditor *ce)
|
|
{
|
|
ce->priv = gtk_inspector_css_editor_get_instance_private (ce);
|
|
gtk_widget_init_template (GTK_WIDGET (ce));
|
|
}
|
|
|
|
static void
|
|
constructed (GObject *object)
|
|
{
|
|
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
|
|
|
|
create_provider (ce);
|
|
set_initial_text (ce);
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object,
|
|
guint param_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
|
|
|
|
switch (param_id)
|
|
{
|
|
case PROP_GLOBAL:
|
|
g_value_set_boolean (value, ce->priv->global);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint param_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
|
|
|
|
switch (param_id)
|
|
{
|
|
case PROP_GLOBAL:
|
|
ce->priv->global = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
|
|
|
|
if (ce->priv->timeout != 0)
|
|
g_source_remove (ce->priv->timeout);
|
|
|
|
destroy_provider (ce);
|
|
if (ce->priv->context)
|
|
{
|
|
g_object_weak_unref (G_OBJECT (ce->priv->context), gtk_inspector_css_editor_remove_dead_object, ce);
|
|
g_object_set_data (G_OBJECT (ce->priv->context),
|
|
GTK_INSPECTOR_CSS_EDITOR_TEXT,
|
|
NULL);
|
|
ce->priv->context = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (gtk_inspector_css_editor_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gtk_inspector_css_editor_class_init (GtkInspectorCssEditorClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->constructed = constructed;
|
|
object_class->finalize = finalize;
|
|
|
|
g_object_class_install_property (object_class, PROP_GLOBAL,
|
|
g_param_spec_boolean ("global", "Global", "Whether this editor changes the whole application or just the selected widget",
|
|
TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/css-editor.ui");
|
|
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssEditor, text);
|
|
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssEditor, view);
|
|
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssEditor, disable_button);
|
|
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorCssEditor, object_title);
|
|
gtk_widget_class_bind_template_callback (widget_class, disable_toggled);
|
|
gtk_widget_class_bind_template_callback (widget_class, save_clicked);
|
|
gtk_widget_class_bind_template_callback (widget_class, text_changed);
|
|
}
|
|
|
|
static void
|
|
gtk_inspector_css_editor_remove_dead_object (gpointer data, GObject *dead_object)
|
|
{
|
|
GtkInspectorCssEditor *ce = data;
|
|
|
|
ce->priv->context = NULL;
|
|
gtk_widget_hide (GTK_WIDGET (ce));
|
|
}
|
|
|
|
void
|
|
gtk_inspector_css_editor_set_object (GtkInspectorCssEditor *ce,
|
|
GObject *object)
|
|
{
|
|
gchar *text;
|
|
GtkCssProvider *provider;
|
|
const gchar *title;
|
|
|
|
g_return_if_fail (GTK_INSPECTOR_IS_CSS_EDITOR (ce));
|
|
g_return_if_fail (!ce->priv->global);
|
|
|
|
if (ce->priv->context)
|
|
{
|
|
g_object_weak_unref (G_OBJECT (ce->priv->context), gtk_inspector_css_editor_remove_dead_object, ce);
|
|
text = get_current_text (GTK_TEXT_BUFFER (ce->priv->text));
|
|
g_object_set_data_full (G_OBJECT (ce->priv->context),
|
|
GTK_INSPECTOR_CSS_EDITOR_TEXT,
|
|
text, g_free);
|
|
ce->priv->context = NULL;
|
|
}
|
|
|
|
if (!GTK_IS_WIDGET (object))
|
|
{
|
|
gtk_widget_hide (GTK_WIDGET (ce));
|
|
return;
|
|
}
|
|
|
|
gtk_widget_show (GTK_WIDGET (ce));
|
|
|
|
title = (const gchar *)g_object_get_data (object, "gtk-inspector-object-title");
|
|
gtk_label_set_label (GTK_LABEL (ce->priv->object_title), title);
|
|
|
|
ce->priv->context = gtk_widget_get_style_context (GTK_WIDGET (object));
|
|
|
|
provider = g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_PROVIDER);
|
|
if (!provider)
|
|
create_provider (ce);
|
|
|
|
set_initial_text (ce);
|
|
disable_toggled (ce->priv->disable_button, ce);
|
|
|
|
g_object_weak_ref (G_OBJECT (ce->priv->context), gtk_inspector_css_editor_remove_dead_object, ce);
|
|
}
|
|
|
|
// vim: set et sw=2 ts=2:
|