Impressum path-editor.c Sprache: unbekannt
|
|
Haftungsausschluß.c Download desAbap {ABAP[40] Lisp[29] Masm[29]}diese Dinge liegen außhalb unserer Verantwortung
/*
* Copyright © 2025 Red Hat, Inc
*
* 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/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
# include "path-editor.h"
# include "gtk/svg/gtksvgelementprivate.h"
struct _PathEditor
{
GtkWidget parent_instance;
double width;
double height;
GskPath *path;
PathPaintable *paintable;
GtkStack *path_cmds_stack;
GtkLabel *path_cmds_label;
GtkEntry *path_cmds_entry;
GtkButton *external_edit_button;
GtkMenuButton *id_dropdown;
};
enum
{
PROP_PATH = 1,
PROP_WIDTH,
PROP_HEIGHT,
PROP_ID,
NUM_PROPERTIES,
};
static GParamSpec *properties[NUM_PROPERTIES];
/* {{{ Path-to-SVG */
static gboolean
collect_path (GskPathOperation op,
const graphene_point_t *pts,
size_t n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[ 0].x, pts[ 0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[ 1].x, pts[ 1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[ 1].x, pts[ 1].y,
pts[ 2].x, pts[ 2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[ 1].x, pts[ 1].y,
pts[ 2].x, pts[ 2].y,
pts[ 3].x, pts[ 3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[ 1].x, pts[ 1].y,
pts[ 2].x, pts[ 2].y,
weight);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
static GskPath *
path_to_svg_path (GskPath *path)
{
GskPathBuilder *builder = gsk_path_builder_new ();
gsk_path_foreach (path,
GSK_PATH_FOREACH_ALLOW_QUAD | GSK_PATH_FOREACH_ALLOW_CUBIC,
collect_path,
builder);
return gsk_path_builder_free_to_path (builder);
}
/* }}} */
/* {{{ Callbacks */
static void
collect_ids (SvgElement *shape,
gpointer data)
{
GMenu *menu = data;
if (svg_element_get_type (shape) == SVG_ELEMENT_CLIP_PATH &&
svg_element_get_id (shape) != NULL)
{
g_autoptr (GMenuItem) item = NULL;
item = g_menu_item_new (svg_element_get_id (shape), NULL);
g_menu_item_set_action_and_target (item, "set_id", "s", svg_element_get_id (shape));
g_menu_append_item (menu, item);
}
}
static void
path_cmds_clicked (PathEditor *self)
{
if (self->paintable)
{
g_autoptr (GMenu) menu = g_menu_new ();
g_autoptr (GMenuItem) item = NULL;
item = g_menu_item_new ( "None", NULL);
g_menu_item_set_action_and_target (item, "set_id", "s", "");
g_menu_append_item (menu, item);
svg_element_foreach (path_paintable_get_svg (self->paintable)->content, collect_ids, m enu);
gtk_menu_button_set_menu_model (self->id_dropdown, G_MENU_MODEL (menu));
}
gtk_stack_set_visible_child_name (self->path_cmds_stack, "entry");
gtk_entry_grab_focus_without_selecting (self->path_cmds_entry);
}
static gboolean
path_cmds_key (PathEditor *self,
unsigned int keyval,
unsigned int keycode,
GdkModifierType state)
{
if (keyval == GDK_KEY_Escape)
{
g_autofree char *text = NULL;
text = gsk_path_to_string (self->path);
gtk_editable_set_text (GTK_EDITABLE (self->path_cmds_entry), text);
gtk_widget_remove_css_class (GTK_WIDGET (self->path_cmds_entry), "error");
gtk_accessible_reset_state (GTK_ACCESSIBLE (self->path_cmds_entry), GTK_ACCESSIBLE_STATE_INVALID);
gtk_stack_set_visible_child_name (self->path_cmds_stack, "label");
return TRUE;
}
return FALSE;
}
static void
path_cmds_activated (PathEditor *self)
{
const char *text;
g_autoptr (GskPath) path = NULL;
const char *id = NULL;
text = gtk_editable_get_text (GTK_EDITABLE (self->path_cmds_entry));
path = gsk_path_parse (text);
if (self->paintable)
{
SvgElement *shape = path_paintable_get_shape_by_id (self->paintable, text);
if (shape && svg_element_get_type (shape) == SVG_ELEMENT_CLIP_PATH)
id = text;
}
if (!path && !id)
{
gtk_widget_error_bell (GTK_WIDGET (self->path_cmds_entry));
gtk_widget_add_css_class (GTK_WIDGET (self->path_cmds_entry), "error");
gtk_accessible_update_state (GTK_ACCESSIBLE (self->path_cmds_entry),
GTK_ACCESSIBLE_STATE_INVALID, GTK_ACCESSIBLE_INVALID_TRUE,
-1);
return;
}
gtk_widget_remove_css_class (GTK_WIDGET (self->path_cmds_entry), "error");
gtk_accessible_reset_state (GTK_ACCESSIBLE (self->path_cmds_entry), GTK_ACCESSIBLE_STATE_INVALID);
if (strcmp (text, "") == 0)
gtk_label_set_label (self->path_cmds_label, "—");
else
gtk_label_set_label (self->path_cmds_label, text);
gtk_stack_set_visible_child_name (self->path_cmds_stack, "label");
g_clear_pointer (&self->path, gsk_path_unref);
if (path)
self->path = gsk_path_ref (path);
gtk_widget_set_sensitive (GTK_WIDGET (self->external_edit_button), path != NULL);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
}
static void
show_error (PathEditor *self,
const char *title,
const char *detail)
{
GtkWindow *window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self)));
g_autoptr (GtkAlertDialog) alert = NULL;
alert = gtk_alert_dialog_new ("title");
gtk_alert_dialog_set_detail (alert, detail);
gtk_alert_dialog_show (alert, window);
}
static void
temp_file_changed (GFileMonitor *monitor,
GFile *file,
GFile *other,
GFileMonitorEvent event,
PathEditor *self)
{
if (event == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
{
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
g_autoptr (PathPaintable) paintable = NULL;
bytes = g_file_load_bytes (file, NULL, NULL, &error);
if (!bytes)
{
show_error (self, "Editing Failed", error->message);
return;
}
paintable = path_paintable_new_from_bytes (bytes, &error);
if (!paintable)
{
show_error (self, "Editing Failed", error->message);
return;
}
gsk_path_unref (self->path);
self->path = gsk_path_ref (path_paintable_get_path_by_id (paintable, "path0"));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
}
}
static void
file_launched (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkFileLauncher *launcher = GTK_FILE_LAUNCHER (source);
g_autoptr (GError) error = NULL;
if (!gtk_file_launcher_launch_finish (launcher, result, &error))
{
g_print ("Failed to launch path editor: %s", error->message);
}
}
static void
edit_path_externally (PathEditor *self)
{
GString *str;
g_autoptr (GBytes) bytes = NULL;
g_autofree char *filename = NULL;
g_autoptr (GFile) file = NULL;
g_autoptr (GOutputStream) ostream = NULL;
gssize written;
g_autoptr (GError) error = NULL;
GFileMonitor *monitor;
g_autoptr (GskPath) path = NULL;
g_autoptr(GtkFileLauncher) launcher = NULL;
GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self));
if (!self->path)
{
gtk_widget_error_bell (GTK_WIDGET (self));
return;
}
path = path_to_svg_path (self->path);
str = g_string_new ("");
g_string_append_printf (str, "<svg width='%g' height='%g'>\n",
self->width, self->height);
g_string_append (str, "<path id='path0'\n"
" d='");
if (path)
gsk_path_print (path, str);
g_string_append (str, "'\n");
g_string_append (str,
" fill='none'\n"
" stroke='black'\n"
" stroke-width='2'\n"
" stroke-linejoin='round'\n"
" stroke-linecap='round'/>\n");
g_string_append (str, "</svg>");
bytes = g_string_free_to_bytes (str);
filename = g_build_filename (g_get_user_cache_dir (), "org.gtk.Shaper-path.svg", NULL);
file = g_file_new_for_path (filename);
ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error));
if (!ostream)
{
show_error (self, "Editing Failed", error->message);
return;
}
written = g_output_stream_write_bytes (ostream, bytes, NULL, &error);
if (written == -1)
{
show_error (self, "Editing Failed", error->message);
return;
}
g_output_stream_close (ostream, NULL, NULL);
monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
if (error)
{
show_error (self, "Editing Failed", error->message);
return;
}
g_signal_connect_object (monitor, "changed",
G_CALLBACK (temp_file_changed), self, G_CONNECT_DEFAULT);
launcher = gtk_file_launcher_new (file);
gtk_file_launcher_set_writable (launcher, TRUE);
gtk_file_launcher_launch (launcher, GTK_WINDOW (root), NULL, file_launched, self);
}
static void
edit_path (PathEditor *self)
{
edit_path_externally (self);
}
/* }}} */
/* {{{ GObject boilerplate */
struct _PathEditorClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (PathEditor, path_editor, GTK_TYPE_WIDGET)
static void
path_editor_init (PathEditor *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
static void
path_editor_set_property (GObject *object,
unsigned int prop_id,
const GValue *value,
GParamSpec *pspec)
{
PathEditor *self = PATH_EDITOR (object);
switch (prop_id)
{
case PROP_PATH:
path_editor_set_path (self, g_value_get_boxed (value));
break;
case PROP_WIDTH:
self->width = g_value_get_double (value);
break;
case PROP_HEIGHT:
self->height = g_value_get_double (value);
break;
case PROP_ID:
path_editor_set_id (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
path_editor_get_property (GObject *object,
unsigned int prop_id,
GValue *value,
GParamSpec *pspec)
{
PathEditor *self = PATH_EDITOR (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
break;
case PROP_WIDTH:
g_value_set_double (value, self->width);
break;
case PROP_HEIGHT:
g_value_set_double (value, self->height);
break;
case PROP_ID:
g_value_set_string (value, gtk_label_get_label (self->path_cmds_label));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
path_editor_dispose (GObject *object)
{
PathEditor *self = PATH_EDITOR (object);
gtk_widget_dispose_template (GTK_WIDGET (object), PATH_EDITOR_TYPE);
g_clear_object (&self->paintable);
G_OBJECT_CLASS (path_editor_parent_class)->dispose (object);
}
static void
path_editor_finalize (GObject *object)
{
PathEditor *self = PATH_EDITOR (object);
g_clear_pointer (&self->path, gsk_path_unref);
G_OBJECT_CLASS (path_editor_parent_class)->finalize (object);
}
static void
set_id (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
PathEditor *self = PATH_EDITOR (widget);
const char *id = g_variant_get_string (parameter, NULL);
if (strcmp (id, "") == 0)
{
g_autoptr (GskPath) path = gsk_path_parse ("");
path_editor_set_path (self, path);
}
else
path_editor_set_id (self, id);
gtk_stack_set_visible_child_name (self->path_cmds_stack, "label");
}
static void
path_editor_class_init (PathEditorClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->set_property = path_editor_set_property;
object_class->get_property = path_editor_get_property;
object_class->dispose = path_editor_dispose;
object_class->finalize = path_editor_finalize;
properties[PROP_PATH] =
g_param_spec_boxed ("path", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_WIDTH] =
g_param_spec_double ("width", NULL, NULL,
0, DBL_MAX, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_HEIGHT] =
g_param_spec_double ("height", NULL, NULL,
0, DBL_MAX, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_ID] =
g_param_spec_string ("id", NULL, NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/Shaper/path-editor.ui");
gtk_widget_class_bind_template_child (widget_class, PathEditor, path_cmds_stack);
gtk_widget_class_bind_template_child (widget_class, PathEditor, path_cmds_label);
gtk_widget_class_bind_template_child (widget_class, PathEditor, path_cmds_entry);
gtk_widget_class_bind_template_child (widget_class, PathEditor, external_edit_button);
gtk_widget_class_bind_template_child (widget_class, PathEditor, id_dropdown);
gtk_widget_class_bind_template_callback (widget_class, path_cmds_clicked);
gtk_widget_class_bind_template_callback (widget_class, path_cmds_activated);
gtk_widget_class_bind_template_callback (widget_class, path_cmds_key);
gtk_widget_class_bind_template_callback (widget_class, edit_path);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_install_action (widget_class, "set_id", "s", set_id);
}
/* }}} */
/* {{{ Public API */
PathEditor *
path_editor_new (GskPath *path,
double width,
double height)
{
return g_object_new (PATH_EDITOR_TYPE,
"path", path,
"width", width,
"height", height,
NULL);
}
GskPath *
path_editor_get_path (PathEditor *self)
{
g_return_val_if_fail (PATH_IS_EDITOR (self), NULL);
return self->path;
}
void
path_editor_set_path (PathEditor *self,
GskPath *path)
{
if (self->path == path ||
(self->path && path && gsk_path_equal (self->path, path)))
return;
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
if (gsk_path_is_empty (path))
{
gtk_label_set_label (self->path_cmds_label, "—");
gtk_editable_set_text (GTK_EDITABLE (self->path_cmds_entry), "");
}
else
{
g_autofree char *text = gsk_path_to_string (path);
gtk_label_set_label (self->path_cmds_label, text);
gtk_editable_set_text (GTK_EDITABLE (self->path_cmds_entry), text);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
}
void
path_editor_set_id (PathEditor *self,
const char *id)
{
g_return_if_fail (self->paintable != NULL);
g_clear_pointer (&self->path, gsk_path_unref);
gtk_label_set_label (self->path_cmds_label, id);
gtk_editable_set_text (GTK_EDITABLE (self->path_cmds_entry), id);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
}
const char *
path_editor_get_id (PathEditor *self)
{
if (self->paintable == NULL)
return NULL;
return gtk_editable_get_text (GTK_EDITABLE (self->path_cmds_entry));
}
void
path_editor_set_paintable (PathEditor *self,
PathPaintable *paintable)
{
g_clear_object (&self->paintable);
self->paintable = g_object_ref (paintable);
gtk_widget_set_visible (GTK_WIDGET (self->id_dropdown), TRUE);
}
/* }}} */
/* vim:set foldmethod=marker: */
[Seitenstruktur0.18Drucken]
|
2026-10-11
|