gtk2/gtk/inspector/css-editor.c

560 lines
18 KiB
C
Raw Normal View History

/*
* 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"
2014-05-09 02:19:21 +00:00
#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"
#include "gtktooltip.h"
#include "gtktextiter.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,
2014-05-04 18:53:17 +00:00
COLUMN_USER
};
enum
{
PROP_0,
PROP_GLOBAL
};
typedef struct
{
gboolean enabled;
gboolean user;
} GtkInspectorCssEditorByContext;
struct _GtkInspectorCssEditorPrivate
{
2014-05-04 18:53:17 +00:00
GtkWidget *view;
GtkWidget *object_title;
GtkTextBuffer *text;
GtkCssProvider *provider;
gboolean global;
2014-05-09 02:19:21 +00:00
GtkStyleContext *context;
GtkToggleButton *disable_button;
guint timeout;
GList *errors;
};
typedef struct {
GError *error;
GtkTextIter start;
GtkTextIter end;
} CssError;
static void
css_error_free (gpointer data)
{
CssError *error = data;
g_error_free (error->error);
g_free (error);
}
static gboolean
query_tooltip_cb (GtkWidget *widget,
gint x,
gint y,
gboolean keyboard_tip,
GtkTooltip *tooltip,
GtkInspectorCssEditor *ce)
{
GtkTextIter iter;
GList *l;
if (keyboard_tip)
{
gint offset;
g_object_get (ce->priv->text, "cursor-position", &offset, NULL);
gtk_text_buffer_get_iter_at_offset (ce->priv->text, &iter, offset);
}
else
{
gint bx, by, trailing;
gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (ce->priv->view), GTK_TEXT_WINDOW_TEXT,
x, y, &bx, &by);
gtk_text_view_get_iter_at_position (GTK_TEXT_VIEW (ce->priv->view), &iter, &trailing, bx, by);
}
for (l = ce->priv->errors; l; l = l->next)
{
CssError *css_error = l->data;
if (gtk_text_iter_in_range (&iter, &css_error->start, &css_error->end))
{
gtk_tooltip_set_text (tooltip, css_error->error->message);
return TRUE;
}
}
return FALSE;
}
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
2014-05-09 02:19:21 +00:00
set_initial_text (GtkInspectorCssEditor *ce)
2014-05-04 18:53:17 +00:00
{
const gchar *text = NULL;
2014-05-09 02:19:21 +00:00
if (ce->priv->context)
text = g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_TEXT);
2014-05-04 18:53:17 +00:00
if (text)
2014-05-09 02:19:21 +00:00
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (ce->priv->text), text, -1);
2014-05-04 18:53:17 +00:00
else
{
gchar *initial_text;
2014-05-09 02:19:21 +00:00
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);
2014-05-09 02:19:21 +00:00
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (ce->priv->text), initial_text, -1);
g_free (initial_text);
}
2014-05-04 18:53:17 +00:00
}
static void
disable_toggled (GtkToggleButton *button,
2014-05-09 02:19:21 +00:00
GtkInspectorCssEditor *ce)
{
if (gtk_toggle_button_get_active (button))
{
2014-05-09 02:19:21 +00:00
if (ce->priv->global)
2014-05-04 18:53:17 +00:00
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
2014-05-09 02:19:21 +00:00
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
{
2014-05-09 02:19:21 +00:00
if (ce->priv->global)
2014-05-04 18:53:17 +00:00
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
2014-05-09 02:19:21 +00:00
GTK_STYLE_PROVIDER (ce->priv->provider),
2014-05-04 18:53:17 +00:00
GTK_STYLE_PROVIDER_PRIORITY_USER);
2014-05-09 02:19:21 +00:00
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)),
2014-05-04 18:53:17 +00:00
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;
2014-05-04 18:53:17 +00:00
gchar *text;
2014-05-09 02:19:21 +00:00
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;
g_list_free_full (ce->priv->errors, css_error_free);
ce->priv->errors = NULL;
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);
g_list_free_full (ce->priv->errors, css_error_free);
ce->priv->errors = NULL;
}
static void
show_parsing_error (GtkCssProvider *provider,
GtkCssSection *section,
const GError *error,
2014-05-09 02:19:21 +00:00
GtkInspectorCssEditor *ce)
{
const char *tag_name;
2014-05-09 02:19:21 +00:00
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (ce->priv->text);
CssError *css_error;
css_error = g_new (CssError, 1);
css_error->error = g_error_copy (error);
gtk_text_buffer_get_iter_at_line_index (buffer,
&css_error->start,
gtk_css_section_get_start_line (section),
gtk_css_section_get_start_position (section));
gtk_text_buffer_get_iter_at_line_index (buffer,
&css_error->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";
if (gtk_text_iter_equal (&css_error->start, &css_error->end))
gtk_text_iter_forward_char (&css_error->end);
gtk_text_buffer_apply_tag_by_name (buffer, tag_name, &css_error->start, &css_error->end);
ce->priv->errors = g_list_prepend (ce->priv->errors, css_error);
}
static void
2014-05-09 02:19:21 +00:00
create_provider (GtkInspectorCssEditor *ce)
{
GtkCssProvider *provider = gtk_css_provider_new ();
2014-05-09 02:19:21 +00:00
if (ce->priv->global)
{
2014-05-09 02:19:21 +00:00
ce->priv->provider = provider;
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
2014-05-09 02:19:21 +00:00
GTK_STYLE_PROVIDER (ce->priv->provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
}
2014-05-09 02:19:21 +00:00
else if (ce->priv->context)
{
2014-05-09 02:19:21 +00:00
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);
}
2014-05-04 18:53:17 +00:00
g_signal_connect (provider, "parsing-error",
2014-05-09 02:19:21 +00:00
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
2014-05-09 02:19:21 +00:00
gtk_inspector_css_editor_init (GtkInspectorCssEditor *ce)
{
2014-05-09 02:19:21 +00:00
ce->priv = gtk_inspector_css_editor_get_instance_private (ce);
gtk_widget_init_template (GTK_WIDGET (ce));
}
static void
constructed (GObject *object)
{
2014-05-09 02:19:21 +00:00
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
2014-05-09 02:19:21 +00:00
create_provider (ce);
set_initial_text (ce);
}
static void
get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec)
{
2014-05-09 02:19:21 +00:00
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
switch (param_id)
{
case PROP_GLOBAL:
2014-05-09 02:19:21 +00:00
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)
{
2014-05-09 02:19:21 +00:00
GtkInspectorCssEditor *ce = GTK_INSPECTOR_CSS_EDITOR (object);
switch (param_id)
{
case PROP_GLOBAL:
2014-05-09 02:19:21 +00:00
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_list_free_full (ce->priv->errors, css_error_free);
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);
2014-05-04 18:53:17 +00:00
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->get_property = get_property;
object_class->set_property = set_property;
2014-05-04 18:53:17 +00:00
object_class->constructed = constructed;
object_class->finalize = finalize;
2014-05-04 18:53:17 +00:00
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));
2014-11-30 20:59:53 +00:00
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);
2014-05-04 18:53:17 +00:00
gtk_widget_class_bind_template_callback (widget_class, disable_toggled);
gtk_widget_class_bind_template_callback (widget_class, save_clicked);
2014-05-04 18:53:17 +00:00
gtk_widget_class_bind_template_callback (widget_class, text_changed);
gtk_widget_class_bind_template_callback (widget_class, query_tooltip_cb);
}
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;
2014-05-09 02:19:21 +00:00
g_return_if_fail (GTK_INSPECTOR_IS_CSS_EDITOR (ce));
g_return_if_fail (!ce->priv->global);
2014-05-09 02:19:21 +00:00
if (ce->priv->context)
{
g_object_weak_unref (G_OBJECT (ce->priv->context), gtk_inspector_css_editor_remove_dead_object, ce);
2014-05-09 02:19:21 +00:00
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));
2014-05-09 02:19:21 +00:00
provider = g_object_get_data (G_OBJECT (ce->priv->context), GTK_INSPECTOR_CSS_EDITOR_PROVIDER);
if (!provider)
2014-05-09 02:19:21 +00:00
create_provider (ce);
2014-05-09 02:19:21 +00:00
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);
}
2014-05-04 18:53:17 +00:00
// vim: set et sw=2 ts=2: