diff --git a/configure.ac b/configure.ac index 70912f337a..195d8747ab 100644 --- a/configure.ac +++ b/configure.ac @@ -1898,6 +1898,8 @@ modules/printbackends/lpr/Makefile modules/printbackends/file/Makefile modules/printbackends/papi/Makefile modules/printbackends/test/Makefile +modules/other/Makefile +modules/other/parasite/Makefile ]) AC_OUTPUT diff --git a/modules/Makefile.am b/modules/Makefile.am index f8e7bb8797..34b26acf36 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -1,6 +1,6 @@ include $(top_srcdir)/Makefile.decl -SUBDIRS = input +SUBDIRS = input other if OS_UNIX SUBDIRS += printbackends diff --git a/modules/other/Makefile.am b/modules/other/Makefile.am new file mode 100644 index 0000000000..e1689249de --- /dev/null +++ b/modules/other/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = parasite diff --git a/modules/other/parasite/Makefile.am b/modules/other/parasite/Makefile.am new file mode 100644 index 0000000000..03243d61e8 --- /dev/null +++ b/modules/other/parasite/Makefile.am @@ -0,0 +1,52 @@ +moduledir = $(libdir)/gtk-3.0/modules + +module_LTLIBRARIES = libgtkparasite.la + +libgtkparasite_la_SOURCES = \ + inspect-button.c \ + module.c \ + parasite.h \ + prop-list.h \ + prop-list.c \ + property-cell-renderer.c \ + property-cell-renderer.h \ + python-hooks.c \ + python-hooks.h \ + python-shell.c \ + python-shell.h \ + widget-tree.h \ + widget-tree.c \ + window.c \ + button-path.h \ + button-path.c \ + classes-list.h \ + classes-list.c \ + css-editor.h \ + css-editor.c \ + object-hierarchy.h \ + object-hierarchy.c \ + themes.h \ + themes.c + +libgtkparasite_la_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/gtk \ + -I$(top_builddir)/gtk \ + -I$(top_srcdir)/gdk \ + -I$(top_builddir)/gdk \ + -DGTK_DATADIR=\"$(datadir)\" \ + -DGTK_COMPILATION \ + $(GTK_DEP_CFLAGS) \ + $(GTK_DEBUG_FLAGS) + +if PLATFORM_WIN32 +no_undefined = -no-undefined +endif + +libgtkparasite_la_LDFLAGS = -avoid-version -module $(no_undefined) + +libgtkparasite_la_LIBADD = \ + $(top_builddir)/gtk/libgtk-3.la \ + $(GTK_DEP_LIBS) + +-include $(top_srcdir)/git.mk diff --git a/modules/other/parasite/button-path.c b/modules/other/parasite/button-path.c new file mode 100644 index 0000000000..b8f35102fe --- /dev/null +++ b/modules/other/parasite/button-path.c @@ -0,0 +1,103 @@ +/* + * 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 "button-path.h" + +struct _ParasiteButtonPathPrivate +{ + GtkWidget *sw; + GtkWidget *button_box; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteButtonPath, parasite_buttonpath, GTK_TYPE_BOX) + +static void +parasite_buttonpath_init (ParasiteButtonPath *bp) +{ + GtkWidget *label; + + bp->priv = parasite_buttonpath_get_instance_private (bp); + + g_object_set (bp, + "orientation", GTK_ORIENTATION_HORIZONTAL, + NULL); + + bp->priv->sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_NEVER, + "hexpand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (bp), bp->priv->sw); + + bp->priv->button_box = g_object_new (GTK_TYPE_BOX, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "hexpand", TRUE, + "margin", 6, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (bp->priv->button_box), "linked"); + gtk_container_add (GTK_CONTAINER (bp->priv->sw), bp->priv->button_box); + + label = g_object_new (GTK_TYPE_LABEL, + "label", "Choose a widget through the inspector", + "xalign", 0.5, + "hexpand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (bp->priv->button_box), label); +} + +static void +parasite_buttonpath_class_init(ParasiteButtonPathClass *klass) +{ +} + +GtkWidget * +parasite_buttonpath_new () +{ + return GTK_WIDGET (g_object_new (PARASITE_TYPE_BUTTONPATH, NULL)); +} + +void +parasite_buttonpath_set_widget(ParasiteButtonPath *bp, + GtkWidget *widget) +{ + char *path, **words; + int i; + GtkWidget *b; + GtkContainer *box = GTK_CONTAINER (bp->priv->button_box); + + gtk_container_foreach (box, (GtkCallback)gtk_widget_destroy, NULL); + + path = gtk_widget_path_to_string (gtk_widget_get_path (widget)); + words = g_strsplit (path, " ", 0); + + for (i = 0; i < g_strv_length (words); i++) + { + b = gtk_button_new_with_label (words[i]); + gtk_widget_show (b); + gtk_container_add (box, b); + } + + g_strfreev (words); + g_free (path); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/button-path.h b/modules/other/parasite/button-path.h new file mode 100644 index 0000000000..e05424c3e2 --- /dev/null +++ b/modules/other/parasite/button-path.h @@ -0,0 +1,60 @@ +/* + * 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. + */ +#ifndef _GTKPARASITE_BUTTONPATH_H_ +#define _GTKPARASITE_BUTTONPATH_H_ + + +#include + +#define PARASITE_TYPE_BUTTONPATH (parasite_buttonpath_get_type()) +#define PARASITE_BUTTONPATH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_BUTTONPATH, ParasiteButtonPath)) +#define PARASITE_BUTTONPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_BUTTONPATH, ParasiteButtonPathClass)) +#define PARASITE_IS_BUTTONPATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_BUTTONPATH)) +#define PARASITE_IS_BUTTONPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_BUTTONPATH)) +#define PARASITE_BUTTONPATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_BUTTONPATH, ParasiteButtonPathClass)) + + +typedef struct _ParasiteButtonPathPrivate ParasiteButtonPathPrivate; + +typedef struct _ParasiteButtonPath { + GtkBox parent; + ParasiteButtonPathPrivate *priv; +} ParasiteButtonPath; + +typedef struct _ParasiteButtonPathClass { + GtkBoxClass parent; +} ParasiteButtonPathClass; + + +G_BEGIN_DECLS + +GType parasite_buttonpath_get_type (); +GtkWidget *parasite_buttonpath_new (); +void parasite_buttonpath_set_widget (ParasiteButtonPath *bp, + GtkWidget *widget); + +G_END_DECLS + + +#endif // _GTKPARASITE_BUTTONPATH_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/classes-list.c b/modules/other/parasite/classes-list.c new file mode 100644 index 0000000000..6164c4cdd5 --- /dev/null +++ b/modules/other/parasite/classes-list.c @@ -0,0 +1,344 @@ +/* + * 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 "classes-list.h" +#include "parasite.h" + +enum +{ + COLUMN_ENABLED, + COLUMN_NAME, + COLUMN_USER, + NUM_COLUMNS +}; + +typedef struct +{ + gboolean enabled; + gboolean user; +} ParasiteClassesListByContext; + +struct _ParasiteClassesListPrivate +{ + GtkWidget *toolbar; + GtkWidget *view; + GtkListStore *model; + GHashTable *contexts; + GtkStyleContext *current_context; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteClassesList, parasite_classeslist, GTK_TYPE_BOX) + +static void +enabled_toggled (GtkCellRendererToggle *renderer, gchar *path, ParasiteClassesList *cl) +{ + GtkTreeIter iter; + gboolean enabled; + GHashTable *context; + ParasiteClassesListByContext *c; + gchar *name; + + if (!gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (cl->priv->model), &iter, path)) + { + g_warning ("Parasite: Couldn't find the css class path for %s.", path); + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (cl->priv->model), &iter, + COLUMN_ENABLED, &enabled, + COLUMN_NAME, &name, + -1); + enabled = !enabled; + gtk_list_store_set (cl->priv->model, &iter, + COLUMN_ENABLED, enabled, + -1); + + context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context); + if (context) + { + c = g_hash_table_lookup (context, name); + if (c) + { + c->enabled = enabled; + if (enabled) + { + gtk_style_context_add_class (cl->priv->current_context, name); + } + else + { + gtk_style_context_remove_class (cl->priv->current_context, name); + } + } + else + { + g_warning ("Parasite: Couldn't find the css class %s in the class hash table.", name); + } + } + else + { + g_warning ("Parasite: Couldn't find the hash table for the style context for css class %s.", name); + } +} + +static void +add_clicked (GtkButton *button, ParasiteClassesList *cl) +{ + GtkWidget *dialog, *content_area, *entry; + + dialog = gtk_dialog_new_with_buttons ("New class", + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (cl))), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "_OK", GTK_RESPONSE_OK, + "Cancel", GTK_RESPONSE_CANCEL, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + entry = g_object_new (GTK_TYPE_ENTRY, + "visible", TRUE, + "margin", 5, + "placeholder-text", "Class name", + "activates-default", TRUE, + NULL); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_container_add (GTK_CONTAINER (content_area), entry); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + const gchar *name = gtk_entry_get_text (GTK_ENTRY (entry)); + GHashTable *context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context); + + if (*name && !g_hash_table_contains (context, name)) + { + GtkTreeIter tree_iter; + + gtk_style_context_add_class (cl->priv->current_context, name); + + ParasiteClassesListByContext *c = g_new0 (ParasiteClassesListByContext, 1); + c->enabled = TRUE; + c->user = TRUE; + g_hash_table_insert (context, (gpointer)g_strdup (name), c); + + gtk_list_store_append (cl->priv->model, &tree_iter); + gtk_list_store_set (cl->priv->model, &tree_iter, + COLUMN_ENABLED, TRUE, + COLUMN_NAME, name, + COLUMN_USER, TRUE, + -1); + } + } + + gtk_widget_destroy (dialog); +} + +static void +read_classes_from_style_context (ParasiteClassesList *cl) +{ + GList *l, *classes; + ParasiteClassesListByContext *c; + GtkTreeIter tree_iter; + GHashTable *hash_context; + + hash_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + classes = gtk_style_context_list_classes (cl->priv->current_context); + + for (l = classes; l; l = l->next) + { + c = g_new0 (ParasiteClassesListByContext, 1); + c->enabled = TRUE; + g_hash_table_insert (hash_context, g_strdup (l->data), c); + + gtk_list_store_append (cl->priv->model, &tree_iter); + gtk_list_store_set (cl->priv->model, &tree_iter, + COLUMN_ENABLED, TRUE, + COLUMN_NAME, l->data, + COLUMN_USER, FALSE, + -1); + } + g_list_free (classes); + g_hash_table_replace (cl->priv->contexts, cl->priv->current_context, hash_context); +} + +static void +restore_defaults_clicked (GtkButton *button, ParasiteClassesList *cl) +{ + GHashTableIter hash_iter; + gchar *name; + ParasiteClassesListByContext *c; + GHashTable *hash_context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context); + + g_hash_table_iter_init (&hash_iter, hash_context); + while (g_hash_table_iter_next (&hash_iter, (gpointer *)&name, (gpointer *)&c)) + { + if (c->user) + { + gtk_style_context_remove_class (cl->priv->current_context, name); + } + else if (!c->enabled) + { + gtk_style_context_add_class (cl->priv->current_context, name); + } + } + + gtk_list_store_clear (cl->priv->model); + read_classes_from_style_context (cl); +} + +static void +create_toolbar (ParasiteClassesList *cl) +{ + GtkWidget *button; + + cl->priv->toolbar = g_object_new (GTK_TYPE_TOOLBAR, + "icon-size", GTK_ICON_SIZE_SMALL_TOOLBAR, + "sensitive", FALSE, + NULL); + gtk_container_add (GTK_CONTAINER (cl), cl->priv->toolbar); + + button = g_object_new (GTK_TYPE_TOOL_BUTTON, + "icon-name", "add", + "tooltip-text", "Add a class", + NULL); + g_signal_connect (button, "clicked", G_CALLBACK (add_clicked), cl); + gtk_container_add (GTK_CONTAINER (cl->priv->toolbar), button); + + button = g_object_new (GTK_TYPE_TOOL_BUTTON, + "icon-name", "revert", + "tooltip-text", "Restore defaults for this widget", + NULL); + g_signal_connect (button, "clicked", G_CALLBACK (restore_defaults_clicked), cl); + gtk_container_add (GTK_CONTAINER (cl->priv->toolbar), button); +} + +static void +draw_name_column (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + ParasiteClassesList *cl) +{ + gboolean user; + + gtk_tree_model_get (model, iter, COLUMN_USER, &user, -1); + if (user) + { + g_object_set (renderer, "style", PANGO_STYLE_ITALIC, NULL); + } + else + { + g_object_set (renderer, "style", PANGO_STYLE_NORMAL, NULL); + } +} + +static void +parasite_classeslist_init (ParasiteClassesList *cl) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkWidget *sw; + + g_object_set (cl, "orientation", GTK_ORIENTATION_VERTICAL, NULL); + + cl->priv = parasite_classeslist_get_instance_private (cl); + + create_toolbar (cl); + + sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "expand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (cl), sw); + + cl->priv->contexts = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_hash_table_destroy); + cl->priv->model = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_BOOLEAN, // COLUMN_ENABLED + G_TYPE_STRING, // COLUMN_NAME + G_TYPE_BOOLEAN); // COLUMN_USER + cl->priv->view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cl->priv->model)); + gtk_container_add (GTK_CONTAINER (sw), cl->priv->view); + + renderer = gtk_cell_renderer_toggle_new (); + g_signal_connect (renderer, "toggled", G_CALLBACK (enabled_toggled), cl); + column = gtk_tree_view_column_new_with_attributes ("", renderer, + "active", COLUMN_ENABLED, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (cl->priv->view), column); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL); + column = gtk_tree_view_column_new_with_attributes ("Name", renderer, + "text", COLUMN_NAME, + NULL); + gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc)draw_name_column, cl, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (cl->priv->view), column); +} + +void +parasite_classeslist_set_widget (ParasiteClassesList *cl, + GtkWidget *widget) +{ + GtkStyleContext *widget_context; + GHashTable *hash_context; + GtkTreeIter tree_iter; + ParasiteClassesListByContext *c; + + gtk_list_store_clear (cl->priv->model); + + gtk_widget_set_sensitive (GTK_WIDGET (cl), TRUE); + widget_context = gtk_widget_get_style_context (widget); + + cl->priv->current_context = widget_context; + gtk_widget_set_sensitive (cl->priv->toolbar, TRUE); + + hash_context = g_hash_table_lookup (cl->priv->contexts, widget_context); + if (hash_context) + { + GHashTableIter hash_iter; + gchar *name; + + g_hash_table_iter_init (&hash_iter, hash_context); + while (g_hash_table_iter_next (&hash_iter, (gpointer *)&name, (gpointer *)&c)) + { + gtk_list_store_append (cl->priv->model, &tree_iter); + gtk_list_store_set (cl->priv->model, &tree_iter, + COLUMN_ENABLED, c->enabled, + COLUMN_NAME, name, + COLUMN_USER, c->user, + -1); + } + } + else + { + read_classes_from_style_context (cl); + } +} + +static void +parasite_classeslist_class_init (ParasiteClassesListClass *klass) +{ +} + +GtkWidget * +parasite_classeslist_new () +{ + return GTK_WIDGET (g_object_new (PARASITE_TYPE_CLASSESLIST, NULL)); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/classes-list.h b/modules/other/parasite/classes-list.h new file mode 100644 index 0000000000..19bc800e42 --- /dev/null +++ b/modules/other/parasite/classes-list.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef _GTKPARASITE_CLASSESLIST_H_ +#define _GTKPARASITE_CLASSESLIST_H_ + +#include + +#define PARASITE_TYPE_CLASSESLIST (parasite_classeslist_get_type()) +#define PARASITE_CLASSESLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_CLASSESLIST, ParasiteClassesList)) +#define PARASITE_CLASSESLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_CLASSESLIST, ParasiteClassesListClass)) +#define PARASITE_IS_CLASSESLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_CLASSESLIST)) +#define PARASITE_IS_CLASSESLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_CLASSESLIST)) +#define PARASITE_CLASSESLIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_CLASSESLIST, ParasiteClassesListClass)) + + +typedef struct _ParasiteClassesListPrivate ParasiteClassesListPrivate; + +typedef struct _ParasiteClassesList { + GtkBox parent; + ParasiteClassesListPrivate *priv; +} ParasiteClassesList; + +typedef struct _ParasiteClassesListClass { + GtkBoxClass parent; +} ParasiteClassesListClass; + +G_BEGIN_DECLS + +GType parasite_classeslist_get_type (); +GtkWidget *parasite_classeslist_new (); +void parasite_classeslist_set_widget (ParasiteClassesList* classeslist, + GtkWidget *widget); + +G_END_DECLS + +#endif // _GTKPARASITE_CLASSESLIST_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/css-editor.c b/modules/other/parasite/css-editor.c new file mode 100644 index 0000000000..ca4dd5b7a1 --- /dev/null +++ b/modules/other/parasite/css-editor.c @@ -0,0 +1,388 @@ +/* + * 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 "css-editor.h" +#include "parasite.h" + +#define PARASITE_CSSEDITOR_TEXT "parasite-csseditor-text" +#define PARASITE_CSSEDITOR_PROVIDER "parasite-csseditor-provider" + +enum +{ + COLUMN_ENABLED, + COLUMN_NAME, + COLUMN_USER, + NUM_COLUMNS +}; + +enum +{ + PROP_0, + PROP_GLOBAL +}; + +typedef struct +{ + gboolean enabled; + gboolean user; +} ParasiteCssEditorByContext; + +struct _ParasiteCssEditorPrivate +{ + GtkWidget *toolbar; + GtkTextBuffer *text; + GtkCssProvider *provider; + gboolean global; + GtkStyleContext *selected_context; + GtkToggleToolButton *disable_button; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteCssEditor, parasite_csseditor, GTK_TYPE_BOX) + +static const gchar *initial_text_global = + "/*\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"; +static const gchar *initial_text_widget = + "/*\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"; + +static void +disable_toggled (GtkToggleToolButton *button, ParasiteCssEditor *editor) +{ + if (gtk_toggle_tool_button_get_active (button)) + { + if (editor->priv->global) + { + gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (editor->priv->provider)); + } + else if (editor->priv->selected_context) + { + gtk_style_context_remove_provider (editor->priv->selected_context, + GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER))); + } + } + else + { + if (editor->priv->global) + { + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (editor->priv->provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + } + else if (editor->priv->selected_context) + { + gtk_style_context_add_provider (editor->priv->selected_context, + GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER)), + G_MAXUINT); + } + } +} + +static void +create_toolbar (ParasiteCssEditor *editor) +{ + editor->priv->toolbar = g_object_new (GTK_TYPE_TOOLBAR, + "icon-size", GTK_ICON_SIZE_SMALL_TOOLBAR, + NULL); + gtk_container_add (GTK_CONTAINER (editor), editor->priv->toolbar); + + editor->priv->disable_button = g_object_new (GTK_TYPE_TOGGLE_TOOL_BUTTON, + "icon-name", "media-playback-pause", + "tooltip-text", "Disable this custom css", + NULL); + g_signal_connect (editor->priv->disable_button, + "toggled", + G_CALLBACK (disable_toggled), + editor); + gtk_container_add (GTK_CONTAINER (editor->priv->toolbar), + GTK_WIDGET (editor->priv->disable_button)); +} + +static void +apply_system_font (GtkWidget *widget) +{ + GSettings *s = g_settings_new ("org.gnome.desktop.interface"); + gchar *font_name = g_settings_get_string (s, "monospace-font-name"); + PangoFontDescription *font_desc = pango_font_description_from_string (font_name); + + gtk_widget_override_font (widget, font_desc); + + pango_font_description_free (font_desc); + g_free (font_name); + g_object_unref (s); +} + +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 +text_changed (GtkTextBuffer *buffer, ParasiteCssEditor *editor) +{ + GtkCssProvider *provider; + char *text; + + if (editor->priv->global) + provider = editor->priv->provider; + else if (editor->priv->selected_context) + provider = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER); + else + return; + + text = get_current_text (buffer); + gtk_css_provider_load_from_data (provider, text, -1, NULL); + g_free (text); + + gtk_style_context_reset_widgets (gdk_screen_get_default ()); +} + +static void +show_parsing_error (GtkCssProvider *provider, + GtkCssSection *section, + const GError *error, + ParasiteCssEditor *editor) +{ + GtkTextIter start, end; + const char *tag_name; + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->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_text_widget (ParasiteCssEditor *editor) +{ + GtkWidget *sw, *view; + + editor->priv->text = gtk_text_buffer_new (NULL); + + if (editor->priv->global) + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_global, -1); + else + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_widget, -1); + + g_signal_connect (editor->priv->text, "changed", G_CALLBACK (text_changed), editor); + + gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (editor->priv->text), + "warning", + "underline", PANGO_UNDERLINE_SINGLE, + NULL); + gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (editor->priv->text), + "error", + "underline", PANGO_UNDERLINE_ERROR, + NULL); + + sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "expand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (editor), sw); + + view = g_object_new (GTK_TYPE_TEXT_VIEW, + "buffer", editor->priv->text, + "wrap-mode", GTK_WRAP_WORD, + NULL); + apply_system_font (view); + gtk_container_add (GTK_CONTAINER (sw), view); +} + +static void +create_provider (ParasiteCssEditor *editor) +{ + GtkCssProvider *provider = gtk_css_provider_new (); + + if (editor->priv->global) + { + editor->priv->provider = provider; + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (editor->priv->provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + } + else if (editor->priv->selected_context) + { + gtk_style_context_add_provider (editor->priv->selected_context, + GTK_STYLE_PROVIDER (provider), + G_MAXUINT); + g_object_set_data (G_OBJECT (editor->priv->selected_context), + PARASITE_CSSEDITOR_PROVIDER, + provider); + } + + g_signal_connect (provider, + "parsing-error", + G_CALLBACK (show_parsing_error), + editor); +} + +static void +parasite_csseditor_init (ParasiteCssEditor *editor) +{ + editor->priv = parasite_csseditor_get_instance_private (editor); +} + +static void +constructed (GObject *object) +{ + ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object); + + g_object_set (editor, + "orientation", GTK_ORIENTATION_VERTICAL, + "sensitive", editor->priv->global, + NULL); + + create_toolbar (editor); + create_provider (editor); + create_text_widget (editor); +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object); + + switch (param_id) + { + case PROP_GLOBAL: + g_value_set_boolean (value, editor->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) +{ + ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object); + + switch (param_id) + { + case PROP_GLOBAL: + editor->priv->global = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +static void +parasite_csseditor_class_init (ParasiteCssEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + + 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)); +} + +GtkWidget * +parasite_csseditor_new (gboolean global) +{ + return GTK_WIDGET (g_object_new (PARASITE_TYPE_CSSEDITOR, + "global", global, + NULL)); +} + +void +parasite_csseditor_set_widget (ParasiteCssEditor *editor, GtkWidget *widget) +{ + gchar *text; + GtkCssProvider *provider; + + g_return_if_fail (PARASITE_IS_CSSEDITOR (editor)); + g_return_if_fail (!editor->priv->global); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + + if (editor->priv->selected_context) + { + text = get_current_text (GTK_TEXT_BUFFER (editor->priv->text)); + g_object_set_data_full (G_OBJECT (editor->priv->selected_context), + PARASITE_CSSEDITOR_TEXT, + text, + g_free); + } + + editor->priv->selected_context = gtk_widget_get_style_context (widget); + + provider = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER); + if (!provider) + { + create_provider (editor); + } + + text = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_TEXT); + if (text) + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), text, -1); + else + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_widget, -1); + + disable_toggled (editor->priv->disable_button, editor); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/css-editor.h b/modules/other/parasite/css-editor.h new file mode 100644 index 0000000000..e7d58dd8ca --- /dev/null +++ b/modules/other/parasite/css-editor.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#ifndef _GTKPARASITE_CSSEDITOR_H_ +#define _GTKPARASITE_CSSEDITOR_H_ + +#include + +#define PARASITE_TYPE_CSSEDITOR (parasite_csseditor_get_type()) +#define PARASITE_CSSEDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_CSSEDITOR, ParasiteCssEditor)) +#define PARASITE_CSSEDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_CSSEDITOR, ParasiteCssEditorClass)) +#define PARASITE_IS_CSSEDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_CSSEDITOR)) +#define PARASITE_IS_CSSEDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_CSSEDITOR)) +#define PARASITE_CSSEDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_CSSEDITOR, ParasiteCssEditorClass)) + + +typedef struct _ParasiteCssEditorPrivate ParasiteCssEditorPrivate; + +typedef struct _ParasiteCssEditor { + GtkBox parent; + ParasiteCssEditorPrivate *priv; +} ParasiteCssEditor; + +typedef struct _ParasiteCssEditorClass { + GtkBoxClass parent; +} ParasiteCssEditorClass; + +G_BEGIN_DECLS + +GType parasite_csseditor_get_type (); + +GtkWidget *parasite_csseditor_new (gboolean global); +void parasite_csseditor_set_widget (ParasiteCssEditor *editor, + GtkWidget *widget); + +G_END_DECLS + +#endif // _GTKPARASITE_CSSEDITOR_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/inspect-button.c b/modules/other/parasite/inspect-button.c new file mode 100644 index 0000000000..b875ef6646 --- /dev/null +++ b/modules/other/parasite/inspect-button.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 "parasite.h" +#include "widget-tree.h" + +static void +on_inspect_widget(GtkWidget *grab_window, + GdkEventButton *event, + ParasiteWindow *parasite) +{ + gdk_device_ungrab (gtk_get_current_event_device (), event->time); + gtk_widget_hide(parasite->highlight_window); + + if (parasite->selected_window != NULL) + { + GtkWidget *toplevel = NULL; + GtkWidget *widget = NULL; + + gdk_window_get_user_data( + gdk_window_get_toplevel(parasite->selected_window), + (gpointer*)&toplevel); + + gdk_window_get_user_data (parasite->selected_window, (gpointer*)&widget); + + if (toplevel) + { + parasite_widget_tree_scan (PARASITE_WIDGET_TREE (parasite->widget_tree), + toplevel); + } + + if (widget) + { + parasite_widget_tree_select_object (PARASITE_WIDGET_TREE (parasite->widget_tree), + G_OBJECT (widget)); + } + } +} + +static void +on_highlight_window_show (GtkWidget *window, ParasiteWindow *parasite) +{ + if (gtk_widget_is_composited (parasite->window)) + { + gtk_widget_set_opacity (parasite->highlight_window, 0.2); + } + else + { + /* + * TODO: Do something different when there's no compositing manager. + * Draw a border or something. + */ + } +} + +static void +ensure_highlight_window (ParasiteWindow *parasite) +{ + GdkRGBA color; + + if (parasite->highlight_window != NULL) + return; + + color.red = 0; + color.green = 0; + color.blue = 65535; + color.alpha = 1; + + parasite->highlight_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_override_background_color (parasite->highlight_window, + GTK_STATE_NORMAL, + &color); + + g_signal_connect (parasite->highlight_window, "show", G_CALLBACK (on_highlight_window_show), parasite); +} + +static void +on_highlight_widget(GtkWidget *grab_window, + GdkEventMotion *event, + ParasiteWindow *parasite) +{ + GdkWindow *selected_window; + gint x, y, width, height; + + ensure_highlight_window (parasite); + + gtk_widget_hide (parasite->highlight_window); + + selected_window = gdk_device_get_window_at_position (gtk_get_current_event_device (), + NULL, + NULL); + if (selected_window == NULL) + { + /* This window isn't in-process. Ignore it. */ + parasite->selected_window = NULL; + return; + } + + if (gdk_window_get_toplevel(selected_window) == gtk_widget_get_window(parasite->window)) + { + /* Don't hilight things in the parasite window */ + parasite->selected_window = NULL; + return; + } + + parasite->selected_window = selected_window; + + gdk_window_get_origin (selected_window, &x, &y); + width = gdk_window_get_width (selected_window); + height = gdk_window_get_height (selected_window); + + gtk_window_move (GTK_WINDOW (parasite->highlight_window), x, y); + gtk_window_resize (GTK_WINDOW (parasite->highlight_window), width, height); + gtk_widget_show (parasite->highlight_window); +} + +static void +on_inspect_button_release (GtkWidget *button, + GdkEventButton *event, + ParasiteWindow *parasite) +{ + GdkCursor *cursor; + GdkEventMask events; + + events = GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK; + + if (parasite->grab_window == NULL) + { + parasite->grab_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_show(parasite->grab_window); + gtk_window_resize (GTK_WINDOW (parasite->grab_window), 1, 1); + gtk_window_move (GTK_WINDOW (parasite->grab_window), -100, -100); + gtk_widget_add_events (parasite->grab_window, events); + + g_signal_connect (parasite->grab_window, "button_release_event", G_CALLBACK (on_inspect_widget), parasite); + g_signal_connect (parasite->grab_window, "motion_notify_event", G_CALLBACK(on_highlight_widget), parasite); + } + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (button), GDK_CROSSHAIR); + gdk_device_grab (gtk_get_current_event_device (), + gtk_widget_get_window (parasite->grab_window), + GDK_OWNERSHIP_WINDOW, + FALSE, + events, + cursor, + event->time); + g_object_unref (cursor); +} + + +GtkWidget * +gtkparasite_inspect_button_new(ParasiteWindow *parasite) +{ + GtkWidget *button; + + button = gtk_button_new_from_icon_name ("find", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text (button, "Inspect"); + g_signal_connect(G_OBJECT(button), "button_release_event", + G_CALLBACK(on_inspect_button_release), parasite); + + return button; +} + +static gboolean +on_flash_timeout(ParasiteWindow *parasite) +{ + parasite->flash_count++; + + if (parasite->flash_count == 8) + { + parasite->flash_cnx = 0; + return FALSE; + } + + if (parasite->flash_count % 2 == 0) + { + if (gtk_widget_get_visible(parasite->highlight_window)) + gtk_widget_hide(parasite->highlight_window); + else + gtk_widget_show(parasite->highlight_window); + } + + return TRUE; +} + +void +gtkparasite_flash_widget(ParasiteWindow *parasite, GtkWidget *widget) +{ + gint x, y, width, height; + GdkWindow *parent_window; + + if (!gtk_widget_get_visible(widget) || !gtk_widget_get_mapped(widget)) + return; + + ensure_highlight_window(parasite); + + parent_window = gtk_widget_get_parent_window(widget); + if (parent_window != NULL) { + GtkAllocation alloc; + gtk_widget_get_allocation(widget, &alloc); + + gdk_window_get_origin(parent_window, &x, &y); + x += alloc.x; + y += alloc.y; + + width = alloc.width; + height = alloc.height; + + gtk_window_move(GTK_WINDOW(parasite->highlight_window), x, y); + gtk_window_resize(GTK_WINDOW(parasite->highlight_window), width, height); + gtk_widget_show(parasite->highlight_window); + + if (parasite->flash_cnx != 0) + g_source_remove(parasite->flash_cnx); + + parasite->flash_count = 0; + parasite->flash_cnx = g_timeout_add(150, (GSourceFunc)on_flash_timeout, + parasite); + } +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/module.c b/modules/other/parasite/module.c new file mode 100644 index 0000000000..3c7206068d --- /dev/null +++ b/modules/other/parasite/module.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 + +#include "config.h" +#include "parasite.h" +#include "python-hooks.h" + + +void +gtk_module_init(gint *argc, gchar ***argv) +{ +#ifdef ENABLE_PYTHON + parasite_python_init(); +#endif + + gtkparasite_window_create(); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/object-hierarchy.c b/modules/other/parasite/object-hierarchy.c new file mode 100644 index 0000000000..3dba5af95b --- /dev/null +++ b/modules/other/parasite/object-hierarchy.c @@ -0,0 +1,126 @@ +/* + * 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 "object-hierarchy.h" +#include "parasite.h" + +enum +{ + COLUMN_OBJECT_NAME, + NUM_COLUMNS +}; + +struct _ParasiteObjectHierarchyPrivate +{ + GtkTreeStore *model; + GtkTreeView *tree; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteObjectHierarchy, parasite_objecthierarchy, GTK_TYPE_BOX) + +static void +parasite_objecthierarchy_init (ParasiteObjectHierarchy *oh) +{ + oh->priv = parasite_objecthierarchy_get_instance_private (oh); +} + +static void +constructed (GObject *object) +{ + ParasiteObjectHierarchy *oh = PARASITE_OBJECTHIERARCHY (object); + GtkWidget *sw; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + g_object_set (object, + "orientation", GTK_ORIENTATION_VERTICAL, + NULL); + + sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "expand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (object), sw); + + oh->priv->model = gtk_tree_store_new (NUM_COLUMNS, + G_TYPE_STRING); // COLUMN_OBJECT_NAME + oh->priv->tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (oh->priv->model))); + gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (oh->priv->tree)); + + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL); + column = gtk_tree_view_column_new_with_attributes ("Object Hierarchy", renderer, + "text", COLUMN_OBJECT_NAME, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (oh->priv->tree), column); +} + +static void +parasite_objecthierarchy_class_init (ParasiteObjectHierarchyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = constructed; +} + +GtkWidget * +parasite_objecthierarchy_new (void) +{ + return GTK_WIDGET (g_object_new (PARASITE_TYPE_OBJECTHIERARCHY, + NULL)); +} + +void +parasite_objecthierarchy_set_object (ParasiteObjectHierarchy *oh, GObject *object) +{ + GObjectClass *klass = G_OBJECT_GET_CLASS (object); + const gchar *class_name; + GtkTreeIter iter, parent; + GSList *list = NULL, *l; + + gtk_tree_store_clear (oh->priv->model); + + do + { + class_name = G_OBJECT_CLASS_NAME (klass); + list = g_slist_append (list, (gpointer)class_name); + } + while ((klass = g_type_class_peek_parent (klass))) ; + list = g_slist_reverse (list); + + for (l = list; l; l = l->next) + { + gtk_tree_store_append (oh->priv->model, &iter, l == list ? NULL : &parent); + gtk_tree_store_set (oh->priv->model, &iter, + COLUMN_OBJECT_NAME, l->data, + -1); + parent = iter; + } + + g_slist_free (list); + + gtk_tree_view_expand_all (oh->priv->tree); + gtk_tree_selection_select_iter (gtk_tree_view_get_selection (oh->priv->tree), + &iter); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/object-hierarchy.h b/modules/other/parasite/object-hierarchy.h new file mode 100644 index 0000000000..6eab63a7ba --- /dev/null +++ b/modules/other/parasite/object-hierarchy.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef _GTKPARASITE_OBJECTHIERARCHY_H_ +#define _GTKPARASITE_OBJECTHIERARCHY_H_ + +#include + +#define PARASITE_TYPE_OBJECTHIERARCHY (parasite_objecthierarchy_get_type()) +#define PARASITE_OBJECTHIERARCHY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchy)) +#define PARASITE_OBJECTHIERARCHY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchyClass)) +#define PARASITE_IS_OBJECTHIERARCHY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_OBJECTHIERARCHY)) +#define PARASITE_IS_OBJECTHIERARCHY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_OBJECTHIERARCHY)) +#define PARASITE_OBJECTHIERARCHY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchyClass)) + + +typedef struct _ParasiteObjectHierarchyPrivate ParasiteObjectHierarchyPrivate; + +typedef struct _ParasiteObjectHierarchy { + GtkBox parent; + ParasiteObjectHierarchyPrivate *priv; +} ParasiteObjectHierarchy; + +typedef struct _ParasiteObjectHierarchyClass { + GtkBoxClass parent; +} ParasiteObjectHierarchyClass; + +G_BEGIN_DECLS + +GType parasite_objecthierarchy_get_type (void); +GtkWidget *parasite_objecthierarchy_new (void); +void parasite_objecthierarchy_set_object (ParasiteObjectHierarchy *editor, + GObject *object); + +G_END_DECLS + +#endif // _GTKPARASITE_OBJECTHIERARCHY_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/parasite.h b/modules/other/parasite/parasite.h new file mode 100644 index 0000000000..8ff6d0925c --- /dev/null +++ b/modules/other/parasite/parasite.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _GTKPARASITE_H_ +#define _GTKPARASITE_H_ + + +#include + + +#define TREE_TEXT_SCALE 0.8 +#define TREE_CHECKBOX_SIZE (gint)(0.8 * 13) + + +typedef struct +{ + GtkWidget *window; + GtkWidget *widget_tree; + GtkWidget *prop_list; + GtkWidget *python_shell; + GtkWidget *button_path; + GtkWidget *classes_list; + GtkWidget *widget_css_editor; + GtkWidget *oh; + + GtkWidget *grab_window; + GtkWidget *highlight_window; + + GtkWidget *widget_popup; + + GdkWindow *selected_window; + + gboolean edit_mode_enabled; + + int flash_count; + int flash_cnx; + +} ParasiteWindow; + + +void gtkparasite_window_create(); + +void gtkparasite_flash_widget(ParasiteWindow *parasite, GtkWidget *widget); + +GtkWidget *gtkparasite_inspect_button_new(ParasiteWindow *parasite); + + +#endif // _GTKPARASITE_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/prop-list.c b/modules/other/parasite/prop-list.c new file mode 100644 index 0000000000..90d395e0ea --- /dev/null +++ b/modules/other/parasite/prop-list.c @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 "parasite.h" +#include "prop-list.h" +#include "property-cell-renderer.h" + + +enum +{ + COLUMN_NAME, + COLUMN_VALUE, + COLUMN_DEFINED_AT, + COLUMN_OBJECT, + COLUMN_TOOLTIP, + COLUMN_RO, + NUM_COLUMNS +}; + +enum +{ + PROP_0, + PROP_WIDGET_TREE +}; + +struct _ParasitePropListPrivate +{ + GObject *object; + GtkListStore *model; + GHashTable *prop_iters; + GList *signal_cnxs; + GtkWidget *widget_tree; + GtkTreeViewColumn *property_column; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasitePropList, parasite_proplist, GTK_TYPE_TREE_VIEW) + +static void +parasite_proplist_init (ParasitePropList *pl) +{ + pl->priv = parasite_proplist_get_instance_private (pl); +} + +static gboolean +query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + ParasitePropList *pl) +{ + GtkTreeIter iter; + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreePath *path = NULL; + gchar *tooltip_text; + + if (!gtk_tree_view_get_tooltip_context (tree_view, + &x, + &y, + keyboard_tip, + &model, + &path, + &iter)) + return FALSE; + + gtk_tree_model_get (model, &iter, COLUMN_TOOLTIP, &tooltip_text, -1); + gtk_tooltip_set_text (tooltip, tooltip_text); + + gtk_tree_view_set_tooltip_cell (tree_view, + tooltip, + path, + pl->priv->property_column, + NULL); + + gtk_tree_path_free (path); + g_free (tooltip_text); + + return TRUE; +} + +static void +draw_columns (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + ParasitePropList *pl) +{ + gboolean ro; + + gtk_tree_model_get (model, iter, COLUMN_RO, &ro, -1); + if (ro) + { + g_object_set (renderer, "foreground", "#a7aba7", NULL); + } + else + { + g_object_set (renderer, "foreground-set", FALSE, NULL); + } +} + +static void +constructed (GObject *object) +{ + ParasitePropList *pl = PARASITE_PROPLIST (object); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + pl->priv->prop_iters = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) gtk_tree_iter_free); + + pl->priv->model = gtk_list_store_new(NUM_COLUMNS, + G_TYPE_STRING, // COLUMN_NAME + G_TYPE_STRING, // COLUMN_VALUE + G_TYPE_STRING, // COLUMN_DEFINED_AT + G_TYPE_OBJECT, // COLUMN_OBJECT + G_TYPE_STRING, // COLUMN_TOOLTIP + G_TYPE_BOOLEAN);// COLUMN_RO + gtk_tree_view_set_model (GTK_TREE_VIEW (pl), + GTK_TREE_MODEL (pl->priv->model)); + + renderer = gtk_cell_renderer_text_new(); + g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL); + pl->priv->property_column = gtk_tree_view_column_new_with_attributes ("Property", + renderer, + "text", COLUMN_NAME, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (pl), pl->priv->property_column); + g_object_set (pl->priv->property_column, + "resizable", TRUE, + "sort-order", GTK_SORT_ASCENDING, + "sort-column-id", COLUMN_NAME, + NULL); + gtk_tree_view_column_set_cell_data_func (pl->priv->property_column, + renderer, + (GtkTreeCellDataFunc) draw_columns, + pl, + NULL); + + renderer = parasite_property_cell_renderer_new (); + g_object_set_data (G_OBJECT (renderer), "parasite-widget-tree", pl->priv->widget_tree); + g_object_set (renderer, + "scale", TREE_TEXT_SCALE, + "editable", TRUE, + NULL); + column = gtk_tree_view_column_new_with_attributes ("Value", renderer, + "text", COLUMN_VALUE, + "object", COLUMN_OBJECT, + "name", COLUMN_NAME, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (pl), column); + gtk_tree_view_column_set_resizable (column, TRUE); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (pl->priv->model), + COLUMN_NAME, + GTK_SORT_ASCENDING); + gtk_tree_view_column_set_cell_data_func (column, + renderer, + (GtkTreeCellDataFunc) draw_columns, + pl, + NULL); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL); + column = gtk_tree_view_column_new_with_attributes ("Defined at", + renderer, + "text", COLUMN_DEFINED_AT, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (pl), column); + gtk_tree_view_column_set_cell_data_func (column, + renderer, + (GtkTreeCellDataFunc) draw_columns, + pl, + NULL); + + g_object_set (object, "has-tooltip", TRUE, NULL); + g_signal_connect (object, "query-tooltip", G_CALLBACK (query_tooltip_cb), pl); +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + ParasitePropList *pl = PARASITE_PROPLIST (object); + + switch (param_id) + { + case PROP_WIDGET_TREE: + g_value_take_object (value, pl->priv->widget_tree); + 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) +{ + ParasitePropList *pl = PARASITE_PROPLIST (object); + + switch (param_id) + { + case PROP_WIDGET_TREE: + pl->priv->widget_tree = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +parasite_proplist_class_init (ParasitePropListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + + g_object_class_install_property (object_class, + PROP_WIDGET_TREE, + g_param_spec_object ("widget-tree", + "Widget Tree", + "Widget tree", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +parasite_prop_list_update_prop (ParasitePropList *pl, + GtkTreeIter *iter, + GParamSpec *prop) +{ + GValue gvalue = {0}; + char *value; + + g_value_init(&gvalue, prop->value_type); + g_object_get_property (pl->priv->object, prop->name, &gvalue); + + if (G_VALUE_HOLDS_ENUM (&gvalue)) + { + GEnumClass *enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class; + GEnumValue *enum_value = g_enum_get_value(enum_class, g_value_get_enum(&gvalue)); + + value = g_strdup (enum_value->value_name); + } + else + { + value = g_strdup_value_contents(&gvalue); + } + + gtk_list_store_set (pl->priv->model, iter, + COLUMN_NAME, prop->name, + COLUMN_VALUE, value, + COLUMN_DEFINED_AT, g_type_name (prop->owner_type), + COLUMN_OBJECT, pl->priv->object, + COLUMN_TOOLTIP, g_param_spec_get_blurb (prop), + COLUMN_RO, !(prop->flags & G_PARAM_WRITABLE), + -1); + + g_free (value); + g_value_unset (&gvalue); +} + +static void +parasite_proplist_prop_changed_cb (GObject *pspec, + GParamSpec *prop, + ParasitePropList *pl) +{ + GtkTreeIter *iter = g_hash_table_lookup(pl->priv->prop_iters, prop->name); + + if (iter != NULL) + parasite_prop_list_update_prop (pl, iter, prop); +} + +GtkWidget * +parasite_proplist_new (GtkWidget *widget_tree) +{ + return g_object_new (PARASITE_TYPE_PROPLIST, + "widget-tree", widget_tree, + NULL); +} + +void +parasite_proplist_set_object (ParasitePropList* pl, GObject *object) +{ + GtkTreeIter iter; + GParamSpec **props; + guint num_properties; + guint i; + GList *l; + + pl->priv->object = object; + + for (l = pl->priv->signal_cnxs; l != NULL; l = l->next) + { + gulong id = GPOINTER_TO_UINT (l->data); + + if (g_signal_handler_is_connected (object, id)) + g_signal_handler_disconnect (object, id); + } + + g_list_free (pl->priv->signal_cnxs); + pl->priv->signal_cnxs = NULL; + + g_hash_table_remove_all (pl->priv->prop_iters); + gtk_list_store_clear (pl->priv->model); + + props = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &num_properties); + for (i = 0; i < num_properties; i++) + { + GParamSpec *prop = props[i]; + char *signal_name; + + if (! (prop->flags & G_PARAM_READABLE)) + continue; + + gtk_list_store_append (pl->priv->model, &iter); + parasite_prop_list_update_prop (pl, &iter, prop); + + g_hash_table_insert (pl->priv->prop_iters, (gpointer) prop->name, gtk_tree_iter_copy (&iter)); + + /* Listen for updates */ + signal_name = g_strdup_printf ("notify::%s", prop->name); + + pl->priv->signal_cnxs = + g_list_prepend (pl->priv->signal_cnxs, GINT_TO_POINTER( + g_signal_connect(object, signal_name, + G_CALLBACK (parasite_proplist_prop_changed_cb), + pl))); + + g_free (signal_name); + } +} + + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/prop-list.h b/modules/other/parasite/prop-list.h new file mode 100644 index 0000000000..d5eed223e0 --- /dev/null +++ b/modules/other/parasite/prop-list.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _GTKPARASITE_PROPLIST_H_ +#define _GTKPARASITE_PROPLIST_H_ + + +#include + +#define PARASITE_TYPE_PROPLIST (parasite_proplist_get_type()) +#define PARASITE_PROPLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PROPLIST, ParasitePropList)) +#define PARASITE_PROPLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PROPLIST, ParasitePropListClass)) +#define PARASITE_IS_PROPLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PROPLIST)) +#define PARASITE_IS_PROPLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PROPLIST)) +#define PARASITE_PROPLIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_PROPLIST, ParasitePropListClass)) + + +typedef struct _ParasitePropListPrivate ParasitePropListPrivate; + +typedef struct _ParasitePropList { + GtkTreeView parent; + ParasitePropListPrivate *priv; +} ParasitePropList; + +typedef struct _ParasitePropListClass { + GtkTreeViewClass parent; +} ParasitePropListClass; + + +G_BEGIN_DECLS + +GType parasite_proplist_get_type (void); +GtkWidget *parasite_proplist_new (GtkWidget *widget_tree); +void parasite_proplist_set_object (ParasitePropList *proplist, + GObject *object); + +G_END_DECLS + +#endif // _GTKPARASITE_PROPLIST_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/property-cell-renderer.c b/modules/other/parasite/property-cell-renderer.c new file mode 100644 index 0000000000..16a7d0fdc0 --- /dev/null +++ b/modules/other/parasite/property-cell-renderer.c @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 "parasite.h" +#include "property-cell-renderer.h" +#include "widget-tree.h" + +struct _ParasitePropertyCellRendererPrivate +{ + GObject *object; + char *name; +}; + +enum +{ + PROP_0, + PROP_OBJECT, + PROP_NAME +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasitePropertyCellRenderer, parasite_property_cell_renderer, GTK_TYPE_CELL_RENDERER_TEXT); + +static void +parasite_property_cell_renderer_init(ParasitePropertyCellRenderer *renderer) +{ + renderer->priv = parasite_property_cell_renderer_get_instance_private (renderer); +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + ParasitePropertyCellRenderer *r = PARASITE_PROPERTY_CELL_RENDERER (object); + + switch (param_id) + { + case PROP_OBJECT: + g_value_set_object(value, r->priv->object); + break; + + case PROP_NAME: + g_value_set_string(value, r->priv->name); + 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) +{ + ParasitePropertyCellRenderer *r = PARASITE_PROPERTY_CELL_RENDERER (object); + + switch (param_id) + { + case PROP_OBJECT: + r->priv->object = g_value_get_object (value); + break; + + case PROP_NAME: + g_free (r->priv->name); + r->priv->name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +static void +stop_editing(GtkCellEditable *editable, GtkCellRenderer *renderer) +{ + GObject *object; + const char *name; + GValue gvalue = {0}; + GParamSpec *prop; + + object = g_object_get_data(G_OBJECT(editable), "_prop_object"); + name = g_object_get_data(G_OBJECT(editable), "_prop_name"); + + prop = g_object_class_find_property(G_OBJECT_GET_CLASS(object), name); + g_value_init(&gvalue, prop->value_type); + + if (GTK_IS_ENTRY(editable)) + { + gboolean canceled; + g_object_get(editable, "editing_canceled", &canceled, NULL); + gtk_cell_renderer_stop_editing(renderer, canceled); + + if (canceled) + return; + + if (GTK_IS_SPIN_BUTTON(editable)) + { + double value = + g_ascii_strtod(gtk_entry_get_text(GTK_ENTRY(editable)), NULL); + + if (G_IS_PARAM_SPEC_INT(prop)) + g_value_set_int(&gvalue, (gint)value); + else if G_IS_PARAM_SPEC_UINT(prop) + g_value_set_uint(&gvalue, (guint)value); + else if G_IS_PARAM_SPEC_INT64(prop) + g_value_set_int64(&gvalue, (gint64)value); + else if G_IS_PARAM_SPEC_UINT64(prop) + g_value_set_uint64(&gvalue, (guint64)value); + else if G_IS_PARAM_SPEC_LONG(prop) + g_value_set_long(&gvalue, (glong)value); + else if G_IS_PARAM_SPEC_ULONG(prop) + g_value_set_ulong(&gvalue, (gulong)value); + else if G_IS_PARAM_SPEC_DOUBLE(prop) + g_value_set_double(&gvalue, (gdouble)value); + else + return; + } + else + { + g_value_set_string(&gvalue, + gtk_entry_get_text(GTK_ENTRY(editable))); + } + } + else if (GTK_IS_COMBO_BOX(editable)) + { + // We have no way of getting the canceled state for a GtkComboBox. + gtk_cell_renderer_stop_editing(renderer, FALSE); + + if (G_IS_PARAM_SPEC_BOOLEAN(prop)) + { + g_value_set_boolean(&gvalue, + gtk_combo_box_get_active(GTK_COMBO_BOX(editable)) == 1); + } + else if (G_IS_PARAM_SPEC_ENUM(prop)) + { + char *enum_name = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (editable)); + GEnumClass *enum_class; + GEnumValue *enum_value; + + if (enum_name == NULL) + return; + + enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class; + enum_value = g_enum_get_value_by_name(enum_class, enum_name); + g_value_set_enum(&gvalue, enum_value->value); + + g_free(enum_name); + } + } + + g_object_set_property(object, name, &gvalue); + g_value_unset(&gvalue); +} + +static GtkCellEditable * +start_editing (GtkCellRenderer *renderer, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + PangoFontDescription *font_desc; + GtkCellEditable *editable = NULL; + GObject *object; + const char *name; + GValue gvalue = {0}; + GParamSpec *prop; + + g_object_get(renderer, + "object", &object, + "name", &name, + NULL); + + prop = g_object_class_find_property(G_OBJECT_GET_CLASS(object), name); + g_value_init(&gvalue, prop->value_type); + g_object_get_property(object, name, &gvalue); + + if (G_VALUE_HOLDS_OBJECT (&gvalue)) + { + ParasiteWidgetTree *widget_tree = g_object_get_data (G_OBJECT (renderer), "parasite-widget-tree"); + GObject *prop_object = g_value_get_object (&gvalue); + GtkTreeIter iter; + + if (prop_object) + { + /* First check if the value is already in the tree (happens with 'parent' for instance) */ + if (parasite_widget_tree_find_object (widget_tree, prop_object, &iter)) + { + parasite_widget_tree_select_object (widget_tree, prop_object); + } + else if (parasite_widget_tree_find_object (widget_tree, object, &iter)) + { + parasite_widget_tree_append_object (widget_tree, prop_object, &iter); + parasite_widget_tree_select_object (widget_tree, prop_object); + } + else + { + g_warning ("Parasite: couldn't find the widget in the tree"); + } + } + g_value_unset (&gvalue); + return NULL; + } + else + { + if (!(prop->flags & G_PARAM_WRITABLE)) + return NULL; + + if (G_VALUE_HOLDS_ENUM(&gvalue) || G_VALUE_HOLDS_BOOLEAN(&gvalue)) + { + GtkWidget *combobox = gtk_combo_box_text_new (); + gtk_widget_show(combobox); + g_object_set(G_OBJECT(combobox), "has-frame", FALSE, NULL); + GList *renderers; + + if (G_VALUE_HOLDS_BOOLEAN(&gvalue)) + { + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox), "FALSE"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox), "TRUE"); + + gtk_combo_box_set_active(GTK_COMBO_BOX (combobox), g_value_get_boolean (&gvalue) ? 1 : 0); + } + else if (G_VALUE_HOLDS_ENUM(&gvalue)) + { + gint value = g_value_get_enum(&gvalue); + GEnumClass *enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class; + guint i; + + for (i = 0; i < enum_class->n_values; i++) + { + GEnumValue *enum_value = &enum_class->values[i]; + + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox), + enum_value->value_name); + + if (enum_value->value == value) + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), i); + } + + } + + renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(combobox)); + g_object_set(G_OBJECT(renderers->data), "scale", TREE_TEXT_SCALE, NULL); + g_list_free(renderers); + + editable = GTK_CELL_EDITABLE(combobox); + } + else if (G_VALUE_HOLDS_STRING(&gvalue)) + { + GtkWidget *entry = gtk_entry_new(); + gtk_widget_show(entry); + gtk_entry_set_text(GTK_ENTRY(entry), g_value_get_string(&gvalue)); + + editable = GTK_CELL_EDITABLE(entry); + } + else if (G_VALUE_HOLDS_INT(&gvalue) || + G_VALUE_HOLDS_UINT(&gvalue) || + G_VALUE_HOLDS_INT64(&gvalue) || + G_VALUE_HOLDS_UINT64(&gvalue) || + G_VALUE_HOLDS_LONG(&gvalue) || + G_VALUE_HOLDS_ULONG(&gvalue) || + G_VALUE_HOLDS_DOUBLE(&gvalue)) + { + double min, max, value; + GtkWidget *spinbutton; + guint digits = 0; + + if (G_VALUE_HOLDS_INT(&gvalue)) + { + GParamSpecInt *paramspec = G_PARAM_SPEC_INT(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_int(&gvalue); + } + else if (G_VALUE_HOLDS_UINT(&gvalue)) + { + GParamSpecUInt *paramspec = G_PARAM_SPEC_UINT(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_uint(&gvalue); + } + else if (G_VALUE_HOLDS_INT64(&gvalue)) + { + GParamSpecInt64 *paramspec = G_PARAM_SPEC_INT64(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_int64(&gvalue); + } + else if (G_VALUE_HOLDS_UINT64(&gvalue)) + { + GParamSpecUInt64 *paramspec = G_PARAM_SPEC_UINT64(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_uint64(&gvalue); + } + else if (G_VALUE_HOLDS_LONG(&gvalue)) + { + GParamSpecLong *paramspec = G_PARAM_SPEC_LONG(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_long(&gvalue); + } + else if (G_VALUE_HOLDS_ULONG(&gvalue)) + { + GParamSpecULong *paramspec = G_PARAM_SPEC_ULONG(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_ulong(&gvalue); + } + else if (G_VALUE_HOLDS_DOUBLE(&gvalue)) + { + GParamSpecDouble *paramspec = G_PARAM_SPEC_DOUBLE(prop); + min = paramspec->minimum; + max = paramspec->maximum; + value = g_value_get_double(&gvalue); + digits = 2; + } + else + { + // Shouldn't really be able to happen. + return NULL; + } + + spinbutton = gtk_spin_button_new_with_range(min, max, 1); + gtk_widget_show(spinbutton); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton), value); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinbutton), digits); + + editable = GTK_CELL_EDITABLE(spinbutton); + } + } + g_value_unset(&gvalue); + + if (!editable) + return NULL; + + font_desc = pango_font_description_new(); + pango_font_description_set_size(font_desc, 8 * PANGO_SCALE); + gtk_widget_override_font (GTK_WIDGET (editable), font_desc); + pango_font_description_free(font_desc); + + g_signal_connect(editable, "editing_done", G_CALLBACK (stop_editing), renderer); + g_object_set_data_full (G_OBJECT (editable), "_prop_name", g_strdup (name), g_free); + g_object_set_data (G_OBJECT (editable), "_prop_object", object); + + return editable; +} + +static void +parasite_property_cell_renderer_class_init (ParasitePropertyCellRendererClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + cell_class->start_editing = start_editing; + + g_object_class_install_property(object_class, + PROP_OBJECT, + g_param_spec_object ("object", + "Object", + "The object owning the property", + G_TYPE_OBJECT, + G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "The property name", + NULL, + G_PARAM_READWRITE)); +} + +GtkCellRenderer * +parasite_property_cell_renderer_new(void) +{ + return g_object_new(PARASITE_TYPE_PROPERTY_CELL_RENDERER, NULL); +} + + +// vim: set et ts=4: diff --git a/modules/other/parasite/property-cell-renderer.h b/modules/other/parasite/property-cell-renderer.h new file mode 100644 index 0000000000..cc88def1f6 --- /dev/null +++ b/modules/other/parasite/property-cell-renderer.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _GTKPARASITE_PROPERTY_CELL_RENDERER_H_ +#define _GTKPARASITE_PROPERTY_CELL_RENDERER_H_ + + +#include + + +#define PARASITE_TYPE_PROPERTY_CELL_RENDERER (parasite_property_cell_renderer_get_type()) +#define PARASITE_PROPERTY_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRenderer)) +#define PARASITE_PROPERTY_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRendererClass)) +#define PARASITE_IS_PROPERTY_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PROPERTY_CELL_RENDERER)) +#define PARASITE_IS_PROPERTY_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PROPERTY_CELL_RENDERER)) +#define PARASITE_PROPERTY_CELL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRendererClass)) + +typedef struct _ParasitePropertyCellRendererPrivate ParasitePropertyCellRendererPrivate; + +typedef struct +{ + GtkCellRendererText parent; + ParasitePropertyCellRendererPrivate *priv; +} ParasitePropertyCellRenderer; + +typedef struct +{ + GtkCellRendererTextClass parent; + + // Padding for future expansion + void (*reserved0)(void); + void (*reserved1)(void); + void (*reserved2)(void); + void (*reserved3)(void); + +} ParasitePropertyCellRendererClass; + + +G_BEGIN_DECLS + + +GType parasite_property_cell_renderer_get_type(); +GtkCellRenderer *parasite_property_cell_renderer_new(); + + +G_END_DECLS + + +#endif // _GTKPARASITE_PROPERTY_CELL_RENDERER_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/python-hooks.c b/modules/other/parasite/python-hooks.c new file mode 100644 index 0000000000..2148ec39f4 --- /dev/null +++ b/modules/other/parasite/python-hooks.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 +#include "config.h" +#include + +#ifdef ENABLE_PYTHON +# include +# include +#endif + +#include "python-hooks.h" + +static gboolean python_enabled = FALSE; + +#ifdef ENABLE_PYTHON +static GString *captured_stdout = NULL; +static GString *captured_stderr = NULL; + + +static PyObject * +capture_stdout(PyObject *self, PyObject *args) +{ + char *str = NULL; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + g_string_append(captured_stdout, str); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +capture_stderr(PyObject *self, PyObject *args) +{ + char *str = NULL; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + g_string_append(captured_stderr, str); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +wrap_gobj(PyObject *self, PyObject *args) +{ + void *addr; + GObject *obj; + + if (!PyArg_ParseTuple(args, "l", &addr)) + return NULL; + + if (!G_IS_OBJECT(addr)) + return NULL; // XXX + + obj = G_OBJECT(addr); + + if (!obj) + return NULL; // XXX + + return pygobject_new(obj); +} + +static PyMethodDef parasite_python_methods[] = { + {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"}, + {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"}, + {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"}, + {NULL, NULL, 0, NULL} +}; + + +static gboolean +is_blacklisted(void) +{ + const char *prgname = g_get_prgname(); + + return (!strcmp(prgname, "gimp")); +} +#endif // ENABLE_PYTHON + +void +parasite_python_init(void) +{ +#ifdef ENABLE_PYTHON + int res; + struct sigaction old_sigint; + + if (is_blacklisted()) + return; + + /* This prevents errors such as "undefined symbol: PyExc_ImportError" */ + if (!dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL)) + { + g_error("%s\n", dlerror()); + return; + } + + captured_stdout = g_string_new(""); + captured_stderr = g_string_new(""); + + /* Back up and later restore SIGINT so Python doesn't steal it from us. */ + res = sigaction(SIGINT, NULL, &old_sigint); + + if (!Py_IsInitialized()) + Py_Initialize(); + + res = sigaction(SIGINT, &old_sigint, NULL); + + Py_InitModule("parasite", parasite_python_methods); + PyRun_SimpleString( + "import parasite\n" + "import sys\n" + "\n" + "class StdoutCatcher:\n" + " def write(self, str):\n" + " parasite.capture_stdout(str)\n" + "\n" + "class StderrCatcher:\n" + " def write(self, str):\n" + " parasite.capture_stderr(str)\n" + "\n" + ); + + if (!pygobject_init(-1, -1, -1)) + { + fprintf(stderr, "Error initializing pygobject support.\n"); + PyErr_Print(); + return; + } + + char *argv[] = { "", NULL }; + PySys_SetArgv(0, argv); + + if (!PyImport_ImportModule("gi._gobject")) + { + PyErr_SetString(PyExc_ImportError, "could not import gi.gobject"); + return; + } + if (!PyImport_ImportModule("gi.repository")) + { + PyErr_SetString(PyExc_ImportError, "could not import gi.repository"); + return; + } + if (!PyImport_ImportModule("gi.repository.Gtk")) + { + PyErr_SetString(PyExc_ImportError, "could not import gtk"); + return; + } + + python_enabled = TRUE; +#endif // ENABLE_PYTHON +} + +void +parasite_python_run(const char *command, + ParasitePythonLogger stdout_logger, + ParasitePythonLogger stderr_logger, + gpointer user_data) +{ +#ifdef ENABLE_PYTHON + PyGILState_STATE gstate; + PyObject *module; + PyObject *dict; + PyObject *obj; + + gstate = PyGILState_Ensure(); + + module = PyImport_AddModule("__main__"); + dict = PyModule_GetDict(module); + + PyRun_SimpleString("old_stdout = sys.stdout\n" + "old_stderr = sys.stderr\n" + "sys.stdout = StdoutCatcher()\n" + "sys.stderr = StderrCatcher()\n"); + + obj = PyRun_String(command, Py_single_input, dict, dict); + + PyRun_SimpleString("sys.stdout = old_stdout\n" + "sys.stderr = old_stderr\n"); + + if (stdout_logger != NULL) + stdout_logger(captured_stdout->str, user_data); + + if (stderr_logger != NULL) + stderr_logger(captured_stderr->str, user_data); + + // Print any returned object + if (obj != NULL && obj != Py_None) { + PyObject *repr = PyObject_Repr(obj); + if (repr != NULL) { + char *string = PyString_AsString(repr); + + stdout_logger(string, user_data); + stdout_logger("\n", user_data); + } + + Py_XDECREF(repr); + } + Py_XDECREF(obj); + + PyGILState_Release(gstate); + g_string_erase(captured_stdout, 0, -1); + g_string_erase(captured_stderr, 0, -1); +#endif // ENABLE_PYTHON +} + +gboolean +parasite_python_is_enabled(void) +{ + return python_enabled; +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/python-hooks.h b/modules/other/parasite/python-hooks.h new file mode 100644 index 0000000000..d6bf1bdf7d --- /dev/null +++ b/modules/other/parasite/python-hooks.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _GTKPARASITE_PYTHON_MODULE_H_ +#define _GTKPARASITE_PYTHON_MODULE_H_ + +#include + + +typedef void (*ParasitePythonLogger)(const char *text, gpointer user_data); + +void parasite_python_init(void); +void parasite_python_run(const char *command, + ParasitePythonLogger stdout_logger, + ParasitePythonLogger stderr_logger, + gpointer user_data); +gboolean parasite_python_is_enabled(void); + +#endif // _GTKPARASITE_PYTHON_MODULE_H_ diff --git a/modules/other/parasite/python-shell.c b/modules/other/parasite/python-shell.c new file mode 100644 index 0000000000..53fc5c6298 --- /dev/null +++ b/modules/other/parasite/python-shell.c @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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 +#include + +#include "python-hooks.h" +#include "python-shell.h" + +#define MAX_HISTORY_LENGTH 20 + +struct _ParasitePythonShellPrivate +{ + GtkWidget *textview; + + GtkTextMark *scroll_mark; + GtkTextMark *line_start_mark; + + GQueue *history; + GList *cur_history_item; + + GString *pending_command; + gboolean in_block; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasitePythonShell, parasite_python_shell, GTK_TYPE_BOX); + +/* Widget functions */ +static void parasite_python_shell_finalize (GObject *obj); + +/* Python integration */ +static void parasite_python_shell_write_prompt(GtkWidget *python_shell); +static char *parasite_python_shell_get_input(GtkWidget *python_shell); + +/* Callbacks */ +static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview, + GdkEventKey *event, + GtkWidget *python_shell); +static void +parasite_python_shell_class_init(ParasitePythonShellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = parasite_python_shell_finalize; +} + +static void +parasite_python_shell_init (ParasitePythonShell *python_shell) +{ + GtkWidget *swin; + GtkTextBuffer *buffer; + GtkTextIter iter; + PangoFontDescription *font_desc; + + python_shell->priv = parasite_python_shell_get_instance_private (python_shell); + + python_shell->priv->history = g_queue_new(); + + gtk_box_set_spacing(GTK_BOX(python_shell), 6); + + swin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(swin); + gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin), + GTK_SHADOW_IN); + + python_shell->priv->textview = gtk_text_view_new(); + gtk_widget_show(python_shell->priv->textview); + gtk_container_add(GTK_CONTAINER(swin), python_shell->priv->textview); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(python_shell->priv->textview), TRUE); + gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(python_shell->priv->textview), 3); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(python_shell->priv->textview), 3); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(python_shell->priv->textview), 3); + + g_signal_connect(python_shell->priv->textview, "key_press_event", + G_CALLBACK(parasite_python_shell_key_press_cb), + python_shell); + + /* Make the textview monospaced */ + font_desc = pango_font_description_from_string("monospace"); + pango_font_description_set_size(font_desc, 8 * PANGO_SCALE); + gtk_widget_override_font(python_shell->priv->textview, font_desc); + pango_font_description_free(font_desc); + + /* Create the end-of-buffer mark */ + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(python_shell->priv->textview)); + gtk_text_buffer_get_end_iter(buffer, &iter); + python_shell->priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark", + &iter, FALSE); + + /* Create the beginning-of-line mark */ + python_shell->priv->line_start_mark = gtk_text_buffer_create_mark(buffer, + "line_start_mark", + &iter, TRUE); + + /* Register some tags */ + gtk_text_buffer_create_tag(buffer, "stdout", NULL); + gtk_text_buffer_create_tag(buffer, "stderr", + "foreground", "red", + "paragraph-background", "#FFFFE0", + NULL); + gtk_text_buffer_create_tag(buffer, "prompt", + "foreground", "blue", + NULL); + + parasite_python_shell_write_prompt(GTK_WIDGET(python_shell)); +} + +static void +parasite_python_shell_finalize(GObject *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + + g_queue_free(priv->history); +} + +static void +parasite_python_shell_log_stdout(const char *text, gpointer python_shell) +{ + parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell), + text, "stdout"); +} + +static void +parasite_python_shell_log_stderr(const char *text, gpointer python_shell) +{ + parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell), + text, "stderr"); +} + +static void +parasite_python_shell_write_prompt(GtkWidget *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + GtkTextBuffer *buffer = + gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview)); + GtkTextIter iter; + const char *prompt = (priv->pending_command == NULL ? ">>> " : "... "); + + parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell), + prompt, "prompt"); + + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter); +} + +static void +parasite_python_shell_process_line(GtkWidget *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + char *command = parasite_python_shell_get_input(python_shell); + char last_char; + + parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell), + "\n", NULL); + + if (*command != '\0') + { + /* Save this command in the history. */ + g_queue_push_head(priv->history, command); + priv->cur_history_item = NULL; + + if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH) + g_free(g_queue_pop_tail(priv->history)); + } + + last_char = command[MAX(0, strlen(command) - 1)]; + + if (last_char == ':' || last_char == '\\' || + (priv->in_block && g_ascii_isspace(command[0]))) + { + printf("in block.. %c, %d, %d\n", + last_char, priv->in_block, + g_ascii_isspace(command[0])); + /* This is a multi-line expression */ + if (priv->pending_command == NULL) + priv->pending_command = g_string_new(command); + else + g_string_append(priv->pending_command, command); + + g_string_append_c(priv->pending_command, '\n'); + + if (last_char == ':') + priv->in_block = TRUE; + } + else + { + if (priv->pending_command != NULL) + { + g_string_append(priv->pending_command, command); + g_string_append_c(priv->pending_command, '\n'); + + /* We're not actually leaking this. It's in the history. */ + command = g_string_free(priv->pending_command, FALSE); + } + + parasite_python_run(command, + parasite_python_shell_log_stdout, + parasite_python_shell_log_stderr, + python_shell); + + if (priv->pending_command != NULL) + { + /* Now do the cleanup. */ + g_free(command); + priv->pending_command = NULL; + priv->in_block = FALSE; + } + } + + parasite_python_shell_write_prompt(python_shell); +} + +static void +parasite_python_shell_replace_input(GtkWidget *python_shell, + const char *text) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + GtkTextBuffer *buffer = + gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview)); + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, + priv->line_start_mark); + gtk_text_buffer_get_end_iter(buffer, &end_iter); + + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + gtk_text_buffer_insert(buffer, &end_iter, text, -1); +} + +static char * +parasite_python_shell_get_input(GtkWidget *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + GtkTextBuffer *buffer = + gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview)); + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, + priv->line_start_mark); + gtk_text_buffer_get_end_iter(buffer, &end_iter); + + return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE); +} + +static const char * +parasite_python_shell_get_history_back(GtkWidget *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + + if (priv->cur_history_item == NULL) + { + priv->cur_history_item = g_queue_peek_head_link(priv->history); + + if (priv->cur_history_item == NULL) + return ""; + } + else if (priv->cur_history_item->next != NULL) + priv->cur_history_item = priv->cur_history_item->next; + + return (const char *)priv->cur_history_item->data; +} + +static const char * +parasite_python_shell_get_history_forward(GtkWidget *python_shell) +{ + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + + if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL) + { + priv->cur_history_item = NULL; + return ""; + } + + priv->cur_history_item = priv->cur_history_item->prev; + + return (const char *)priv->cur_history_item->data; +} + +static gboolean +parasite_python_shell_key_press_cb(GtkWidget *textview, + GdkEventKey *event, + GtkWidget *python_shell) +{ + if (event->keyval == GDK_KEY_Return) + { + parasite_python_shell_process_line(python_shell); + return TRUE; + } + else if (event->keyval == GDK_KEY_Up) + { + parasite_python_shell_replace_input(python_shell, + parasite_python_shell_get_history_back(python_shell)); + return TRUE; + } + else if (event->keyval == GDK_KEY_Down) + { + parasite_python_shell_replace_input(python_shell, + parasite_python_shell_get_history_forward(python_shell)); + return TRUE; + } + else if (event->string != NULL) + { + ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv; + GtkTextBuffer *buffer = + gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview)); + GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer); + GtkTextMark *selection_mark = + gtk_text_buffer_get_selection_bound(buffer); + GtkTextIter insert_iter; + GtkTextIter selection_iter; + GtkTextIter start_iter; + gint cmp_start_insert; + gint cmp_start_select; + gint cmp_insert_select; + + gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, + priv->line_start_mark); + gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark); + gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter, + selection_mark); + + cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter); + cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter); + cmp_insert_select = gtk_text_iter_compare(&insert_iter, + &selection_iter); + + if (cmp_start_insert == 0 && cmp_start_select == 0 && + (event->keyval == GDK_KEY_BackSpace || + event->keyval == GDK_KEY_Left)) + { + return TRUE; + } + if (cmp_start_insert <= 0 && cmp_start_select <= 0) + { + return FALSE; + } + else if (cmp_start_insert > 0 && cmp_start_select > 0) + { + gtk_text_buffer_place_cursor(buffer, &start_iter); + } + else if (cmp_insert_select < 0) + { + gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter); + } + else if (cmp_insert_select > 0) + { + gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter); + } + } + + return FALSE; +} + +GtkWidget * +parasite_python_shell_new(void) +{ + return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL); +} + +void +parasite_python_shell_append_text(ParasitePythonShell *python_shell, + const char *str, + const char *tag) +{ + ParasitePythonShellPrivate *priv = python_shell->priv; + + GtkTextIter end; + GtkTextBuffer *buffer = + gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview)); + GtkTextMark *mark = gtk_text_buffer_get_insert(buffer); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_move_mark(buffer, mark, &end); + gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL); + gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark, + 0, TRUE, 0, 1); +} + +void +parasite_python_shell_focus(ParasitePythonShell *python_shell) +{ + gtk_widget_grab_focus (python_shell->priv->textview); +} + +// vim: set et ts=4: diff --git a/modules/other/parasite/python-shell.h b/modules/other/parasite/python-shell.h new file mode 100644 index 0000000000..8d4f58d864 --- /dev/null +++ b/modules/other/parasite/python-shell.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _PARASITE_PYTHON_SHELL_H_ +#define _PARASITE_PYTHON_SHELL_H_ + +typedef struct _ParasitePythonShell ParasitePythonShell; +typedef struct _ParasitePythonShellClass ParasitePythonShellClass; +typedef struct _ParasitePythonShellPrivate ParasitePythonShellPrivate; + +#include + +#define PARASITE_TYPE_PYTHON_SHELL (parasite_python_shell_get_type()) +#define PARASITE_PYTHON_SHELL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShell)) +#define PARASITE_PYTHON_SHELL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass)) +#define PARASITE_IS_PYTHON_SHELL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PYTHON_SHELL)) +#define PARASITE_IS_PYTHON_SHELL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PYTHON_SHELL)) +#define PARASITE_PYTHON_SHELL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass)) + + +struct _ParasitePythonShell +{ + GtkBox parent_object; + ParasitePythonShellPrivate *priv; +}; + +struct _ParasitePythonShellClass +{ + GtkBoxClass parent_class; +}; + +G_BEGIN_DECLS + +GType parasite_python_shell_get_type(void); + +GtkWidget *parasite_python_shell_new(void); +void parasite_python_shell_append_text(ParasitePythonShell *python_shell, + const char *str, + const char *tag); +void parasite_python_shell_focus(ParasitePythonShell *python_shell); + +G_END_DECLS + +#endif // _PARASITE_PYTHON_SHELL_H_ diff --git a/modules/other/parasite/themes.c b/modules/other/parasite/themes.c new file mode 100644 index 0000000000..ed0ff5c8eb --- /dev/null +++ b/modules/other/parasite/themes.c @@ -0,0 +1,177 @@ +/* + * 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 "themes.h" + +struct _ParasiteThemesPrivate +{ + int dummy; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteThemes, parasite_themes, GTK_TYPE_LIST_BOX) + +static void +parasite_themes_init (ParasiteThemes *pt) +{ + pt->priv = parasite_themes_get_instance_private (pt); +} + +static GtkWidget * +create_dark (ParasiteThemes *pt) +{ + GtkWidget *b, *l, *s; + + b = g_object_new (GTK_TYPE_BOX, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "margin", 10, + NULL); + + l = g_object_new (GTK_TYPE_LABEL, + "label", "Use dark variant", + "hexpand", TRUE, + "xalign", 0.0, + NULL); + gtk_container_add (GTK_CONTAINER (b), l); + + s = gtk_switch_new (); + g_object_bind_property (s, "active", + gtk_settings_get_default (), "gtk-application-prefer-dark-theme", + G_BINDING_BIDIRECTIONAL); + gtk_container_add (GTK_CONTAINER (b), s); + + return b; +} + +static void +fill_gtk (const char *path, GHashTable *t) +{ + const gchar *dir_entry; + GDir *dir = g_dir_open (path, 0, NULL); + + if (!dir) + return; + + while ((dir_entry = g_dir_read_name (dir))) + { + char *filename = g_build_filename (path, dir_entry, "gtk-3.0", NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_DIR) + && !g_hash_table_contains (t, dir_entry)) + g_hash_table_add (t, g_strdup (dir_entry)); + + g_free (filename); + } +} + +static void +gtk_changed (GtkComboBox *c, ParasiteThemes *pt) +{ + char *theme = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (c)); + + g_object_set (gtk_settings_get_default (), + "gtk-theme-name", theme, + NULL); + g_free (theme); +} + +static GtkWidget * +create_gtk (ParasiteThemes *pt) +{ + GtkWidget *b, *l, *c; + GHashTable *t; + char *theme, *default_theme, *path; + GHashTableIter iter; + int i, pos; + GSettings *settings; + + b = g_object_new (GTK_TYPE_BOX, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "margin", 10, + NULL); + + l = g_object_new (GTK_TYPE_LABEL, + "label", "GTK+ Theme", + "hexpand", TRUE, + "xalign", 0.0, + NULL); + gtk_container_add (GTK_CONTAINER (b), l); + + t = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + fill_gtk (GTK_DATADIR "/themes", t); + path = g_build_filename (g_get_user_data_dir (), "themes", NULL); + fill_gtk (path, t); + g_free (path); + + c = gtk_combo_box_text_new (); + gtk_container_add (GTK_CONTAINER (b), c); + + settings = g_settings_new ("org.gnome.desktop.interface"); + default_theme = g_settings_get_string (settings, "gtk-theme"); + g_object_unref (settings); + + g_hash_table_iter_init (&iter, t); + pos = i = 0; + while (g_hash_table_iter_next (&iter, (gpointer *)&theme, NULL)) + { + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (c), theme); + if (g_strcmp0 (theme, default_theme) == 0) + pos = i; + i++; + } + g_hash_table_destroy (t); + + gtk_combo_box_set_active (GTK_COMBO_BOX (c), pos); + g_signal_connect (c, "changed", G_CALLBACK (gtk_changed), pt); + + return b; +} + +static void +constructed (GObject *object) +{ + ParasiteThemes *pt = PARASITE_THEMES (object); + GtkContainer *box = GTK_CONTAINER (object); + + g_object_set (object, + "selection-mode", GTK_SELECTION_NONE, + NULL); + + gtk_container_add (box, create_dark (pt)); + gtk_container_add (box, create_gtk (pt)); +} + +static void +parasite_themes_class_init (ParasiteThemesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = constructed; +} + +GtkWidget * +parasite_themes_new (void) +{ + return GTK_WIDGET (g_object_new (PARASITE_TYPE_THEMES, NULL)); +} + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/themes.h b/modules/other/parasite/themes.h new file mode 100644 index 0000000000..9f76650831 --- /dev/null +++ b/modules/other/parasite/themes.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef _GTKPARASITE_THEMES_H_ +#define _GTKPARASITE_THEMES_H_ + +#include + +#define PARASITE_TYPE_THEMES (parasite_themes_get_type()) +#define PARASITE_THEMES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_THEMES, ParasiteThemes)) +#define PARASITE_THEMES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_THEMES, ParasiteThemesClass)) +#define PARASITE_IS_THEMES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_THEMES)) +#define PARASITE_IS_THEMES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_THEMES)) +#define PARASITE_THEMES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_THEMES, ParasiteThemesClass)) + + +typedef struct _ParasiteThemesPrivate ParasiteThemesPrivate; + +typedef struct _ParasiteThemes { + GtkListBox parent; + ParasiteThemesPrivate *priv; +} ParasiteThemes; + +typedef struct _ParasiteThemesClass { + GtkListBoxClass parent; +} ParasiteThemesClass; + +G_BEGIN_DECLS + +GType parasite_themes_get_type (void); +GtkWidget *parasite_themes_new (void); + +G_END_DECLS + +#endif // _GTKPARASITE_THEMES_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/widget-tree.c b/modules/other/parasite/widget-tree.c new file mode 100644 index 0000000000..e35c505402 --- /dev/null +++ b/modules/other/parasite/widget-tree.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * 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 "parasite.h" +#include "prop-list.h" +#include "widget-tree.h" +#include + +enum +{ + OBJECT, + OBJECT_TYPE, + OBJECT_NAME, + WIDGET_REALIZED, + WIDGET_VISIBLE, + WIDGET_MAPPED, + OBJECT_ADDRESS, + SENSITIVE, + NUM_COLUMNS +}; + + +enum +{ + WIDGET_CHANGED, + LAST_SIGNAL +}; + + +struct _ParasiteWidgetTreePrivate +{ + GtkTreeStore *model; + GHashTable *iters; +}; + +static guint widget_tree_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE_WITH_PRIVATE (ParasiteWidgetTree, parasite_widget_tree, GTK_TYPE_TREE_VIEW) + +static void +parasite_widget_tree_on_widget_selected(GtkTreeSelection *selection, + ParasiteWidgetTree *widget_tree) +{ + g_signal_emit(widget_tree, widget_tree_signals[WIDGET_CHANGED], 0); +} + + +static void +parasite_widget_tree_init (ParasiteWidgetTree *widget_tree) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + widget_tree->priv = parasite_widget_tree_get_instance_private (widget_tree); + + widget_tree->priv->iters = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify) gtk_tree_iter_free); + + widget_tree->priv->model = gtk_tree_store_new( + NUM_COLUMNS, + G_TYPE_POINTER, // OBJECT + G_TYPE_STRING, // OBJECT_TYPE + G_TYPE_STRING, // OBJECT_NAME + G_TYPE_BOOLEAN, // WIDGET_REALIZED + G_TYPE_BOOLEAN, // WIDGET_VISIBLE + G_TYPE_BOOLEAN, // WIDGET_MAPPED + G_TYPE_STRING, // OBJECT_ADDRESS + G_TYPE_BOOLEAN);// SENSITIVE + + gtk_tree_view_set_model(GTK_TREE_VIEW(widget_tree), + GTK_TREE_MODEL(widget_tree->priv->model)); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(widget_tree), TRUE); + gtk_tree_view_set_search_column(GTK_TREE_VIEW(widget_tree), OBJECT_NAME); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget_tree)); + g_signal_connect(G_OBJECT(selection), "changed", + G_CALLBACK(parasite_widget_tree_on_widget_selected), + widget_tree); + + // Widget column + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "scale", TREE_TEXT_SCALE, NULL); + column = gtk_tree_view_column_new_with_attributes("Widget", renderer, + "text", OBJECT_TYPE, + "sensitive", SENSITIVE, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + gtk_tree_view_column_set_resizable(column, TRUE); + + // Name column + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "scale", TREE_TEXT_SCALE, NULL); + column = gtk_tree_view_column_new_with_attributes("Name", renderer, + "text", OBJECT_NAME, + "sensitive", SENSITIVE, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + gtk_tree_view_column_set_resizable(column, TRUE); + + // Realized column + renderer = gtk_cell_renderer_toggle_new(); + g_object_set(G_OBJECT(renderer), + "activatable", TRUE, + "indicator-size", TREE_CHECKBOX_SIZE, + NULL); + column = gtk_tree_view_column_new_with_attributes("Realized", + renderer, + "active", WIDGET_REALIZED, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + + // Mapped column + renderer = gtk_cell_renderer_toggle_new(); + g_object_set(G_OBJECT(renderer), + "activatable", TRUE, + "indicator-size", TREE_CHECKBOX_SIZE, + NULL); + column = gtk_tree_view_column_new_with_attributes("Mapped", + renderer, + "active", WIDGET_MAPPED, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + + // Visible column + renderer = gtk_cell_renderer_toggle_new(); + g_object_set(G_OBJECT(renderer), + "activatable", TRUE, + "indicator-size", TREE_CHECKBOX_SIZE, + NULL); + column = gtk_tree_view_column_new_with_attributes("Visible", + renderer, + "active", WIDGET_VISIBLE, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + + // Poinder Address column + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), + "scale", TREE_TEXT_SCALE, + "family", "monospace", + NULL); + column = gtk_tree_view_column_new_with_attributes("Pointer Address", + renderer, + "text", OBJECT_ADDRESS, + "sensitive", SENSITIVE, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column); + gtk_tree_view_column_set_resizable(column, TRUE); + + parasite_widget_tree_append_object (widget_tree, G_OBJECT (gtk_settings_get_default ()), NULL); +} + + +static void +parasite_widget_tree_class_init(ParasiteWidgetTreeClass *klass) +{ + klass->widget_changed = NULL; + + widget_tree_signals[WIDGET_CHANGED] = + g_signal_new("widget-changed", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, + G_STRUCT_OFFSET(ParasiteWidgetTreeClass, widget_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + +GtkWidget * +parasite_widget_tree_new () +{ + return g_object_new (PARASITE_TYPE_WIDGET_TREE, NULL); +} + + +GObject * +parasite_widget_tree_get_selected_object (ParasiteWidgetTree *widget_tree) +{ + GtkTreeIter iter; + GtkTreeSelection *sel; + GtkTreeModel *model; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget_tree)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + GObject *object; + gtk_tree_model_get (model, &iter, + OBJECT, &object, + -1); + return object; + } + + return NULL; +} + +static void +on_container_forall(GtkWidget *widget, gpointer data) +{ + GList **list = (GList **)data; + + *list = g_list_append(*list, widget); +} + +void +parasite_widget_tree_append_object (ParasiteWidgetTree *widget_tree, + GObject *object, + GtkTreeIter *parent_iter) +{ + GtkTreeIter iter; + const char *class_name = G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (object)); + const char *name = NULL; + char *address; + gboolean realized; + gboolean mapped; + gboolean visible; + gboolean is_widget; + GList *l; + + realized = mapped = visible = FALSE; + + is_widget = GTK_IS_WIDGET (object); + if (is_widget) + { + GtkWidget *widget = GTK_WIDGET (object); + name = gtk_widget_get_name (GTK_WIDGET (object)); + + realized = gtk_widget_get_realized (widget); + mapped = gtk_widget_get_mapped (widget); + visible = gtk_widget_get_visible (widget); + } + + if (name == NULL || g_strcmp0 (name, class_name) == 0) + { + if (GTK_IS_LABEL (object)) + { + name = gtk_label_get_text (GTK_LABEL (object)); + } + else if (GTK_IS_BUTTON (object)) + { + name = gtk_button_get_label (GTK_BUTTON (object)); + } + else if (GTK_IS_WINDOW (object)) + { + name = gtk_window_get_title (GTK_WINDOW (object)); + } + else + { + name = ""; + } + } + + address = g_strdup_printf ("%p", object); + + gtk_tree_store_append (widget_tree->priv->model, &iter, parent_iter); + gtk_tree_store_set (widget_tree->priv->model, &iter, + OBJECT, object, + OBJECT_TYPE, class_name, + OBJECT_NAME, name, + WIDGET_REALIZED, realized, + WIDGET_MAPPED, mapped, + WIDGET_VISIBLE, visible, + OBJECT_ADDRESS, address, + SENSITIVE, !is_widget || (realized && mapped && visible), + -1); + g_hash_table_insert (widget_tree->priv->iters, object, gtk_tree_iter_copy (&iter)); + + g_free(address); + + if (GTK_IS_CONTAINER (object)) + { + GList* children = NULL; + + /* Pick up all children, including those that are internal. */ + gtk_container_forall (GTK_CONTAINER (object), + on_container_forall, &children); + + for (l = children; l != NULL; l = l->next) + { + parasite_widget_tree_append_object (widget_tree, l->data, &iter); + } + + g_list_free(children); + } +} + +void +parasite_widget_tree_scan (ParasiteWidgetTree *widget_tree, + GtkWidget *window) +{ + gtk_tree_store_clear (widget_tree->priv->model); + g_hash_table_remove_all (widget_tree->priv->iters); + parasite_widget_tree_append_object (widget_tree, G_OBJECT (gtk_settings_get_default ()), NULL); + parasite_widget_tree_append_object (widget_tree, G_OBJECT (window), NULL); + + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (widget_tree)); +} + + +/* +static GList * +get_parents(GtkWidget *widget, + GList *parents) +{ + GtkWidget *parent = gtk_widget_get_parent(widget); + + parents = g_list_prepend(parents, widget); + + if (parent != NULL) + return get_parents(parent, parents); + + return parents; +} + +gboolean +parasite_widget_tree_find_widget (ParasiteWidgetTree *widget_tree, + GtkWidget *widget, + GtkTreeIter *iter) +{ + GList *parents = get_parents (widget, NULL); + GList *l; + GtkTreeIter inner_iter, parent_iter = {0}; + gboolean found = FALSE; + gboolean in_root = TRUE; + + for (l = parents; l != NULL; l = l->next) + { + GtkWidget *cur_widget = GTK_WIDGET (l->data); + gboolean valid; + found = FALSE; + + for (valid = gtk_tree_model_iter_children (widget_tree->priv->model, + &inner_iter, + in_root ? NULL : &parent_iter); + valid; + valid = gtk_tree_model_iter_next (widget_tree->priv->model, &inner_iter)) + { + GtkWidget *iter_widget; + gtk_tree_model_get (widget_tree->priv->model, + &inner_iter, + WIDGET, &iter_widget, + -1); + if (iter_widget == cur_widget) + { + parent_iter = inner_iter; + in_root = FALSE; + found = TRUE; + break; + } + } + } + + g_list_free(parents); + + *iter = inner_iter; + return found; +} +*/ + +gboolean +parasite_widget_tree_find_object (ParasiteWidgetTree *widget_tree, + GObject *object, + GtkTreeIter *iter) +{ + GtkTreeIter *internal_iter = g_hash_table_lookup (widget_tree->priv->iters, object); + if (internal_iter) + { + *iter = *internal_iter; + return TRUE; + } + + return FALSE; +} + +void +parasite_widget_tree_select_object (ParasiteWidgetTree *widget_tree, + GObject *object) +{ + GtkTreeIter iter; + + if (parasite_widget_tree_find_object (widget_tree, object, &iter)) + { + GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (widget_tree->priv->model), &iter); + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(widget_tree), path); + gtk_tree_selection_select_iter( + gtk_tree_view_get_selection(GTK_TREE_VIEW(widget_tree)), + &iter); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget_tree), path, NULL, FALSE, 0, 0); + } + +} + + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/widget-tree.h b/modules/other/parasite/widget-tree.h new file mode 100644 index 0000000000..f3c50177ac --- /dev/null +++ b/modules/other/parasite/widget-tree.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * + * 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. + */ +#ifndef _GTKPARASITE_WIDGET_TREE_H_ +#define _GTKPARASITE_WIDGET_TREE_H_ + + +#include + + +#define PARASITE_TYPE_WIDGET_TREE (parasite_widget_tree_get_type()) +#define PARASITE_WIDGET_TREE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_WIDGET_TREE, ParasiteWidgetTree)) +#define PARASITE_WIDGET_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_WIDGET_TREE, ParasiteWidgetTreeClass)) +#define PARASITE_IS_WIDGET_TREE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_WIDGET_TREE)) +#define PARASITE_IS_WIDGET_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_WIDGET_TREE)) +#define PARASITE_WIDGET_TREE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_WIDGET_TREE, ParasiteWidgetTreeClass)) + + +typedef struct _ParasiteWidgetTreePrivate ParasiteWidgetTreePrivate; + +typedef struct _ParasiteWidgetTree { + GtkTreeView parent; + + // Private + ParasiteWidgetTreePrivate *priv; +} ParasiteWidgetTree; + +typedef struct _ParasiteWidgetTreeClass { + GtkTreeViewClass parent; + + void (*widget_changed)(ParasiteWidgetTree *tree); +} ParasiteWidgetTreeClass; + + +G_BEGIN_DECLS + + +GType parasite_widget_tree_get_type (void); +GtkWidget *parasite_widget_tree_new (void); + +GObject *parasite_widget_tree_get_selected_object (ParasiteWidgetTree *widget_tree); + +void parasite_widget_tree_scan (ParasiteWidgetTree *widget_tree, + GtkWidget *window); +void parasite_widget_tree_select_object (ParasiteWidgetTree *widget_tree, + GObject *object); +void parasite_widget_tree_append_object (ParasiteWidgetTree *widget_tree, + GObject *object, + GtkTreeIter *parent_iter); +gboolean parasite_widget_tree_find_object (ParasiteWidgetTree *widget_tree, + GObject *object, + GtkTreeIter *iter); + +G_END_DECLS + + +#endif // _GTKPARASITE_WIDGETTREE_H_ + +// vim: set et sw=4 ts=4: diff --git a/modules/other/parasite/window.c b/modules/other/parasite/window.c new file mode 100644 index 0000000000..56670ddc9a --- /dev/null +++ b/modules/other/parasite/window.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2008-2009 Christian Hammond + * Copyright (c) 2008-2009 David Trowbridge + * 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 +#include "parasite.h" +#include "prop-list.h" +#include "classes-list.h" +#include "css-editor.h" +#include "object-hierarchy.h" +#include "widget-tree.h" +#include "python-hooks.h" +#include "python-shell.h" +#include "button-path.h" +#include "themes.h" + +static void +on_widget_tree_selection_changed (ParasiteWidgetTree *widget_tree, + ParasiteWindow *parasite) +{ + GObject *selected = parasite_widget_tree_get_selected_object (widget_tree); + + if (selected != NULL) + { + parasite_proplist_set_object (PARASITE_PROPLIST (parasite->prop_list), + selected); + parasite_objecthierarchy_set_object (PARASITE_OBJECTHIERARCHY (parasite->oh), + selected); + + if (GTK_IS_WIDGET (selected)) + { + GtkWidget *widget = GTK_WIDGET (selected); + + gtkparasite_flash_widget(parasite, widget); + parasite_buttonpath_set_widget (PARASITE_BUTTONPATH (parasite->button_path), widget); + parasite_classeslist_set_widget (PARASITE_CLASSESLIST (parasite->classes_list), widget); + parasite_csseditor_set_widget (PARASITE_CSSEDITOR (parasite->widget_css_editor), widget); + } + else + { + gtk_widget_set_sensitive (parasite->classes_list, FALSE); + gtk_widget_set_sensitive (parasite->widget_css_editor, FALSE); + } + } +} + + +static gboolean +on_widget_tree_button_press(ParasiteWidgetTree *widget_tree, + GdkEventButton *event, + ParasiteWindow *parasite) +{ + if (event->button == 3) + { + gtk_menu_popup(GTK_MENU(parasite->widget_popup), NULL, NULL, + NULL, NULL, event->button, event->time); + } + + return FALSE; +} + + +static void +on_send_widget_to_shell_activate(GtkWidget *menuitem, + ParasiteWindow *parasite) +{ + char *str; + GObject *object; + + object = parasite_widget_tree_get_selected_object (PARASITE_WIDGET_TREE (parasite->widget_tree)); + + if (!object) + return; + + str = g_strdup_printf ("parasite.gobj(%p)", object); + parasite_python_shell_append_text (PARASITE_PYTHON_SHELL (parasite->python_shell), + str, + NULL); + + g_free(str); + parasite_python_shell_focus (PARASITE_PYTHON_SHELL (parasite->python_shell)); +} + + +static GtkWidget * +create_widget_list_pane(ParasiteWindow *parasite) +{ + GtkWidget *swin; + + swin = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_ALWAYS, + "shadow-type", GTK_SHADOW_IN, + "width-request", 250, + "expand", TRUE, + NULL); + + parasite->widget_tree = parasite_widget_tree_new(); + gtk_container_add(GTK_CONTAINER(swin), parasite->widget_tree); + + g_signal_connect(G_OBJECT(parasite->widget_tree), + "widget-changed", + G_CALLBACK(on_widget_tree_selection_changed), + parasite); + + if (parasite_python_is_enabled()) + { + g_signal_connect(G_OBJECT(parasite->widget_tree), + "button-press-event", + G_CALLBACK(on_widget_tree_button_press), + parasite); + } + + return swin; +} + +static GtkWidget * +create_prop_list_pane(ParasiteWindow *parasite) +{ + GtkWidget *swin; + + swin = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_ALWAYS, + "shadow-type", GTK_SHADOW_IN, + "width-request", 250, + NULL); + + parasite->prop_list = parasite_proplist_new (parasite->widget_tree); + gtk_container_add(GTK_CONTAINER(swin), parasite->prop_list); + + return swin; +} + +static void +on_show_graphic_updates_toggled(GtkWidget *toggle_button, + ParasiteWindow *parasite) +{ + gdk_window_set_debug_updates( + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button))); +} + +static GtkWidget * +create_toolbar (ParasiteWindow *window) { + GtkWidget *button; + GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + GtkStyleContext *context = gtk_widget_get_style_context (box); + GtkWidget *image; + + gtk_style_context_add_class (context, "linked"); + + button = gtkparasite_inspect_button_new (window); + gtk_container_add (GTK_CONTAINER (box), button); + + button = gtk_toggle_button_new (); + image = gtk_image_new_from_icon_name ("view-refresh", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), image); + gtk_widget_set_tooltip_text (button, "Show Graphic Updates"); + gtk_container_add (GTK_CONTAINER (box), button); + g_signal_connect (button, + "toggled", + G_CALLBACK (on_show_graphic_updates_toggled), + window); + + gtk_widget_show_all (box); + return box; +} + +static void +delete_window (GtkWidget *widget) { + GApplication *app = g_application_get_default (); + + gtk_widget_hide (widget); + + if (app) + g_application_quit (app); + else + exit (0); +} + +void +gtkparasite_window_create() +{ + ParasiteWindow *window; + GtkWidget *vpaned, *hpaned; + GtkWidget *header; + GtkWidget *box; + GtkWidget *nb; + char *title; + + window = g_new0(ParasiteWindow, 1); + + /* + * Create the top-level window. + */ + window->window = g_object_new (GTK_TYPE_WINDOW, + "default-height", 500, + "default-width", 1000, + NULL); + g_signal_connect (window->window, + "delete-event", + G_CALLBACK (delete_window), + NULL); + + title = g_strdup_printf("Parasite - %s", g_get_application_name()); + gtk_window_set_title (GTK_WINDOW (window->window), title); + + header = gtk_header_bar_new (); + gtk_header_bar_set_title (GTK_HEADER_BAR (header), title); + gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE); + gtk_window_set_titlebar (GTK_WINDOW (window->window), header); + gtk_header_bar_pack_start (GTK_HEADER_BAR (header), create_toolbar (window)); + + g_free(title); + + nb = g_object_new (GTK_TYPE_NOTEBOOK, + "show-border", FALSE, + "margin-left", 6, + "margin-right", 6, + "margin-bottom", 6, + NULL); + gtk_container_add (GTK_CONTAINER (window->window), nb); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + box, + gtk_label_new ("Widget Tree")); + + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + parasite_themes_new (), + gtk_label_new ("Themes")); + + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + parasite_csseditor_new (TRUE), + gtk_label_new ("Custom CSS")); + + window->button_path = parasite_buttonpath_new (); + gtk_container_add (GTK_CONTAINER (box), window->button_path); + + hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_container_add (GTK_CONTAINER (box), hpaned); + + vpaned = gtk_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_paned_pack1 (GTK_PANED (hpaned), vpaned, TRUE, FALSE); + gtk_paned_pack1 (GTK_PANED (vpaned), create_widget_list_pane (window), TRUE, FALSE); + + nb = g_object_new (GTK_TYPE_NOTEBOOK, + "enable-popup", TRUE, + "show-border", FALSE, + NULL); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + create_prop_list_pane (window), + gtk_label_new ("GObject Properties")); + + window->oh = parasite_objecthierarchy_new (); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + window->oh, + gtk_label_new ("Hierarchy")); + + window->classes_list = parasite_classeslist_new (); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + window->classes_list, + gtk_label_new ("CSS Classes")); + + window->widget_css_editor = parasite_csseditor_new (FALSE); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), + window->widget_css_editor, + gtk_label_new ("Custom CSS")); + + gtk_paned_pack2 (GTK_PANED (hpaned), nb, FALSE, FALSE); + + if (parasite_python_is_enabled()) + { + GtkWidget *menuitem; + + window->python_shell = parasite_python_shell_new(); + gtk_paned_pack2(GTK_PANED(vpaned), window->python_shell, FALSE, FALSE); + + /* + * XXX Eventually we'll want to put more in here besides the menu + * item we define below. At that point, we'll need to make this + * more generic. + */ + window->widget_popup = gtk_menu_new(); + gtk_widget_show(window->widget_popup); + + menuitem = gtk_menu_item_new_with_label("Send Widget to Shell"); + gtk_widget_show(menuitem); + gtk_menu_shell_append(GTK_MENU_SHELL(window->widget_popup), menuitem); + + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(on_send_widget_to_shell_activate), window); + } + + gtk_widget_show_all (window->window); +} + +// vim: set et sw=4 ts=4: