diff --git a/about-dialog.c b/about-dialog.c new file mode 100644 index 0000000..c188122 --- /dev/null +++ b/about-dialog.c @@ -0,0 +1,489 @@ +/* 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); +} diff --git a/pika.yaml b/pika.yaml index c947d8d..1c3df34 100644 --- a/pika.yaml +++ b/pika.yaml @@ -5,7 +5,10 @@ icon: pika.png scalable: pika.svg splash: pika-splash.png url: https://heckin.technology/AlderconeStudio/PIKApp -version: 2.99.0 +docsurl: https://heckin.technology/AlderconeStudio/PIKApp/wiki/ +donateurl: https://mastodon.art/@aldercone +version: 0.0.0+GnuImp2.99.70 copyright: Aldercone Studio shadow: pika-shadow.txt - +files: + about-dialog.c: app/dialogs/about-dialog.c