GtkFileChooser's API predates GIO by a few years, so it started off with filenames and URI as character arrays. After introducing GIO as a dependency, the API included GFile-based entry points. It's much more appropriate to use GFile everywhere, as we want to encourage people to use GIO instead of passing random bytes to low level POSIX API. See: #2455
870 lines
28 KiB
C
870 lines
28 KiB
C
/*
|
|
* 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 "gtkrendererpaintableprivate.h"
|
|
|
|
#include "gsk/gskrendernodeparserprivate.h"
|
|
|
|
#ifndef NODE_EDITOR_SOURCE_DIR
|
|
#define NODE_EDITOR_SOURCE_DIR "." /* Fallback */
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
gsize start_chars;
|
|
gsize end_chars;
|
|
char *message;
|
|
} TextViewError;
|
|
|
|
struct _NodeEditorWindow
|
|
{
|
|
GtkApplicationWindow parent;
|
|
|
|
GtkWidget *picture;
|
|
GtkWidget *text_view;
|
|
GtkTextBuffer *text_buffer;
|
|
GtkTextTagTable *tag_table;
|
|
|
|
GtkWidget *testcase_popover;
|
|
GtkWidget *testcase_error_label;
|
|
GtkWidget *testcase_cairo_checkbutton;
|
|
GtkWidget *testcase_name_entry;
|
|
GtkWidget *testcase_save_button;
|
|
|
|
GtkWidget *renderer_listbox;
|
|
GListStore *renderers;
|
|
GdkPaintable *paintable;
|
|
|
|
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);
|
|
|
|
return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
|
}
|
|
|
|
static void
|
|
text_buffer_remove_all_tags (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);
|
|
}
|
|
|
|
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_iter_skip_alpha_backward (GtkTextIter *iter)
|
|
{
|
|
/* Just skip to the previous non-whitespace char */
|
|
|
|
while (!gtk_text_iter_is_start (iter))
|
|
{
|
|
gunichar c = gtk_text_iter_get_char (iter);
|
|
|
|
if (g_unichar_isspace (c))
|
|
{
|
|
gtk_text_iter_forward_char (iter);
|
|
break;
|
|
}
|
|
|
|
gtk_text_iter_backward_char (iter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
text_iter_skip_whitespace_backward (GtkTextIter *iter)
|
|
{
|
|
while (!gtk_text_iter_is_start (iter))
|
|
{
|
|
gunichar c = gtk_text_iter_get_char (iter);
|
|
|
|
if (g_unichar_isalpha (c))
|
|
{
|
|
gtk_text_iter_forward_char (iter);
|
|
break;
|
|
}
|
|
|
|
gtk_text_iter_backward_char (iter);
|
|
}
|
|
}
|
|
|
|
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);
|
|
text_buffer_remove_all_tags (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;
|
|
guint i;
|
|
|
|
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);
|
|
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->renderers)); i++)
|
|
{
|
|
gpointer item = g_list_model_get_item (G_LIST_MODEL (self->renderers), i);
|
|
gtk_renderer_paintable_set_paintable (item, paintable);
|
|
g_object_unref (item);
|
|
}
|
|
g_clear_object (&paintable);
|
|
}
|
|
else
|
|
{
|
|
gtk_picture_set_paintable (GTK_PICTURE (self->picture), NULL);
|
|
}
|
|
|
|
GtkTextIter iter;
|
|
|
|
gtk_text_buffer_get_start_iter (self->text_buffer, &iter);
|
|
|
|
while (!gtk_text_iter_is_end (&iter))
|
|
{
|
|
gunichar c = gtk_text_iter_get_char (&iter);
|
|
|
|
if (c == '{')
|
|
{
|
|
GtkTextIter word_end = iter;
|
|
GtkTextIter word_start;
|
|
|
|
gtk_text_iter_backward_char (&word_end);
|
|
text_iter_skip_whitespace_backward (&word_end);
|
|
|
|
word_start = word_end;
|
|
gtk_text_iter_backward_word_start (&word_start);
|
|
text_iter_skip_alpha_backward (&word_start);
|
|
|
|
gtk_text_buffer_apply_tag_by_name (self->text_buffer, "nodename",
|
|
&word_start, &word_end);
|
|
}
|
|
else if (c == ':')
|
|
{
|
|
GtkTextIter word_end = iter;
|
|
GtkTextIter word_start;
|
|
|
|
gtk_text_iter_backward_char (&word_end);
|
|
text_iter_skip_whitespace_backward (&word_end);
|
|
|
|
word_start = word_end;
|
|
gtk_text_iter_backward_word_start (&word_start);
|
|
text_iter_skip_alpha_backward (&word_start);
|
|
|
|
gtk_text_buffer_apply_tag_by_name (self->text_buffer, "propname",
|
|
&word_start, &word_end);
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
GtkTextIter string_start = iter;
|
|
GtkTextIter string_end = iter;
|
|
|
|
gtk_text_iter_forward_char (&iter);
|
|
while (!gtk_text_iter_is_end (&iter))
|
|
{
|
|
c = gtk_text_iter_get_char (&iter);
|
|
|
|
if (c == '"')
|
|
{
|
|
gtk_text_iter_forward_char (&iter);
|
|
string_end = iter;
|
|
break;
|
|
}
|
|
|
|
gtk_text_iter_forward_char (&iter);
|
|
}
|
|
|
|
gtk_text_buffer_apply_tag_by_name (self->text_buffer, "string",
|
|
&string_start, &string_end);
|
|
}
|
|
|
|
gtk_text_iter_forward_char (&iter);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
text_view_query_tooltip_cb (GtkWidget *widget,
|
|
int x,
|
|
int y,
|
|
gboolean keyboard_tip,
|
|
GtkTooltip *tooltip,
|
|
NodeEditorWindow *self)
|
|
{
|
|
GtkTextIter iter;
|
|
guint i;
|
|
GString *text;
|
|
|
|
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);
|
|
}
|
|
|
|
text = g_string_new ("");
|
|
|
|
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))
|
|
{
|
|
if (text->len > 0)
|
|
g_string_append (text, "\n");
|
|
g_string_append (text, e->message);
|
|
}
|
|
}
|
|
|
|
if (text->len > 0)
|
|
{
|
|
gtk_tooltip_set_text (tooltip, text->str);
|
|
g_string_free (text, TRUE);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_string_free (text, 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));
|
|
|
|
g_bytes_unref (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
|
|
show_open_filechooser (NodeEditorWindow *self)
|
|
{
|
|
GtkWidget *dialog;
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("Open node file",
|
|
GTK_WINDOW (self),
|
|
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);
|
|
|
|
GFile *cwd = g_file_new_for_path (".");
|
|
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL);
|
|
g_object_unref (cwd);
|
|
|
|
g_signal_connect (dialog, "response", G_CALLBACK (open_response_cb), self);
|
|
gtk_widget_show (dialog);
|
|
}
|
|
|
|
static void
|
|
open_cb (GtkWidget *button,
|
|
NodeEditorWindow *self)
|
|
{
|
|
show_open_filechooser (self);
|
|
}
|
|
|
|
static void
|
|
save_response_cb (GtkWidget *dialog,
|
|
gint response,
|
|
NodeEditorWindow *self)
|
|
{
|
|
gtk_widget_hide (dialog);
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
GFile *file;
|
|
char *text;
|
|
GError *error = NULL;
|
|
|
|
text = get_current_text (self->text_buffer);
|
|
|
|
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
|
g_file_replace_contents (file, text, -1,
|
|
NULL, FALSE,
|
|
G_FILE_CREATE_NONE,
|
|
NULL,
|
|
NULL,
|
|
&error);
|
|
if (error != NULL)
|
|
{
|
|
GtkWidget *dialog;
|
|
|
|
dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (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 (text);
|
|
g_object_unref (file);
|
|
}
|
|
|
|
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_root (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);
|
|
|
|
GFile *cwd = g_file_new_for_path (".");
|
|
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL);
|
|
g_object_unref (cwd);
|
|
|
|
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;
|
|
|
|
renderer = gtk_native_get_renderer (gtk_widget_get_native (GTK_WIDGET (self)));
|
|
texture = gsk_renderer_render_texture (renderer, node, NULL);
|
|
gsk_render_node_unref (node);
|
|
|
|
return texture;
|
|
}
|
|
|
|
static GdkTexture *
|
|
create_cairo_texture (NodeEditorWindow *self)
|
|
{
|
|
GdkPaintable *paintable;
|
|
GtkSnapshot *snapshot;
|
|
GskRenderer *renderer;
|
|
GskRenderNode *node;
|
|
GdkTexture *texture;
|
|
GdkSurface *surface;
|
|
|
|
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;
|
|
|
|
surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (self)));
|
|
renderer = gsk_cairo_renderer_new ();
|
|
gsk_renderer_realize (renderer, surface, NULL);
|
|
|
|
texture = gsk_renderer_render_texture (renderer, node, NULL);
|
|
gsk_render_node_unref (node);
|
|
gsk_renderer_unrealize (renderer);
|
|
g_object_unref (renderer);
|
|
|
|
return texture;
|
|
}
|
|
|
|
static void
|
|
export_image_response_cb (GtkWidget *dialog,
|
|
gint response,
|
|
GdkTexture *texture)
|
|
{
|
|
gtk_widget_hide (dialog);
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
GFile *file;
|
|
|
|
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
|
if (!gdk_texture_save_to_png (texture, g_file_peek_path (file)))
|
|
{
|
|
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_object_unref (file);
|
|
}
|
|
|
|
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_root (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
|
|
testcase_name_entry_changed_cb (GtkWidget *button,
|
|
GParamSpec *pspec,
|
|
NodeEditorWindow *self)
|
|
|
|
{
|
|
const char *text = gtk_editable_get_text (GTK_EDITABLE (self->testcase_name_entry));
|
|
|
|
if (strlen (text) > 0)
|
|
gtk_widget_set_sensitive (self->testcase_save_button, TRUE);
|
|
else
|
|
gtk_widget_set_sensitive (self->testcase_save_button, FALSE);
|
|
}
|
|
|
|
static void
|
|
testcase_save_clicked_cb (GtkWidget *button,
|
|
NodeEditorWindow *self)
|
|
{
|
|
const char *testcase_name = gtk_editable_get_text (GTK_EDITABLE (self->testcase_name_entry));
|
|
char *source_dir = g_canonicalize_filename (NODE_EDITOR_SOURCE_DIR, NULL);
|
|
char *node_file_name;
|
|
char *node_file;
|
|
char *png_file_name;
|
|
char *png_file;
|
|
char *text = NULL;
|
|
GdkTexture *texture;
|
|
GError *error = NULL;
|
|
|
|
node_file_name = g_strconcat (testcase_name, ".node", NULL);
|
|
node_file = g_build_filename (source_dir, node_file_name, NULL);
|
|
g_free (node_file_name);
|
|
|
|
png_file_name = g_strconcat (testcase_name, ".png", NULL);
|
|
png_file = g_build_filename (source_dir, png_file_name, NULL);
|
|
g_free (png_file_name);
|
|
|
|
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->testcase_cairo_checkbutton)))
|
|
texture = create_cairo_texture (self);
|
|
else
|
|
texture = create_texture (self);
|
|
|
|
if (!gdk_texture_save_to_png (texture, png_file))
|
|
{
|
|
gtk_label_set_label (GTK_LABEL (self->testcase_error_label),
|
|
"Could not save texture file");
|
|
goto out;
|
|
}
|
|
|
|
text = get_current_text (self->text_buffer);
|
|
if (!g_file_set_contents (node_file, text, -1, &error))
|
|
{
|
|
gtk_label_set_label (GTK_LABEL (self->testcase_error_label), error->message);
|
|
/* TODO: Remove texture file again? */
|
|
goto out;
|
|
}
|
|
|
|
gtk_editable_set_text (GTK_EDITABLE (self->testcase_name_entry), "");
|
|
gtk_popover_popdown (GTK_POPOVER (self->testcase_popover));
|
|
|
|
out:
|
|
g_free (text);
|
|
g_free (png_file);
|
|
g_free (node_file);
|
|
g_free (source_dir);
|
|
}
|
|
|
|
static void
|
|
node_editor_window_finalize (GObject *object)
|
|
{
|
|
NodeEditorWindow *self = (NodeEditorWindow *)object;
|
|
|
|
g_array_free (self->errors, TRUE);
|
|
|
|
g_clear_object (&self->renderers);
|
|
|
|
G_OBJECT_CLASS (node_editor_window_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
node_editor_window_add_renderer (NodeEditorWindow *self,
|
|
GskRenderer *renderer,
|
|
const char *description)
|
|
{
|
|
GdkSurface *surface;
|
|
GdkPaintable *paintable;
|
|
|
|
surface = gtk_native_get_surface (GTK_NATIVE (self));
|
|
g_assert (surface != NULL);
|
|
|
|
if (renderer != NULL && !gsk_renderer_realize (renderer, surface, NULL))
|
|
{
|
|
g_object_unref (renderer);
|
|
return;
|
|
}
|
|
|
|
paintable = gtk_renderer_paintable_new (renderer, gtk_picture_get_paintable (GTK_PICTURE (self->picture)));
|
|
g_object_set_data_full (G_OBJECT (paintable), "description", g_strdup (description), g_free);
|
|
g_clear_object (&renderer);
|
|
|
|
g_list_store_append (self->renderers, paintable);
|
|
g_object_unref (paintable);
|
|
}
|
|
|
|
static void
|
|
node_editor_window_realize (GtkWidget *widget)
|
|
{
|
|
NodeEditorWindow *self = NODE_EDITOR_WINDOW (widget);
|
|
|
|
GTK_WIDGET_CLASS (node_editor_window_parent_class)->realize (widget);
|
|
|
|
#if 0
|
|
node_editor_window_add_renderer (self,
|
|
NULL,
|
|
"Default");
|
|
#endif
|
|
node_editor_window_add_renderer (self,
|
|
gsk_gl_renderer_new (),
|
|
"OpenGL");
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
node_editor_window_add_renderer (self,
|
|
gsk_vulkan_renderer_new (),
|
|
"Vulkan");
|
|
#endif
|
|
#ifdef GDK_WINDOWING_BROADWAY
|
|
node_editor_window_add_renderer (self,
|
|
gsk_broadway_renderer_new (),
|
|
"Broadway");
|
|
#endif
|
|
node_editor_window_add_renderer (self,
|
|
gsk_cairo_renderer_new (),
|
|
"Cairo");
|
|
}
|
|
|
|
static void
|
|
node_editor_window_unrealize (GtkWidget *widget)
|
|
{
|
|
NodeEditorWindow *self = NODE_EDITOR_WINDOW (widget);
|
|
guint i;
|
|
|
|
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->renderers)); i ++)
|
|
{
|
|
gpointer item = g_list_model_get_item (G_LIST_MODEL (self->renderers), i);
|
|
gsk_renderer_unrealize (gtk_renderer_paintable_get_renderer (item));
|
|
g_object_unref (item);
|
|
}
|
|
|
|
g_list_store_remove_all (self->renderers);
|
|
|
|
GTK_WIDGET_CLASS (node_editor_window_parent_class)->unrealize (widget);
|
|
}
|
|
|
|
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 (widget_class,
|
|
"/org/gtk/gtk4/node-editor/node-editor-window.ui");
|
|
|
|
widget_class->realize = node_editor_window_realize;
|
|
widget_class->unrealize = node_editor_window_unrealize;
|
|
|
|
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_child (widget_class, NodeEditorWindow, renderer_listbox);
|
|
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_popover);
|
|
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_error_label);
|
|
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_cairo_checkbutton);
|
|
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_name_entry);
|
|
gtk_widget_class_bind_template_child (widget_class, NodeEditorWindow, testcase_save_button);
|
|
|
|
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);
|
|
gtk_widget_class_bind_template_callback (widget_class, testcase_save_clicked_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, testcase_name_entry_changed_cb);
|
|
}
|
|
|
|
static GtkWidget *
|
|
node_editor_window_create_renderer_widget (gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GdkPaintable *paintable = item;
|
|
GtkWidget *box, *label, *picture;
|
|
GtkWidget *row;
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_widget_set_size_request (box, 120, 90);
|
|
|
|
label = gtk_label_new (g_object_get_data (G_OBJECT (paintable), "description"));
|
|
gtk_container_add (GTK_CONTAINER (box), label);
|
|
|
|
picture = gtk_picture_new_for_paintable (paintable);
|
|
/* don't ever scale up, we want to be as accurate as possible */
|
|
gtk_widget_set_halign (picture, GTK_ALIGN_CENTER);
|
|
gtk_widget_set_valign (picture, GTK_ALIGN_CENTER);
|
|
gtk_container_add (GTK_CONTAINER (box), picture);
|
|
|
|
row = gtk_list_box_row_new ();
|
|
gtk_container_add (GTK_CONTAINER (row), box);
|
|
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
|
|
|
|
return row;
|
|
}
|
|
|
|
static void
|
|
window_open (GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
NodeEditorWindow *self = user_data;
|
|
|
|
show_open_filechooser (self);
|
|
}
|
|
|
|
static GActionEntry win_entries[] = {
|
|
{ "open", window_open, NULL, NULL, NULL },
|
|
};
|
|
|
|
static void
|
|
node_editor_window_init (NodeEditorWindow *self)
|
|
{
|
|
gtk_widget_init_template (GTK_WIDGET (self));
|
|
|
|
self->renderers = g_list_store_new (GDK_TYPE_PAINTABLE);
|
|
gtk_list_box_bind_model (GTK_LIST_BOX (self->renderer_listbox),
|
|
G_LIST_MODEL (self->renderers),
|
|
node_editor_window_create_renderer_widget,
|
|
self,
|
|
NULL);
|
|
|
|
self->errors = g_array_new (FALSE, TRUE, sizeof (TextViewError));
|
|
g_array_set_clear_func (self->errors, (GDestroyNotify)text_view_error_free);
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP (self), win_entries, G_N_ELEMENTS (win_entries), self);
|
|
|
|
self->tag_table = gtk_text_tag_table_new ();
|
|
gtk_text_tag_table_add (self->tag_table,
|
|
g_object_new (GTK_TYPE_TEXT_TAG,
|
|
"name", "error",
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
NULL));
|
|
gtk_text_tag_table_add (self->tag_table,
|
|
g_object_new (GTK_TYPE_TEXT_TAG,
|
|
"name", "nodename",
|
|
"foreground-rgba", &(GdkRGBA) { 0.9, 0.78, 0.53, 1},
|
|
NULL));
|
|
gtk_text_tag_table_add (self->tag_table,
|
|
g_object_new (GTK_TYPE_TEXT_TAG,
|
|
"name", "propname",
|
|
"foreground-rgba", &(GdkRGBA) { 0.7, 0.55, 0.67, 1},
|
|
NULL));
|
|
gtk_text_tag_table_add (self->tag_table,
|
|
g_object_new (GTK_TYPE_TEXT_TAG,
|
|
"name", "string",
|
|
"foreground-rgba", &(GdkRGBA) { 0.63, 0.73, 0.54, 1},
|
|
NULL));
|
|
gtk_text_tag_table_add (self->tag_table,
|
|
g_object_new (GTK_TYPE_TEXT_TAG,
|
|
"name", "number",
|
|
"foreground-rgba", &(GdkRGBA) { 0.8, 0.52, 0.43, 1},
|
|
NULL));
|
|
|
|
self->text_buffer = gtk_text_buffer_new (self->tag_table);
|
|
g_signal_connect (self->text_buffer, "changed", G_CALLBACK (text_changed), self);
|
|
gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->text_view), self->text_buffer);
|
|
}
|
|
|
|
NodeEditorWindow *
|
|
node_editor_window_new (NodeEditorApplication *application)
|
|
{
|
|
return g_object_new (NODE_EDITOR_WINDOW_TYPE,
|
|
"application", application,
|
|
NULL);
|
|
}
|