Quelle icon-editor-window.c
Sprache: unbekannt
|
|
/*
* 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 "icon-editor-window.h"
#include "paintable-editor.h"
#include "path-paintable.h"
#include "border-paintable.h"
#include "state-editor.h"
#include "gtk/svg/gtksvgelementprivate.h"
#include "gtk/svg/gtksvgpaintprivate.h"
#include "gtk/svg/gtksvgserializeprivate.h"
#include <glib/gstdio.h>
struct _IconEditorWindow
{
GtkApplicationWindow parent;
GFile *file;
PathPaintable *paintable;
PathPaintable *orig_paintable;
GBinding *playing_binding;
gboolean changed;
gboolean show_sidebar;
gboolean show_thumbnails;
gboolean show_bounds;
gboolean show_spines;
gboolean show_grid;
gboolean invert_colors;
gboolean playing;
gboolean compat_classes;
float weight;
unsigned int state;
GtkStack *main_stack;
GtkImage *empty_logo;
union {
GtkImage *images[24];
struct {
GtkImage *image48_0, *image48_1, *image48_2, *image48_3;
GtkImage *image48_4, *image48_5, *image48_6, *image48_7;
GtkImage *image24_0, *image24_1, *image24_2, *image24_3;
GtkImage *image24_4, *image24_5, *image24_6, *image24_7;
GtkImage *image24_8, *image24_9, *image24_10, *image24_11;
GtkImage *image24_12, *image24_13, *image24_14, *image24_15;
};
};
union {
GtkImage *examples[6];
struct {
GtkImage *example1, *example2, *example3;
GtkImage *example4, *example5, *example6;
};
};
PaintableEditor *paintable_editor;
GtkCssProvider *paintable_style;
};
struct _IconEditorWindowClass
{
GtkApplicationWindowClass parent_class;
};
enum
{
PROP_PAINTABLE = 1,
PROP_CHANGED,
PROP_SHOW_SIDEBAR,
PROP_SHOW_BOUNDS,
PROP_SHOW_SPINES,
PROP_SHOW_GRID,
PROP_INVERT_COLORS,
PROP_WEIGHT,
PROP_STATE,
PROP_PLAYING,
PROP_COMPAT_CLASSES,
NUM_PROPERTIES,
};
static GParamSpec *properties[NUM_PROPERTIES];
G_DEFINE_TYPE(IconEditorWindow, icon_editor_window, GTK_TYPE_APPLICATION_WINDOW);
/* {{{ Setters */
static void
icon_editor_window_set_show_sidebar (IconEditorWindow *self,
gboolean show_sidebar)
{
if (self->show_sidebar == show_sidebar)
return;
if (show_sidebar)
{
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (self), "close");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
gtk_stack_set_visible_child_name (self->main_stack, "content");
}
self->show_sidebar = show_sidebar;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SIDEBAR]);
}
static void
icon_editor_window_set_show_bounds (IconEditorWindow *self,
gboolean show_bounds)
{
if (self->show_bounds == show_bounds)
return;
self->show_bounds = show_bounds;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_BOUNDS]);
}
static void
icon_editor_window_set_show_spines (IconEditorWindow *self,
gboolean show_spines)
{
if (self->show_spines == show_spines)
return;
self->show_spines = show_spines;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SPINES]);
}
static void
icon_editor_window_set_show_grid (IconEditorWindow *self,
gboolean show_grid)
{
if (self->show_grid == show_grid)
return;
self->show_grid = show_grid;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_GRID]);
}
static void
icon_editor_window_set_invert_colors (IconEditorWindow *self,
gboolean invert_colors)
{
GtkSettings *settings;
if (self->invert_colors == invert_colors)
return;
self->invert_colors = invert_colors;
settings = gtk_widget_get_settings (GTK_WIDGET (self));
if (invert_colors)
{
GtkInterfaceColorScheme color_scheme;
g_object_get (settings, "gtk-interface-color-scheme", &color_scheme, NULL);
if (color_scheme == GTK_INTERFACE_COLOR_SCHEME_DARK)
color_scheme = GTK_INTERFACE_COLOR_SCHEME_LIGHT;
else
color_scheme = GTK_INTERFACE_COLOR_SCHEME_DARK;
g_object_set (settings, "gtk-interface-color-scheme", color_scheme, NULL);
}
else
gtk_settings_reset_property (settings, "gtk-interface-color-scheme");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INVERT_COLORS]);
}
static void
update_icon_paintable_style (IconEditorWindow *self,
float weight)
{
char *css;
css = g_strdup_printf ("image.icon-paintable-preview { -gtk-icon-weight: %f; }", weight);
if (!self->paintable_style)
{
self->paintable_style = gtk_css_provider_new ();
gtk_style_context_add_provider_for_display (gtk_widget_get_display (GTK_WIDGET (self)),
GTK_STYLE_PROVIDER (self->paintable_style),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
gtk_css_provider_load_from_string (self->paintable_style, css);
g_free (css);
}
static void
icon_editor_window_set_weight (IconEditorWindow *self,
float weight)
{
if (self->weight == weight)
return;
self->weight = weight;
path_paintable_set_weight (self->paintable, weight);
update_icon_paintable_style (self, weight);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WEIGHT]);
}
static void
icon_editor_window_set_state (IconEditorWindow *self,
unsigned int state)
{
if (self->state == state)
return;
self->state = state;
path_paintable_set_state (self->paintable, state);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]);
}
static void
icon_editor_window_set_changed (IconEditorWindow *self,
gboolean changed)
{
GAction *action;
if (self->changed == changed)
return;
self->changed = changed;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHANGED]);
action = g_action_map_lookup_action (G_ACTION_MAP (self), "save");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), changed);
action = g_action_map_lookup_action (G_ACTION_MAP (self), "revert");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), changed);
}
static void
icon_editor_window_set_playing (IconEditorWindow *self,
gboolean playing)
{
if (self->playing == playing)
return;
self->playing = playing;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLAYING]);
}
static void
icon_editor_window_set_compat_classes (IconEditorWindow *self,
gboolean compat_classes)
{
if (self->compat_classes == compat_classes)
return;
self->compat_classes = compat_classes;
icon_editor_window_set_changed (self, TRUE);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COMPAT_CLASSES]);
}
/* }}} */
/* {{{ Callbacks, utilities */
static void
toggle_sidebar (IconEditorWindow *self)
{
icon_editor_window_set_show_sidebar (self, !self->show_sidebar);
}
static void
paintable_changed (IconEditorWindow *self)
{
gboolean changed;
changed = !gtk_svg_equal (path_paintable_get_svg (self->paintable),
path_paintable_get_svg (self->orig_paintable));
icon_editor_window_set_changed (self, changed);
}
static void
show_error (IconEditorWindow *self,
const char *title,
const char *detail)
{
g_autoptr (GtkAlertDialog) alert = NULL;
alert = gtk_alert_dialog_new ("%s", title);
gtk_alert_dialog_set_detail (alert, detail);
gtk_alert_dialog_show (alert, GTK_WINDOW (self));
}
static gboolean
is_regular_file (GFile *file)
{
const char *uri;
if (file == NULL)
return FALSE;
uri = g_file_get_uri (file);
if (uri != NULL && g_str_has_prefix (uri, "resource://"))
return FALSE;
return TRUE;
}
/* }}} */
/* {{{ Opening/Importing */
static void
load_error (IconEditorWindow *self,
const char *message)
{
show_error (self, "Loading failed", message);
}
static void
set_random_icons (IconEditorWindow *self)
{
const char *names[] = {
"bookmark-new-symbolic",
"color-select-symbolic",
"document-open-recent-symbolic",
"document-open-symbolic",
"document-save-as-symbolic",
"document-save-symbolic",
"edit-clear-all-symbolic",
"edit-clear-symbolic-rtl",
"edit-clear-symbolic",
"edit-copy-symbolic",
"edit-cut-symbolic",
"edit-delete-symbolic",
"edit-find-symbolic",
"edit-paste-symbolic",
"edit-select-all-symbolic",
"find-location-symbolic",
"folder-new-symbolic",
"function-linear-symbolic",
"go-down-symbolic",
"go-next-symbolic-rtl",
"go-next-symbolic",
"go-previous-symbolic-rtl",
"go-previous-symbolic",
"go-up-symbolic",
"info-outline-symbolic",
"insert-image-symbolic",
"insert-object-symbolic",
"list-add-symbolic",
"list-remove-all-symbolic",
"list-remove-symbolic",
"media-eject-symbolic",
"media-playback-pause-symbolic",
"media-playback-start-symbolic",
"media-playback-stop-symbolic",
"media-record-symbolic",
"object-select-symbolic",
"open-menu-symbolic",
"pan-down-symbolic",
"pan-end-symbolic-rtl",
"pan-end-symbolic",
"pan-start-symbolic-rtl",
"pan-start-symbolic",
"pan-up-symbolic",
"system-run-symbolic",
"system-search-symbolic",
"value-decrease-symbolic",
"value-increase-symbolic",
"view-conceal-symbolic",
"view-grid-symbolic",
"view-list-symbolic",
"view-more-symbolic",
"view-refresh-symbolic",
"view-reveal-symbolic",
"window-close-symbolic",
"window-maximize-symbolic",
"window-minimize-symbolic",
"window-restore-symbolic",
"zoom-in-symbolic",
"zoom-original-symbolic",
"zoom-out-symbolic",
"emoji-activities-symbolic",
"emoji-body-symbolic",
"emoji-flags-symbolic",
"emoji-food-symbolic",
"emoji-nature-symbolic",
"emoji-objects-symbolic",
"emoji-people-symbolic",
"emoji-recent-symbolic",
"emoji-symbols-symbolic",
"emoji-travel-symbolic",
"audio-volume-high-symbolic",
"audio-volume-low-symbolic",
"audio-volume-medium-symbolic",
"audio-volume-muted-symbolic",
"caps-lock-symbolic",
"changes-allow-symbolic",
"changes-prevent-symbolic",
"dialog-error-symbolic",
"dialog-information-symbolic",
"dialog-password-symbolic",
"dialog-question-symbolic",
"dialog-warning-symbolic",
"display-brightness-symbolic",
"media-playlist-repeat-symbolic",
"orientation-landscape-inverse-symbolic",
"orientation-landscape-symbolic",
"orientation-portrait-inverse-symbolic",
"orientation-portrait-symbolic",
"process-working-symbolic",
"switch-off-symbolic",
"switch-on-symbolic",
};
g_autoptr (GtkBitset) used = gtk_bitset_new_empty ();
GtkImage *image;
for (unsigned int i = 0; i < 24; i++)
{
uint32_t r;
g_autofree char *path;
g_autoptr (GtkSvg) paintable = NULL;
do {
r = g_random_int_range (0, (uint32_t) G_N_ELEMENTS (names));
} while (gtk_bitset_contains (used, r));
path = g_strconcat ("/org/gtk/libgtk/icons/", names[r], ".svg", NULL);
paintable = gtk_svg_new_from_resource (path);
gtk_svg_set_state (paintable, 0);
gtk_svg_play (paintable);
g_object_bind_property (self, "weight",
paintable, "weight",
G_BINDING_SYNC_CREATE);
gtk_image_set_from_paintable (self->images[i], GDK_PAINTABLE (paintable));
gtk_widget_set_tooltip_text (GTK_WIDGET (self->images[i]), names[r]);
gtk_bitset_add (used, r);
}
image = self->images[g_random_int_range (0, 3)];
gtk_image_set_from_paintable (image, GDK_PAINTABLE (self->paintable));
gtk_widget_set_tooltip_text (GTK_WIDGET (image), NULL);
image = self->images[g_random_int_range (4, 7)];
gtk_image_set_from_paintable (image, GDK_PAINTABLE (self->paintable));
gtk_widget_set_tooltip_text (GTK_WIDGET (image), NULL);
image = self->images[g_random_int_range (8, 15)];
gtk_image_set_from_paintable (image, GDK_PAINTABLE (self->paintable));
gtk_widget_set_tooltip_text (GTK_WIDGET (image), NULL);
image = self->images[g_random_int_range (16, 23)];
gtk_image_set_from_paintable (image, GDK_PAINTABLE (self->paintable));
gtk_widget_set_tooltip_text (GTK_WIDGET (image), NULL);
}
static void
icon_editor_window_set_paintable (IconEditorWindow *self,
PathPaintable *paintable)
{
if (self->paintable == paintable)
return;
if (self->paintable)
{
g_signal_handlers_disconnect_by_func (self->paintable, paintable_changed, self);
g_clear_object (&self->playing_binding);
}
g_set_object (&self->paintable, paintable);
if (self->paintable)
{
path_paintable_set_frame_clock (self->paintable, gtk_widget_get_frame_clock (GTK_WIDGET (self)));
icon_editor_window_set_state (self, path_paintable_get_state (paintable));
path_paintable_set_weight (self->paintable, self->weight);
g_signal_connect_swapped (self->paintable, "changed",
G_CALLBACK (paintable_changed), self);
self->playing_binding = g_object_bind_property (self, "playing",
self->paintable, "playing",
G_BINDING_SYNC_CREATE);
set_random_icons (self);
if (svg_element_get_n_children (path_paintable_get_svg (self->paintable)->content) > 0)
icon_editor_window_set_show_sidebar (self, TRUE);
}
g_clear_object (&self->orig_paintable);
if (paintable)
self->orig_paintable = path_paintable_copy (paintable);
icon_editor_window_set_changed (self, FALSE);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PAINTABLE]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHANGED]);
}
static gboolean
load_bytes (IconEditorWindow *self,
GBytes *bytes)
{
g_autoptr (GError) error = NULL;
g_autoptr (PathPaintable) paintable = NULL;
if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
{
load_error (self, "Invalid UTF-8");
return FALSE;
}
paintable = path_paintable_new_from_bytes (bytes, &error);
if (!paintable)
{
load_error (self, error->message);
return FALSE;
}
icon_editor_window_set_paintable (self, paintable);
return TRUE;
}
static gboolean
load_file_contents (IconEditorWindow *self,
GFile *file)
{
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
bytes = g_file_load_bytes (file, NULL, NULL, &error);
if (!bytes)
{
load_error (self, error->message);
return FALSE;
}
return load_bytes (self, bytes);
}
static gboolean
file_drop (GtkDropTarget *target,
const GValue *value,
double x,
double y,
gpointer user_data)
{
if (G_VALUE_HOLDS (value, G_TYPE_FILE))
{
GtkWidget *self = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (target));
GFile *file = g_value_get_object (value);
icon_editor_window_load (ICON_EDITOR_WINDOW (self), file);
return TRUE;
}
return FALSE;
}
static void
open_response_cb (GObject *source,
GAsyncResult *result,
void *user_data)
{
GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
IconEditorWindow *self = user_data;
g_autoptr (GFile) file = NULL;
g_autoptr (GError) error = NULL;
file = gtk_file_dialog_open_finish (dialog, result, &error);
if (!file)
{
if (!g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED))
load_error (self, error->message);
return;
}
icon_editor_window_load (self, file);
}
static void
show_open_filechooser (IconEditorWindow *self)
{
g_autoptr (GtkFileDialog) dialog = NULL;
g_autoptr (GListStore) filters = NULL;
GtkFileFilter *filter;
filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, "All files");
gtk_file_filter_add_pattern (filter, "*");
g_list_store_append (filters, filter);
g_object_unref (filter);
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, "SVG files");
gtk_file_filter_add_mime_type (filter, "image/svg+xml");
g_list_store_append (filters, filter);
g_object_unref (filter);
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, "GTK icons");
gtk_file_filter_add_mime_type (filter, "image/x-gtk-path-animation");
gtk_file_filter_add_pattern (filter, "*.gpa");
g_list_store_append (filters, filter);
g_object_unref (filter);
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (dialog, "Open icon file");
gtk_file_dialog_set_filters (dialog, G_LIST_MODEL (filters));
if (is_regular_file (self->file))
{
gtk_file_dialog_set_initial_file (dialog, self->file);
}
else
{
g_autoptr (GFile) cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
}
gtk_file_dialog_open (dialog, GTK_WINDOW (self),
NULL, open_response_cb, self);
}
static gboolean
icon_editor_window_load_file (IconEditorWindow *self,
GFile *file)
{
g_autofree char *basename = NULL;
if (!load_file_contents (self, file))
return FALSE;
g_set_object (&self->file, file);
basename = g_file_get_basename (file);
gtk_window_set_title (GTK_WINDOW (self), basename);
return TRUE;
}
/* }}} */
/* {{{ Saving/Exporting */
static void
set_compat (SvgElement *shape,
gpointer data)
{
IconEditorWindow *self = data;
if (!svg_element_type_is_graphical (svg_element_get_type (shape)))
return;
svg_element_parse_classes (shape, NULL);
if (self->compat_classes)
{
SvgValue *value;
GStrvBuilder *builder;
builder = g_strv_builder_new ();
value = ref_value (shape, SVG_PROPERTY_FILL);
switch ((unsigned int) svg_paint_get_kind (value))
{
case PAINT_SYMBOLIC:
switch (svg_paint_get_symbolic (value))
{
case GTK_SYMBOLIC_COLOR_FOREGROUND:
g_strv_builder_add_many (builder, "foreground", "foreground-fill", NULL);
break;
case GTK_SYMBOLIC_COLOR_ERROR:
g_strv_builder_add_many (builder, "error", "error-fill", NULL);
break;
case GTK_SYMBOLIC_COLOR_WARNING:
g_strv_builder_add_many (builder, "warning", "warning-fill", NULL);
break;
case GTK_SYMBOLIC_COLOR_SUCCESS:
g_strv_builder_add_many (builder, "success", "success-fill", NULL);
break;
case GTK_SYMBOLIC_COLOR_ACCENT:
default:
break;
}
break;
case PAINT_NONE:
g_strv_builder_add (builder, "transparent-fill");
break;
default:
break;
}
svg_value_unref (value);
value = ref_value (shape, SVG_PROPERTY_STROKE);
switch ((unsigned int) svg_paint_get_kind (value))
{
case PAINT_SYMBOLIC:
switch (svg_paint_get_symbolic (value))
{
case GTK_SYMBOLIC_COLOR_FOREGROUND:
g_strv_builder_add (builder, "foreground-stroke");
break;
case GTK_SYMBOLIC_COLOR_ERROR:
g_strv_builder_add (builder, "error-stroke");
break;
case GTK_SYMBOLIC_COLOR_WARNING:
g_strv_builder_add (builder, "warning-stroke");
break;
case GTK_SYMBOLIC_COLOR_SUCCESS:
g_strv_builder_add (builder, "success-stroke");
break;
case GTK_SYMBOLIC_COLOR_ACCENT:
default:
break;
}
break;
default:
break;
}
svg_value_unref (value);
svg_element_take_classes (shape, g_strv_builder_end (builder));
}
}
static void
apply_compat_classes (IconEditorWindow *window,
GtkSvg *svg)
{
svg_element_foreach (svg->content, set_compat, window);
}
static void
save_error (IconEditorWindow *self,
const char *message)
{
show_error (self, "Saving failed", message);
}
static void
save_to_file (IconEditorWindow *self,
GFile *file)
{
GtkSvg *svg;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
svg = path_paintable_get_svg (self->paintable);
apply_compat_classes (self, svg);
bytes = gtk_svg_serialize (svg);
if (!g_file_replace_contents (file,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL, FALSE,
G_FILE_CREATE_NONE,
NULL,
NULL,
&error))
{
save_error (self, error->message);
return;
}
g_set_object (&self->file, file);
g_set_object (&self->orig_paintable, path_paintable_copy (self->paintable));
icon_editor_window_set_changed (self, FALSE);
}
typedef void (* FollowupCallback) (IconEditorWindow *self,
gpointer data);
typedef struct
{
FollowupCallback callback;
gpointer data;
GDestroyNotify destroy;
} FollowupData;
static void
followup_data_free (gpointer data)
{
FollowupData *fd = data;
if (fd->destroy)
fd->destroy (fd->data);
g_free (data);
}
static void
save_response_cb (GObject *source,
GAsyncResult *result,
void *user_data)
{
GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
IconEditorWindow *self = user_data;
g_autoptr (GFile) file = NULL;
g_autoptr (GError) error = NULL;
FollowupData *data = g_object_get_data (G_OBJECT (dialog), "followup");
file = gtk_file_dialog_save_finish (dialog, result, &error);
if (!file)
{
if (!g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED))
save_error (self, error->message);
return;
}
save_to_file (self, file);
if (data)
data->callback (self, data->data);
}
static void
show_save_filechooser_with_followup (IconEditorWindow *self,
FollowupData *data)
{
g_autoptr (GtkFileDialog) dialog = NULL;
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (dialog, "Save icon");
if (is_regular_file (self->file))
{
gtk_file_dialog_set_initial_file (dialog, self->file);
}
else
{
g_autoptr (GFile) cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
gtk_file_dialog_set_initial_name (dialog, "demo.gpa");
}
if (data)
g_object_set_data_full (G_OBJECT (dialog), "followup", data, followup_data_free);
gtk_file_dialog_save (dialog,
GTK_WINDOW (self),
NULL,
save_response_cb, self);
}
static void
show_save_filechooser (IconEditorWindow *self)
{
show_save_filechooser_with_followup (self, NULL);
}
static void
export_to_file (IconEditorWindow *self,
GFile *file)
{
GtkSvg *svg;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
svg = path_paintable_get_svg (self->paintable);
apply_compat_classes (self, svg);
bytes = gtk_svg_serialize_full (svg,
NULL, 0,
GTK_SVG_SERIALIZE_EXPAND_GPA_ATTRS |
GTK_SVG_SERIALIZE_NO_COMPAT);
if (!g_file_replace_contents (file,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL, FALSE,
G_FILE_CREATE_NONE,
NULL,
NULL,
&error))
{
save_error (self, error->message);
return;
}
}
static void
export_response_cb (GObject *source,
GAsyncResult *result,
void *user_data)
{
GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
IconEditorWindow *self = user_data;
g_autoptr (GFile) file = NULL;
g_autoptr (GError) error = NULL;
file = gtk_file_dialog_save_finish (dialog, result, &error);
if (!file)
{
if (!g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED))
save_error (self, error->message);
return;
}
export_to_file (self, file);
}
static void
show_export_filechooser (IconEditorWindow *self)
{
g_autoptr (GtkFileDialog) dialog = NULL;
g_autoptr (GFile) cwd = g_file_new_for_path (".");
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (dialog, "Export as SVG");
gtk_file_dialog_set_initial_folder (dialog, cwd);
gtk_file_dialog_set_initial_name (dialog, "demo.svg");
gtk_file_dialog_save (dialog,
GTK_WINDOW (self),
NULL,
export_response_cb, self);
}
/* }}} */
/* {{{ GtkWindow implementation */
static void
quit_alert_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkAlertDialog *alert = GTK_ALERT_DIALOG (source);
IconEditorWindow *self = ICON_EDITOR_WINDOW (data);
g_autoptr (GError) error = NULL;
int res;
res = gtk_alert_dialog_choose_finish (alert, result, &error);
if (res == -1)
return;
if (res == 0)
{
/* Don't try to save resource files */
if (is_regular_file (self->file))
save_to_file (self, self->file);
else
show_save_filechooser (self);
}
else if (res == 1)
{
self->changed = FALSE;
gtk_window_close (GTK_WINDOW (self));
}
}
static gboolean
icon_editor_window_close_request (GtkWindow *window)
{
IconEditorWindow *self = ICON_EDITOR_WINDOW (window);
if (self->changed)
{
g_autoptr (GtkAlertDialog) alert = NULL;
const char *buttons[] = { "Save", "Quit", NULL };
alert = gtk_alert_dialog_new ("Unsaved changes");
gtk_alert_dialog_set_detail (alert, "The icon contains unsaved changes.");
gtk_alert_dialog_set_modal (alert, TRUE);
gtk_alert_dialog_set_buttons (alert, buttons);
gtk_alert_dialog_set_default_button (alert, 0);
gtk_alert_dialog_set_cancel_button (alert, 0);
gtk_alert_dialog_choose (alert, window, NULL, quit_alert_done, self);
return TRUE;
}
return FALSE;
}
/* }}} */
/* {{{ Actions */
static void
file_open (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
show_open_filechooser (self);
}
static void
file_save (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
if (is_regular_file (self->file))
save_to_file (self, self->file);
else
show_save_filechooser (self);
}
static void
file_save_as (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
show_save_filechooser (self);
}
static void
file_export (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
show_export_filechooser (self);
}
static void
back_to_empty (IconEditorWindow *self)
{
g_autoptr (PathPaintable) paintable = path_paintable_new ();
GAction *action;
icon_editor_window_set_paintable (self, paintable);
icon_editor_window_set_show_sidebar (self, FALSE);
gtk_stack_set_visible_child_name (self->main_stack, "empty");
action = g_action_map_lookup_action (G_ACTION_MAP (self), "close");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
}
static void
close_alert_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkAlertDialog *alert = GTK_ALERT_DIALOG (source);
IconEditorWindow *self = ICON_EDITOR_WINDOW (data);
g_autoptr (GError) error = NULL;
int res;
res = gtk_alert_dialog_choose_finish (alert, result, &error);
if (res == -1)
return;
if (res == 0)
{
if (is_regular_file (self->file))
save_to_file (self, self->file);
else
show_save_filechooser (self);
}
else if (res == 1)
{
back_to_empty (self);
}
}
static void
file_close (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
if (self->changed)
{
g_autoptr (GtkAlertDialog) alert = NULL;
const char *buttons[] = { "Save", "Close", NULL };
alert = gtk_alert_dialog_new ("Unsaved changes");
gtk_alert_dialog_set_detail (alert, "The icon contains unsaved changes.");
gtk_alert_dialog_set_modal (alert, TRUE);
gtk_alert_dialog_set_buttons (alert, buttons);
gtk_alert_dialog_set_default_button (alert, 0);
gtk_alert_dialog_choose (alert, GTK_WINDOW (self), NULL, close_alert_done, self);
}
else
{
back_to_empty (self);
}
}
static void
revert_changes (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
if (!self->paintable)
return;
path_paintable_set_state (self->orig_paintable, path_paintable_get_state (self->paintable));
icon_editor_window_set_paintable (self, self->orig_paintable);
}
static void
add_element (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
paintable_editor_add_element (self->paintable_editor);
}
static void
edit_states (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
StateEditor *editor;
editor = state_editor_new ();
gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (self));
state_editor_set_paintable (editor, self->paintable);
gtk_window_present (GTK_WINDOW (editor));
}
static void
show_sidebar (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
icon_editor_window_set_show_sidebar (self, TRUE);
}
static void
open_example (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
g_autofree char *uri = NULL;
g_autoptr (GFile) file = NULL;
uri = g_strconcat ("resource:///org/gtk/Shaper/",
g_variant_get_string (parameter, NULL),
NULL);
file = g_file_new_for_uri (uri);
icon_editor_window_load (self, file);
}
static void
reshuffle (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
set_random_icons (self);
}
static void
set_sidebar_contents (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
IconEditorWindow *self = user_data;
paintable_editor_set_show_xml (self->paintable_editor, strcmp (g_variant_get_string (parameter, NULL), "xml") == 0);
g_simple_action_set_state (action, parameter);
}
static GActionEntry win_entries[] = {
{ "open", file_open, NULL, NULL, NULL },
{ "save", file_save, NULL, NULL, NULL },
{ "save-as", file_save_as, NULL, NULL, NULL },
{ "export", file_export, NULL, NULL, NULL },
{ "revert", revert_changes, NULL, NULL, NULL },
{ "close", file_close, NULL, NULL, NULL },
{ "add-element", add_element, NULL, NULL, NULL },
{ "edit-states", edit_states, NULL, NULL, NULL },
{ "show-sidebar", show_sidebar, NULL, NULL, NULL },
{ "open-example", open_example, "s", NULL, NULL },
{ "reshuffle", reshuffle, NULL, NULL, NULL },
{ "set-sidebar-contents", NULL, "s", "'controls'", set_sidebar_contents },
};
/* }}} */
/* {{{ GObject boilerplate */
static void
icon_editor_window_init (IconEditorWindow *self)
{
g_autoptr (GPropertyAction) action = NULL;
g_autoptr (PathPaintable) paintable = path_paintable_new ();
self->weight = 400;
self->state = 0;
self->playing = TRUE;
self->compat_classes = TRUE;
gtk_widget_init_template (GTK_WIDGET (self));
g_action_map_add_action_entries (G_ACTION_MAP (self),
win_entries, G_N_ELEMENTS (win_entries),
self);
g_set_object (&action, g_property_action_new ("invert-colors", self, "invert-colors"));
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
g_set_object (&action, g_property_action_new ("show-bounds", self, "show-bounds"));
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
g_set_object (&action, g_property_action_new ("show-spines", self, "show-spines"));
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
g_set_object (&action, g_property_action_new ("show-grid", self, "show-grid"));
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
g_set_object (&action, g_property_action_new ("set-playing", self, "playing"));
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (self), "save")), FALSE);
g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (self), "revert")), FALSE);
g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (self), "close")), FALSE);
icon_editor_window_set_paintable (self, paintable);
}
static void
icon_editor_window_set_property (GObject *object,
unsigned int prop_id,
const GValue *value,
GParamSpec *pspec)
{
IconEditorWindow *self = ICON_EDITOR_WINDOW (object);
switch (prop_id)
{
case PROP_CHANGED:
icon_editor_window_set_changed (self, g_value_get_boolean (value));
break;
case PROP_PAINTABLE:
icon_editor_window_set_paintable (self, g_value_get_object (value));
break;
case PROP_SHOW_SIDEBAR:
icon_editor_window_set_show_sidebar (self, g_value_get_boolean (value));
break;
case PROP_SHOW_BOUNDS:
icon_editor_window_set_show_bounds (self, g_value_get_boolean (value));
break;
case PROP_SHOW_SPINES:
icon_editor_window_set_show_spines (self, g_value_get_boolean (value));
break;
case PROP_SHOW_GRID:
icon_editor_window_set_show_grid (self, g_value_get_boolean (value));
break;
case PROP_INVERT_COLORS:
icon_editor_window_set_invert_colors (self, g_value_get_boolean (value));
break;
case PROP_WEIGHT:
icon_editor_window_set_weight (self, g_value_get_float (value));
break;
case PROP_STATE:
icon_editor_window_set_state (self, g_value_get_uint (value));
break;
case PROP_PLAYING:
icon_editor_window_set_playing (self, g_value_get_boolean (value));
break;
case PROP_COMPAT_CLASSES:
icon_editor_window_set_compat_classes (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
icon_editor_window_get_property (GObject *object,
unsigned int prop_id,
GValue *value,
GParamSpec *pspec)
{
IconEditorWindow *self = ICON_EDITOR_WINDOW (object);
switch (prop_id)
{
case PROP_PAINTABLE:
g_value_set_object (value, self->paintable);
break;
case PROP_CHANGED:
g_value_set_boolean (value, self->changed);
break;
case PROP_SHOW_SIDEBAR:
g_value_set_boolean (value, self->show_sidebar);
break;
case PROP_SHOW_BOUNDS:
g_value_set_boolean (value, self->show_bounds);
break;
case PROP_SHOW_SPINES:
g_value_set_boolean (value, self->show_spines);
break;
case PROP_SHOW_GRID:
g_value_set_boolean (value, self->show_grid);
break;
case PROP_INVERT_COLORS:
g_value_set_boolean (value, self->invert_colors);
break;
case PROP_WEIGHT:
g_value_set_float (value, self->weight);
break;
case PROP_STATE:
g_value_set_uint (value, self->state);
break;
case PROP_PLAYING:
g_value_set_boolean (value, self->playing);
break;
case PROP_COMPAT_CLASSES:
g_value_set_boolean (value, self->compat_classes);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
icon_editor_window_dispose (GObject *object)
{
gtk_widget_dispose_template (GTK_WIDGET (object), ICON_EDITOR_WINDOW_TYPE);
G_OBJECT_CLASS (icon_editor_window_parent_class)->dispose (object);
}
static void
icon_editor_window_finalize (GObject *object)
{
IconEditorWindow *self = ICON_EDITOR_WINDOW (object);
if (self->paintable)
g_signal_handlers_disconnect_by_func (self->paintable, paintable_changed, self);
g_clear_object (&self->paintable);
g_clear_object (&self->orig_paintable);
g_clear_object (&self->file);
g_clear_object (&self->paintable_style);
G_OBJECT_CLASS (icon_editor_window_parent_class)->finalize (object);
}
static void
icon_editor_window_realize (GtkWidget *widget)
{
IconEditorWindow *self = ICON_EDITOR_WINDOW (widget);
GtkApplication *app = gtk_window_get_application (GTK_WINDOW (self));
g_autofree char *path = NULL;
g_autoptr (GFile) file = NULL;
g_autoptr (GtkIconPaintable) logo = NULL;
GdkFrameClock *clock;
GTK_WIDGET_CLASS (icon_editor_window_parent_class)->realize (widget);
path = g_strconcat ("resource:///org/gtk/Shaper/",
g_application_get_application_id (G_APPLICATION (app)),
".svg",
NULL);
file = g_file_new_for_uri (path);
logo = gtk_icon_paintable_new_for_file (file, 128, 1);
gtk_image_set_from_paintable (self->empty_logo, GDK_PAINTABLE (logo));
clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
for (unsigned int i = 0; i < 6; i++)
gtk_svg_set_frame_clock (GTK_SVG (gtk_image_get_paintable (GTK_IMAGE (self->examples[i]))), clock);
if (self->paintable)
path_paintable_set_frame_clock (self->paintable, clock);
}
static void
icon_editor_window_class_init (IconEditorWindowClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GtkWindowClass *window_class = GTK_WINDOW_CLASS (class);
g_type_ensure (PAINTABLE_EDITOR_TYPE);
g_type_ensure (BORDER_PAINTABLE_TYPE);
object_class->set_property = icon_editor_window_set_property;
object_class->get_property = icon_editor_window_get_property;
object_class->dispose = icon_editor_window_dispose;
object_class->finalize = icon_editor_window_finalize;
widget_class->realize = icon_editor_window_realize;
window_class->close_request = icon_editor_window_close_request;
properties[PROP_PAINTABLE] =
g_param_spec_object ("paintable", NULL, NULL,
PATH_PAINTABLE_TYPE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_CHANGED] =
g_param_spec_boolean ("changed", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_SHOW_SIDEBAR] =
g_param_spec_boolean ("show-sidebar", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_SHOW_BOUNDS] =
g_param_spec_boolean ("show-bounds", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_SHOW_SPINES] =
g_param_spec_boolean ("show-spines", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_SHOW_GRID] =
g_param_spec_boolean ("show-grid", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_INVERT_COLORS] =
g_param_spec_boolean ("invert-colors", NULL, NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_WEIGHT] =
g_param_spec_float ("weight", NULL, NULL,
1.f, 1000.f, 400.f,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_STATE] =
g_param_spec_uint ("state", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_PLAYING] =
g_param_spec_boolean ("playing", NULL, NULL,
TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
properties[PROP_COMPAT_CLASSES] =
g_param_spec_boolean ("compat-classes", NULL, NULL,
TRUE,
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/icon-editor-window.ui");
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, main_stack);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, empty_logo);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_0);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_1);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_2);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_3);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_4);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_5);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_6);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image48_7);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_0);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_1);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_2);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_3);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_4);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_5);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_6);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_7);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_8);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_9);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_10);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_11);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_12);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_13);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_14);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, image24_15);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, paintable_editor);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example1);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example2);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example3);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example4);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example5);
gtk_widget_class_bind_template_child (widget_class, IconEditorWindow, example6);
gtk_widget_class_bind_template_callback (widget_class, show_open_filechooser);
gtk_widget_class_bind_template_callback (widget_class, toggle_sidebar);
gtk_widget_class_bind_template_callback (widget_class, file_drop);
gtk_widget_class_bind_template_callback (widget_class, gtk_widget_activate);
}
/* }}} */
/* {{{ Public API */
IconEditorWindow *
icon_editor_window_new (IconEditorApplication *application)
{
return g_object_new (ICON_EDITOR_WINDOW_TYPE,
"application", application,
NULL);
}
static void
open_alert_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkAlertDialog *alert = GTK_ALERT_DIALOG (source);
IconEditorWindow *self = ICON_EDITOR_WINDOW (data);
g_autoptr (GError) error = NULL;
int res;
GFile *file = G_FILE (g_object_get_data (G_OBJECT (alert), "file"));
res = gtk_alert_dialog_choose_finish (alert, result, &error);
if (res == -1)
return;
if (res == 0)
{
/* Don't try to save resource files */
if (is_regular_file (self->file))
{
save_to_file (self, self->file);
icon_editor_window_load_file (self, file);
}
else
{
FollowupData *fd = g_new (FollowupData, 1);
fd->callback = (FollowupCallback) icon_editor_window_load_file;
fd->data = g_object_ref (file);
fd->destroy = g_object_unref;
show_save_filechooser_with_followup (self, fd);
}
}
else if (res == 1)
{
icon_editor_window_load_file (self, file);
}
}
gboolean
icon_editor_window_load (IconEditorWindow *self,
GFile *file)
{
if (self->changed)
{
g_autoptr (GtkAlertDialog) alert = NULL;
const char *buttons[] = { "Save", "Continue", NULL };
alert = gtk_alert_dialog_new ("Unsaved changes");
g_object_set_data_full (G_OBJECT (alert), "file", g_object_ref (file), g_object_unref);
gtk_alert_dialog_set_detail (alert, "The icon contains unsaved changes.");
gtk_alert_dialog_set_modal (alert, TRUE);
gtk_alert_dialog_set_buttons (alert, buttons);
gtk_alert_dialog_set_default_button (alert, 0);
gtk_alert_dialog_set_cancel_button (alert, 0);
gtk_alert_dialog_choose (alert, GTK_WINDOW (self), NULL, open_alert_done, self);
return TRUE;
}
return icon_editor_window_load_file (self, file);
}
/* }}} */
/* vim:set foldmethod=marker: */
| Messung V0.5 in Prozent |
|---|
| | | |
[zur Elbe Produktseite wechseln0.20QuellennavigatorsAnalyse erneut starten2026-07-02]
|
2026-07-09
|