[gtk/wip/otte/nodeeditor2: 22/30] Add gtk4-node-editor



commit 860ab29430c9f15db01cbc3dc5c200a8f0b35b7a
Author: Benjamin Otte <otte redhat com>
Date:   Tue Mar 12 15:05:25 2019 +0100

    Add gtk4-node-editor
    
    It's meant to be a little editor for render nodes so we can do testing
    with it.

 demos/meson.build                           |   1 +
 demos/node-editor/main.c                    |  28 ++
 demos/node-editor/meson.build               |  17 ++
 demos/node-editor/node-editor-application.c | 114 ++++++++
 demos/node-editor/node-editor-application.h |  38 +++
 demos/node-editor/node-editor-window.c      | 431 ++++++++++++++++++++++++++++
 demos/node-editor/node-editor-window.h      |  42 +++
 demos/node-editor/node-editor-window.ui     |  99 +++++++
 demos/node-editor/node-editor.gresource.xml |   6 +
 9 files changed, 776 insertions(+)
---
diff --git a/demos/meson.build b/demos/meson.build
index e45a7c8b4a..6f1905ddb6 100644
--- a/demos/meson.build
+++ b/demos/meson.build
@@ -1,3 +1,4 @@
 subdir('gtk-demo')
 subdir('icon-browser')
+subdir('node-editor')
 subdir('widget-factory')
diff --git a/demos/node-editor/main.c b/demos/node-editor/main.c
new file mode 100644
index 0000000000..8aec52a759
--- /dev/null
+++ b/demos/node-editor/main.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include <node-editor-application.h>
+
+int
+main (int argc, char *argv[])
+{
+  return g_application_run (G_APPLICATION (node_editor_application_new ()), argc, argv);
+}
diff --git a/demos/node-editor/meson.build b/demos/node-editor/meson.build
new file mode 100644
index 0000000000..b12bc326e6
--- /dev/null
+++ b/demos/node-editor/meson.build
@@ -0,0 +1,17 @@
+node_editor_sources = [
+  'main.c',
+  'node-editor-application.c',
+  'node-editor-window.c',
+]
+
+node_editor_resources = gnome.compile_resources('node_editor_resources',
+                                                'node-editor.gresource.xml',
+                                                source_dir: '.')
+
+executable('gtk4-node-editor',
+           node_editor_sources, node_editor_resources,
+           dependencies: libgtk_dep,
+           include_directories: confinc,
+           gui_app: true,
+           link_args: extra_demo_ldflags,
+           install: false)
diff --git a/demos/node-editor/node-editor-application.c b/demos/node-editor/node-editor-application.c
new file mode 100644
index 0000000000..9455cf3bc3
--- /dev/null
+++ b/demos/node-editor/node-editor-application.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "node-editor-application.h"
+
+#include "node-editor-window.h"
+
+struct _NodeEditorApplication
+{
+  GtkApplication parent;
+};
+
+struct _NodeEditorApplicationClass
+{
+  GtkApplicationClass parent_class;
+};
+
+G_DEFINE_TYPE(NodeEditorApplication, node_editor_application, GTK_TYPE_APPLICATION);
+
+static void
+node_editor_application_init (NodeEditorApplication *app)
+{
+}
+
+static void
+quit_activated (GSimpleAction *action,
+                GVariant      *parameter,
+                gpointer       data)
+{
+  g_application_quit (G_APPLICATION (data));
+}
+
+static GActionEntry app_entries[] =
+{
+  { "quit", quit_activated, NULL, NULL, NULL }
+};
+
+static void
+node_editor_application_startup (GApplication *app)
+{
+  const gchar *quit_accels[2] = { "<Ctrl>Q", NULL };
+
+  G_APPLICATION_CLASS (node_editor_application_parent_class)->startup (app);
+
+  g_action_map_add_action_entries (G_ACTION_MAP (app),
+                                   app_entries, G_N_ELEMENTS (app_entries),
+                                   app);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (app),
+                                         "app.quit",
+                                         quit_accels);
+}
+
+static void
+node_editor_application_activate (GApplication *app)
+{
+  NodeEditorWindow *win;
+
+  win = node_editor_window_new (NODE_EDITOR_APPLICATION (app));
+  gtk_window_present (GTK_WINDOW (win));
+}
+
+static void
+node_editor_application_open (GApplication  *app,
+                              GFile        **files,
+                              gint           n_files,
+                              const gchar   *hint)
+{
+  NodeEditorWindow *win;
+  gint i;
+
+  for (i = 0; i < n_files; i++)
+    {
+      win = node_editor_window_new (NODE_EDITOR_APPLICATION (app));
+      node_editor_window_load (win, files[i]);
+      gtk_window_present (GTK_WINDOW (win));
+    }
+}
+
+static void
+node_editor_application_class_init (NodeEditorApplicationClass *class)
+{
+  GApplicationClass *application_class = G_APPLICATION_CLASS (class);
+
+  application_class->startup = node_editor_application_startup;
+  application_class->activate = node_editor_application_activate;
+  application_class->open = node_editor_application_open;
+}
+
+NodeEditorApplication *
+node_editor_application_new (void)
+{
+  return g_object_new (NODE_EDITOR_APPLICATION_TYPE,
+                       "application-id", "org.gtk.gtk4.NodeEditor",
+                       "flags", G_APPLICATION_HANDLES_OPEN,
+                       NULL);
+}
diff --git a/demos/node-editor/node-editor-application.h b/demos/node-editor/node-editor-application.h
new file mode 100644
index 0000000000..90fbb24cb6
--- /dev/null
+++ b/demos/node-editor/node-editor-application.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __NODE_EDITOR_APPLICATION_H__
+#define __NODE_EDITOR_APPLICATION_H__
+
+#include <gtk/gtk.h>
+
+
+#define NODE_EDITOR_APPLICATION_TYPE (node_editor_application_get_type ())
+#define NODE_EDITOR_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NODE_EDITOR_APPLICATION_TYPE, 
NodeEditorApplication))
+
+
+typedef struct _NodeEditorApplication       NodeEditorApplication;
+typedef struct _NodeEditorApplicationClass  NodeEditorApplicationClass;
+
+
+GType           node_editor_application_get_type    (void);
+NodeEditorApplication *node_editor_application_new         (void);
+
+
+#endif /* __NODE_EDITOR_APPLICATION_H__ */
diff --git a/demos/node-editor/node-editor-window.c b/demos/node-editor/node-editor-window.c
new file mode 100644
index 0000000000..ac276dd3f1
--- /dev/null
+++ b/demos/node-editor/node-editor-window.c
@@ -0,0 +1,431 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "node-editor-window.h"
+
+#include "gsk/gskrendernodeparserprivate.h"
+
+typedef struct
+{
+  gsize  start_chars;
+  gsize  end_chars;
+  char  *message;
+} TextViewError;
+
+struct _NodeEditorWindow
+{
+  GtkApplicationWindow parent;
+
+  GtkWidget *picture;
+  GtkWidget *text_view;
+  GtkTextBuffer *text_buffer;
+
+  GArray *errors;
+};
+
+struct _NodeEditorWindowClass
+{
+  GtkApplicationWindowClass parent_class;
+};
+
+G_DEFINE_TYPE(NodeEditorWindow, node_editor_window, GTK_TYPE_APPLICATION_WINDOW);
+
+static void
+text_view_error_free (TextViewError *e)
+{
+  g_free (e->message);
+}
+
+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
+deserialize_error_func (const GtkCssSection *section,
+                        const GError        *error,
+                        gpointer             user_data)
+{
+  const GtkCssLocation *start_location = gtk_css_section_get_start_location (section);
+  const GtkCssLocation *end_location = gtk_css_section_get_end_location (section);
+  NodeEditorWindow *self = user_data;
+  GtkTextIter start_iter, end_iter;
+  TextViewError text_view_error;
+
+  gtk_text_buffer_get_iter_at_line_offset (self->text_buffer, &start_iter,
+                                           start_location->lines,
+                                           start_location->line_chars);
+  gtk_text_buffer_get_iter_at_line_offset (self->text_buffer, &end_iter,
+                                           end_location->lines,
+                                           end_location->line_chars);
+
+  gtk_text_buffer_apply_tag_by_name (self->text_buffer, "error",
+                                     &start_iter, &end_iter);
+
+  text_view_error.start_chars = start_location->chars;
+  text_view_error.end_chars = end_location->chars;
+  text_view_error.message = g_strdup (error->message);
+  g_array_append_val (self->errors, text_view_error);
+}
+
+static void
+text_changed (GtkTextBuffer    *buffer,
+              NodeEditorWindow *self)
+{
+  GskRenderNode *node;
+  char *text;
+  GBytes *bytes;
+
+  g_array_remove_range (self->errors, 0, self->errors->len);
+  text = get_current_text (self->text_buffer);
+  bytes = g_bytes_new_take (text, strlen (text));
+
+  /* If this is too slow, go fix the parser performance */
+  node = gsk_render_node_deserialize (bytes, deserialize_error_func, self);
+  g_bytes_unref (bytes);
+  if (node)
+    {
+      /* XXX: Is this code necessary or can we have API to turn nodes into paintables? */
+      GtkSnapshot *snapshot;
+      GdkPaintable *paintable;
+      graphene_rect_t bounds;
+
+      snapshot = gtk_snapshot_new ();
+      gsk_render_node_get_bounds (node, &bounds);
+      gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- bounds.origin.x, - bounds.origin.y));
+      gtk_snapshot_append_node (snapshot, node);
+      gsk_render_node_unref (node);
+      paintable = gtk_snapshot_free_to_paintable (snapshot, &bounds.size);
+      gtk_picture_set_paintable (GTK_PICTURE (self->picture), paintable);
+      g_clear_object (&paintable);
+    }
+  else
+    {
+      gtk_picture_set_paintable (GTK_PICTURE (self->picture), NULL);
+    }
+}
+
+static gboolean
+text_view_query_tooltip_cb (GtkWidget        *widget,
+                            int               x,
+                            int               y,
+                            gboolean          keyboard_tip,
+                            GtkTooltip       *tooltip,
+                            NodeEditorWindow *self)
+{
+  GtkTextIter iter;
+  guint i;
+
+  if (keyboard_tip)
+    {
+      gint offset;
+
+      g_object_get (self->text_buffer, "cursor-position", &offset, NULL);
+      gtk_text_buffer_get_iter_at_offset (self->text_buffer, &iter, offset);
+    }
+  else
+    {
+      gint bx, by, trailing;
+
+      gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (self->text_view), GTK_TEXT_WINDOW_TEXT,
+                                             x, y, &bx, &by);
+      gtk_text_view_get_iter_at_position (GTK_TEXT_VIEW (self->text_view), &iter, &trailing, bx, by);
+    }
+
+  for (i = 0; i < self->errors->len; i ++)
+    {
+      const TextViewError *e = &g_array_index (self->errors, TextViewError, i);
+      GtkTextIter start_iter, end_iter;
+
+      gtk_text_buffer_get_iter_at_offset (self->text_buffer, &start_iter, e->start_chars);
+      gtk_text_buffer_get_iter_at_offset (self->text_buffer, &end_iter, e->end_chars);
+
+      if (gtk_text_iter_in_range (&iter, &start_iter, &end_iter))
+        {
+          gtk_tooltip_set_text (tooltip, e->message);
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+gboolean
+node_editor_window_load (NodeEditorWindow *self,
+                         GFile            *file)
+{
+  GtkTextIter end;
+  GBytes *bytes;
+
+  bytes = g_file_load_bytes (file, NULL, NULL, NULL);
+  if (bytes == NULL)
+    return FALSE;
+
+  if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
+    {
+      g_bytes_unref (bytes);
+      return FALSE;
+    }
+
+  gtk_text_buffer_get_end_iter (self->text_buffer, &end);
+  gtk_text_buffer_insert (self->text_buffer,
+                          &end,
+                          g_bytes_get_data (bytes, NULL),
+                          g_bytes_get_size (bytes));
+
+  return TRUE;
+}
+
+static void
+open_response_cb (GtkWidget        *dialog,
+                  gint              response,
+                  NodeEditorWindow *self)
+{
+  gtk_widget_hide (dialog);
+
+  if (response == GTK_RESPONSE_ACCEPT)
+    {
+      GFile *file;
+
+      file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+      node_editor_window_load (self, file);
+      g_object_unref (file);
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+open_cb (GtkWidget        *button,
+         NodeEditorWindow *self)
+{
+  GtkWidget *dialog;
+
+  dialog = gtk_file_chooser_dialog_new ("Open node file",
+                                        GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))),
+                                        GTK_FILE_CHOOSER_ACTION_OPEN,
+                                        "_Cancel", GTK_RESPONSE_CANCEL,
+                                        "_Load", GTK_RESPONSE_ACCEPT,
+                                        NULL);
+
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), ".");
+  g_signal_connect (dialog, "response", G_CALLBACK (open_response_cb), self);
+  gtk_widget_show (dialog);
+}
+
+static void
+save_response_cb (GtkWidget        *dialog,
+                  gint              response,
+                  NodeEditorWindow *self)
+{
+  gtk_widget_hide (dialog);
+
+  if (response == GTK_RESPONSE_ACCEPT)
+    {
+      char *text, *filename;
+      GError *error = NULL;
+
+      text = get_current_text (self->text_buffer);
+
+      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+      if (!g_file_set_contents (filename, text, -1, &error))
+        {
+          GtkWidget *dialog;
+
+          dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
+                                           GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
+                                           GTK_MESSAGE_INFO,
+                                           GTK_BUTTONS_OK,
+                                           "Saving failed");
+          gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                                    "%s", error->message);
+          g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+          gtk_widget_show (dialog);
+          g_error_free (error);
+        }
+      g_free (filename);
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+save_cb (GtkWidget        *button,
+         NodeEditorWindow *self)
+{
+  GtkWidget *dialog;
+
+  dialog = gtk_file_chooser_dialog_new ("Save node",
+                                        GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))),
+                                        GTK_FILE_CHOOSER_ACTION_SAVE,
+                                        "_Cancel", GTK_RESPONSE_CANCEL,
+                                        "_Save", GTK_RESPONSE_ACCEPT,
+                                        NULL);
+
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), ".");
+  g_signal_connect (dialog, "response", G_CALLBACK (save_response_cb), self);
+  gtk_widget_show (dialog);
+}
+
+static GdkTexture *
+create_texture (NodeEditorWindow *self)
+{
+  GdkPaintable *paintable;
+  GtkSnapshot *snapshot;
+  GskRenderer *renderer;
+  GskRenderNode *node;
+  GdkTexture *texture;
+
+  paintable = gtk_picture_get_paintable (GTK_PICTURE (self->picture));
+  if (paintable == NULL ||
+      gdk_paintable_get_intrinsic_width (paintable) <= 0 ||
+      gdk_paintable_get_intrinsic_height (paintable) <= 0)
+    return NULL;
+  snapshot = gtk_snapshot_new ();
+  gdk_paintable_snapshot (paintable, snapshot, gdk_paintable_get_intrinsic_width (paintable), 
gdk_paintable_get_intrinsic_height (paintable));
+  node = gtk_snapshot_free_to_node (snapshot);
+  if (node == NULL)
+    return NULL;
+
+  /* ahem */
+  renderer = GTK_ROOT_GET_IFACE (gtk_widget_get_root (GTK_WIDGET (self)))->get_renderer (gtk_widget_get_root 
(GTK_WIDGET (self)));
+  texture = gsk_renderer_render_texture (renderer, node, NULL);
+  gsk_render_node_unref (node);
+
+  return texture;
+}
+
+static void
+export_image_response_cb (GtkWidget  *dialog,
+                          gint        response,
+                          GdkTexture *texture)
+{
+  gtk_widget_hide (dialog);
+
+  if (response == GTK_RESPONSE_ACCEPT)
+    {
+      char *filename;
+
+      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+      if (!gdk_texture_save_to_png (texture, filename))
+        {
+          GtkWidget *message_dialog;
+
+          message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW 
(dialog))),
+                                                   GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
+                                                   GTK_MESSAGE_INFO,
+                                                   GTK_BUTTONS_OK,
+                                                   "Exporting to image failed");
+          g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+          gtk_widget_show (message_dialog);
+        }
+      g_free (filename);
+    }
+
+  gtk_widget_destroy (dialog);
+  g_object_unref (texture);
+}
+
+static void
+export_image_cb (GtkWidget        *button,
+                 NodeEditorWindow *self)
+{
+  GdkTexture *texture;
+  GtkWidget *dialog;
+
+  texture = create_texture (self);
+  if (texture == NULL)
+    return;
+
+  dialog = gtk_file_chooser_dialog_new ("",
+                                        GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))),
+                                        GTK_FILE_CHOOSER_ACTION_SAVE,
+                                        "_Cancel", GTK_RESPONSE_CANCEL,
+                                        "_Save", GTK_RESPONSE_ACCEPT,
+                                        NULL);
+
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
+  g_signal_connect (dialog, "response", G_CALLBACK (export_image_response_cb), texture);
+  gtk_widget_show (dialog);
+}
+
+static void
+node_editor_window_finalize (GObject *object)
+{
+  NodeEditorWindow *self = (NodeEditorWindow *)object;
+
+  g_array_free (self->errors, TRUE);
+
+  G_OBJECT_CLASS (node_editor_window_parent_class)->finalize (object);
+}
+
+static void
+node_editor_window_class_init (NodeEditorWindowClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->finalize = node_editor_window_finalize;
+
+  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
+                                               "/org/gtk/gtk4/node-editor/node-editor-window.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, text_buffer);
+  gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, text_view);
+  gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, picture);
+
+  gtk_widget_class_bind_template_callback (widget_class, text_changed);
+  gtk_widget_class_bind_template_callback (widget_class, text_view_query_tooltip_cb);
+  gtk_widget_class_bind_template_callback (widget_class, open_cb);
+  gtk_widget_class_bind_template_callback (widget_class, save_cb);
+  gtk_widget_class_bind_template_callback (widget_class, export_image_cb);
+}
+
+static void
+node_editor_window_init (NodeEditorWindow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->errors = g_array_new (FALSE, TRUE, sizeof (TextViewError));
+  g_array_set_clear_func (self->errors, (GDestroyNotify)text_view_error_free);
+}
+
+NodeEditorWindow *
+node_editor_window_new (NodeEditorApplication *application)
+{
+  return g_object_new (NODE_EDITOR_WINDOW_TYPE,
+                       "application", application,
+                       NULL);
+}
diff --git a/demos/node-editor/node-editor-window.h b/demos/node-editor/node-editor-window.h
new file mode 100644
index 0000000000..9eaf6aed3f
--- /dev/null
+++ b/demos/node-editor/node-editor-window.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __NODE_EDITOR_WINDOW_H__
+#define __NODE_EDITOR_WINDOW_H__
+
+#include <gtk/gtk.h>
+
+#include "node-editor-application.h"
+
+#define NODE_EDITOR_WINDOW_TYPE (node_editor_window_get_type ())
+#define NODE_EDITOR_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NODE_EDITOR_WINDOW_TYPE, 
NodeEditorWindow))
+
+
+typedef struct _NodeEditorWindow         NodeEditorWindow;
+typedef struct _NodeEditorWindowClass    NodeEditorWindowClass;
+
+
+GType                   node_editor_window_get_type     (void);
+
+NodeEditorWindow *      node_editor_window_new          (NodeEditorApplication  *application);
+
+gboolean                node_editor_window_load         (NodeEditorWindow       *self,
+                                                         GFile                  *file);
+
+#endif /* __NODE_EDITOR_WINDOW_H__ */
diff --git a/demos/node-editor/node-editor-window.ui b/demos/node-editor/node-editor-window.ui
new file mode 100644
index 0000000000..e886b53ee0
--- /dev/null
+++ b/demos/node-editor/node-editor-window.ui
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <object class="GtkTextTagTable" id="tags">
+    <child type="tag">
+      <object class="GtkTextTag">
+        <property name="name">error</property>
+        <property name="underline">error</property>
+      </object>
+    </child>
+  </object>
+  <object class="GtkTextBuffer" id="text_buffer">
+    <property name="tag-table">tags</property>
+    <signal name="changed" handler="text_changed"/>
+  </object>
+  <template class="NodeEditorWindow" parent="GtkApplicationWindow">
+    <style>
+      <class name="devel"/>
+    </style>
+    <property name="title" translatable="yes">GTK Node Editor</property>
+    <property name="default-width">1024</property>
+    <property name="default-height">768</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="header">
+        <property name="title" translatable="yes">GTK Node Editor</property>
+        <property name="show-title-buttons">1</property>
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">document-open-symbolic</property>
+            <property name="tooltip-text">Open node file</property>
+            <signal name="clicked" handler="open_cb"/>
+          </object>
+        </child>
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">document-save-symbolic</property>
+            <property name="tooltip-text">Save to node file</property>
+            <signal name="clicked" handler="save_cb"/>
+          </object>
+        </child>
+        <child type="start">
+          <object class="GtkButton">
+            <property name="icon-name">insert-image-symbolic</property>
+            <property name="tooltip-text">Export to image</property>
+            <signal name="clicked" handler="export_image_cb"/>
+          </object>
+        </child>
+        <child type="title">
+          <object class="GtkLabel">
+            <property name="label" translatable="yes">GTK Node Editor</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkPaned">
+        <property name="shrink-child2">false</property>
+        <property name="position">400</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="hscrollbar-policy">never</property>
+            <property name="expand">1</property>
+            <child>
+              <object class="GtkTextView" id="text_view">
+                <property name="buffer">text_buffer</property>
+                <property name="wrap-mode">word</property>
+                <property name="monospace">1</property>
+                <property name="has-focus">1</property>
+                <property name="top-margin">6</property>
+                <property name="left-margin">6</property>
+                <property name="right-margin">6</property>
+                <property name="bottom-margin">6</property>
+                <property name="has-tooltip">1</property>
+                <signal name="query-tooltip" handler="text_view_query_tooltip_cb"/>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="expand">1</property>
+            <property name="min-content-height">100</property>
+            <property name="min-content-width">100</property>
+            <child>
+              <object class="GtkViewport">
+                <child>
+                  <object class="GtkPicture" id="picture">
+                    <property name="can-shrink">0</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/node-editor/node-editor.gresource.xml b/demos/node-editor/node-editor.gresource.xml
new file mode 100644
index 0000000000..ca6cdd2c1b
--- /dev/null
+++ b/demos/node-editor/node-editor.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gtk/gtk4/node-editor">
+    <file preprocess="xml-stripblanks">node-editor-window.ui</file>
+  </gresource>
+</gresources>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]