/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "dialogs-types.h" #include "config/pikacoreconfig.h" #include "widgets/pikawidgets-utils.h" #include "about.h" #include "git-version.h" #include "about-dialog.h" #include "pika-update.h" #include "pika-version.h" #include "pika-intl.h" typedef struct { GtkWidget *dialog; Pika *pika; GtkWidget *update_frame; PikaCoreConfig *config; GtkWidget *anim_area; PangoLayout *layout; guint timer; gint index; gint animstep; gint state; gboolean visible; } PikaAboutDialog; static void about_dialog_map (GtkWidget *widget, PikaAboutDialog *dialog); static void about_dialog_unmap (GtkWidget *widget, PikaAboutDialog *dialog); static GdkPixbuf * about_dialog_load_logo (void); static void about_dialog_add_animation (GtkWidget *vbox, PikaAboutDialog *dialog); static void about_dialog_add_update (PikaAboutDialog *dialog, PikaCoreConfig *config); static gboolean about_dialog_anim_draw (GtkWidget *widget, cairo_t *cr, PikaAboutDialog *dialog); static void about_dialog_reshuffle (PikaAboutDialog *dialog); static gboolean about_dialog_timer (gpointer data); #ifdef PIKA_UNSTABLE static void about_dialog_add_unstable_message (GtkWidget *vbox); #endif /* PIKA_UNSTABLE */ static void about_dialog_last_release_changed (PikaCoreConfig *config, const GParamSpec *pspec, PikaAboutDialog *dialog); static void about_dialog_download_clicked (GtkButton *button, const gchar *link); GtkWidget * about_dialog_create (Pika *pika, PikaCoreConfig *config) { static PikaAboutDialog dialog; if (! dialog.dialog) { GtkWidget *widget; GtkWidget *container; GdkPixbuf *pixbuf; GList *children; gchar *copyright; gchar *version; dialog.pika = pika; dialog.config = config; pixbuf = about_dialog_load_logo (); copyright = g_strdup_printf (PIKA_COPYRIGHT, PIKA_GIT_LAST_COMMIT_YEAR); if (pika_version_get_revision () > 0) /* Translators: the %s is PIKA version, the %d is the * installer/package revision. * For instance: "2.10.18 (revision 2)" */ version = g_strdup_printf (_("%s (revision %d)"), PIKA_VERSION, pika_version_get_revision ()); else version = g_strdup (PIKA_VERSION); widget = g_object_new (GTK_TYPE_ABOUT_DIALOG, "role", "pika-about", "window-position", GTK_WIN_POS_CENTER, "title", _("About PIKA"), "program-name", PIKA_ACRONYM, "version", version, "copyright", copyright, "comments", PIKA_NAME, "license", PIKA_LICENSE, "wrap-license", TRUE, "logo", pixbuf, "website", "https://heckin.technology/AlderconeStudio/PIKApp/", "website-label", _("Visit the PIKA website"), NULL); if (pixbuf) g_object_unref (pixbuf); g_free (copyright); g_free (version); g_set_weak_pointer (&dialog.dialog, widget); g_signal_connect (widget, "response", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect (widget, "map", G_CALLBACK (about_dialog_map), &dialog); g_signal_connect (widget, "unmap", G_CALLBACK (about_dialog_unmap), &dialog); /* kids, don't try this at home! */ container = gtk_dialog_get_content_area (GTK_DIALOG (widget)); children = gtk_container_get_children (GTK_CONTAINER (container)); if (GTK_IS_BOX (children->data)) { about_dialog_add_animation (children->data, &dialog); #ifdef PIKA_UNSTABLE about_dialog_add_unstable_message (children->data); #endif /* PIKA_UNSTABLE */ about_dialog_add_update (&dialog, config); } else g_warning ("%s: ooops, no box in this container?", G_STRLOC); g_list_free (children); } return dialog.dialog; } static void about_dialog_map (GtkWidget *widget, PikaAboutDialog *dialog) { pika_update_refresh (dialog->config); if (dialog->layout && dialog->timer == 0) { dialog->state = 0; dialog->index = 0; dialog->animstep = 0; dialog->visible = FALSE; about_dialog_reshuffle (dialog); dialog->timer = g_timeout_add (800, about_dialog_timer, dialog); } #ifdef G_OS_WIN32 pika_window_set_title_bar_theme (dialog->pika, widget, FALSE); #endif } static void about_dialog_unmap (GtkWidget *widget, PikaAboutDialog *dialog) { if (dialog->timer) { g_source_remove (dialog->timer); dialog->timer = 0; } } static GdkPixbuf * about_dialog_load_logo (void) { GdkPixbuf *pixbuf = NULL; GFile *file; GInputStream *input; file = pika_data_directory_file ("images", #ifdef PIKA_UNSTABLE "pika-devel-logo.png", #else "pika-logo.png", #endif NULL); input = G_INPUT_STREAM (g_file_read (file, NULL, NULL)); g_object_unref (file); if (input) { pixbuf = gdk_pixbuf_new_from_stream (input, NULL, NULL); g_object_unref (input); } return pixbuf; } static void about_dialog_add_animation (GtkWidget *vbox, PikaAboutDialog *dialog) { gint height; dialog->anim_area = gtk_drawing_area_new (); gtk_box_pack_start (GTK_BOX (vbox), dialog->anim_area, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (vbox), dialog->anim_area, 5); gtk_widget_show (dialog->anim_area); dialog->layout = gtk_widget_create_pango_layout (dialog->anim_area, NULL); g_object_weak_ref (G_OBJECT (dialog->anim_area), (GWeakNotify) g_object_unref, dialog->layout); pango_layout_get_pixel_size (dialog->layout, NULL, &height); gtk_widget_set_size_request (dialog->anim_area, -1, 2 * height); g_signal_connect (dialog->anim_area, "draw", G_CALLBACK (about_dialog_anim_draw), dialog); } static void about_dialog_add_update (PikaAboutDialog *dialog, PikaCoreConfig *config) { } static void about_dialog_reshuffle (PikaAboutDialog *dialog) { } static gboolean about_dialog_anim_draw (GtkWidget *widget, cairo_t *cr, PikaAboutDialog *dialog) { GtkStyleContext *style = gtk_widget_get_style_context (widget); GtkAllocation allocation; GdkRGBA color; gdouble alpha = 0.0; gint x, y; gint width, height; if (! dialog->visible) return FALSE; if (dialog->animstep < 16) { alpha = (gfloat) dialog->animstep / 15.0; } else if (dialog->animstep < 18) { alpha = 1.0; } else if (dialog->animstep < 33) { alpha = 1.0 - ((gfloat) (dialog->animstep - 17)) / 15.0; } gtk_style_context_get_color (style, gtk_style_context_get_state (style), &color); gdk_cairo_set_source_rgba (cr, &color); gtk_widget_get_allocation (widget, &allocation); pango_layout_get_pixel_size (dialog->layout, &width, &height); x = (allocation.width - width) / 2; y = (allocation.height - height) / 2; cairo_move_to (cr, x, y); cairo_push_group (cr); pango_cairo_show_layout (cr, dialog->layout); cairo_pop_group_to_source (cr); cairo_paint_with_alpha (cr, alpha); return FALSE; } static gchar * insert_spacers (const gchar *string) { GString *str = g_string_new (NULL); gchar *normalized; gchar *ptr; gunichar unichr; normalized = g_utf8_normalize (string, -1, G_NORMALIZE_DEFAULT_COMPOSE); ptr = normalized; while ((unichr = g_utf8_get_char (ptr))) { g_string_append_unichar (str, unichr); g_string_append_unichar (str, 0x200b); /* ZERO WIDTH SPACE */ ptr = g_utf8_next_char (ptr); } g_free (normalized); return g_string_free (str, FALSE); } static void decorate_text (PikaAboutDialog *dialog, gint anim_type, gdouble time) { const gchar *text; const gchar *ptr; gint letter_count = 0; gint cluster_start, cluster_end; gunichar unichr; PangoAttrList *attrlist = NULL; PangoAttribute *attr; PangoRectangle irect = {0, 0, 0, 0}; PangoRectangle lrect = {0, 0, 0, 0}; text = pango_layout_get_text (dialog->layout); g_return_if_fail (text != NULL); attrlist = pango_attr_list_new (); switch (anim_type) { case 0: /* Fade in */ break; case 1: /* Fade in, spread */ ptr = text; cluster_start = 0; while ((unichr = g_utf8_get_char (ptr))) { ptr = g_utf8_next_char (ptr); cluster_end = (ptr - text); if (unichr == 0x200b) { lrect.width = (1.0 - time) * 15.0 * PANGO_SCALE + 0.5; attr = pango_attr_shape_new (&irect, &lrect); attr->start_index = cluster_start; attr->end_index = cluster_end; pango_attr_list_change (attrlist, attr); } cluster_start = cluster_end; } break; case 2: /* Fade in, sinewave */ ptr = text; cluster_start = 0; while ((unichr = g_utf8_get_char (ptr))) { if (unichr == 0x200b) { cluster_end = ptr - text; attr = pango_attr_rise_new ((1.0 -time) * 18000 * sin (4.0 * time + (float) letter_count * 0.7)); attr->start_index = cluster_start; attr->end_index = cluster_end; pango_attr_list_change (attrlist, attr); letter_count++; cluster_start = cluster_end; } ptr = g_utf8_next_char (ptr); } break; default: g_printerr ("Unknown animation type %d\n", anim_type); } pango_layout_set_attributes (dialog->layout, attrlist); pango_attr_list_unref (attrlist); } static gboolean about_dialog_timer (gpointer data) { return TRUE; } #ifdef PIKA_UNSTABLE static void about_dialog_add_unstable_message (GtkWidget *vbox) { GtkWidget *label; gchar *text; text = g_strdup_printf (_("This is an unstable development release\n" "commit %s"), PIKA_GIT_VERSION_ABBREV); label = gtk_label_new (text); g_free (text); gtk_label_set_selectable (GTK_LABEL (label), TRUE); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); pika_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, -1); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (vbox), label, 2); gtk_widget_show (label); } #endif /* PIKA_UNSTABLE */ static void about_dialog_last_release_changed (PikaCoreConfig *config, const GParamSpec *pspec, PikaAboutDialog *dialog) { g_signal_handlers_disconnect_by_func (config, (GCallback) about_dialog_last_release_changed, dialog); if (! dialog->dialog) return; about_dialog_add_update (dialog, config); } static void about_dialog_download_clicked (GtkButton *button, const gchar *link) { GtkWidget *window; window = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_WINDOW); if (window) gtk_show_uri_on_window (GTK_WINDOW (window), link, GDK_CURRENT_TIME, NULL); }