+/* -copyright-
+#-# Copyright © 2021 Eric Bina, Dave Black, TJ Phan,
+#-# Vincent Renardias, Willem Vermin
+#-#
+#-# Permission is hereby granted, free of charge, to any person
+#-# obtaining a copy of this software and associated documentation
+#-# files (the “Software”), to deal in the Software without
+#-# restriction, including without limitation the rights to use,
+#-# copy, modify, merge, publish, distribute, sublicense, and/or
+#-# sell copies of the Software, and to permit persons to whom
+#-# the Software is furnished to do so, subject to the following
+#-# conditions:
+#-#
+#-# The above copyright notice and this permission notice shall
+#-# be included in all copies or substantial portions of the Software.
+#-#
+#-# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+#-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#-# OTHER DEALINGS IN THE SOFTWARE.
+#-#
+*/
+
+#include <gtk/gtk.h>
+#include <math.h>
+#include "ui_xml.h"
+#include "ui.h"
+#include "debug.h"
+#include "xfishtank.h"
+#include "utils.h"
+#include "fishes.h"
+
+//#include "xfishtank.xpm"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __cplusplus
+#define MODULE_EXPORT extern "C" G_MODULE_EXPORT
+#else
+#define MODULE_EXPORT G_MODULE_EXPORT
+#endif
+
+int FlagsChanged = 1;
+
+static int human_action = 1; /* is it a human who is playing with the buttons? */
+
+#define NTYPES 16
+static GtkBuilder *builder;
+static GtkWidget *hauptfenster;
+static GtkStyleContext *hauptfenstersc;
+static char buf[100];
+static GtkWidget *id_fishes;
+static GtkWidget *id_fishesvalue;
+static GtkWidget *id_speed;
+static GtkWidget *id_speedvalue;
+static GtkWidget *id_bubbles;
+//static GtkWidget *id_squish;
+static GtkWidget *id_logo1;
+static GtkWidget *id_logo2;
+static GtkWidget *id_logo3;
+static GtkWidget *id_bubble_color;
+static GtkWidget *id_bg_color;
+static GtkWidget *id_bubblesvalue;
+//static GtkWidget *id_squish_picture;
+
+static int logo[3];
+static int flip[3];
+
+static void handle_css(void);
+static void init_ids(void);
+static void init_pixmaps(void);
+static FILE *openflagsfile(char *mode);
+static float scale_to_bubblesfactor(float scale);
+static void ab(float Max, float Min, float *a, float *b);
+static float fishes_to_scale(float n);
+static float bubblesfactor_to_scale(float n);
+static float scale_to_fishes(float scale);
+static float speedfactor_to_scale(float n);
+static float scale_to_speedfactor(float scale);
+static void init_pixmap(GtkWidget *id, int n, int flip);
+static int do_animate(void *dummy);
+
+
+// Sometimes it is good to have a logarithmic scale, such that
+//
+// V = a*M*10**s + b
+//
+// where: V = parameter (e.g. SpeedFactor)
+// M = desired maximum of V (e.g. 4.0)
+// s = value of the gtkscale (0 .. 1.0)
+// Furthermore: m = desired minimum of V (e.g. 0.2)
+//
+// Then:
+// a = (M - m)/(9*M)
+// b = m - a*M
+//
+// The placement of a is a logical choice, the placement of b is
+// more or less random. I need a constant next to a, because I want
+// to define minimum V AND maximum V.
+//
+// Given V, compute s:
+//
+// s = log10((V-b)/(a*M))
+//
+
+void ab(float Max, float Min, float *a, float *b)
+{
+ *a = (Max - Min)/(9.0*Max);
+ *b = Min - (*a)*Max;
+}
+
+static const float MaxBubbles = 500;
+static const float MinBubbles = 1;
+
+static const float MaxFishes = 200;
+static const float MinFishes = 1.0;
+
+static const float MaxSpeed = 400;
+static const float MinSpeed = 30;
+
+void handle_css()
+{
+}
+
+
+void show_dialog(int type, const char *format, const char *text)
+{
+ GtkMessageType message_type;
+ if (type == 1)
+ message_type = GTK_MESSAGE_ERROR;
+ else
+ message_type = GTK_MESSAGE_INFO;
+
+ GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(hauptfenster),
+ GTK_DIALOG_MODAL,
+ message_type,
+ GTK_BUTTONS_OK,
+ format,
+ text
+ );
+ g_signal_connect(m,"response",G_CALLBACK(gtk_main_quit),NULL);
+ gtk_widget_show_all(m);
+}
+
+void init_ids()
+{
+ id_fishes = GTK_WIDGET(gtk_builder_get_object(builder, "id-fishes"));
+ id_fishesvalue = GTK_WIDGET(gtk_builder_get_object(builder, "id-fishesvalue"));
+ id_speed = GTK_WIDGET(gtk_builder_get_object(builder, "id-speed"));
+ id_speedvalue = GTK_WIDGET(gtk_builder_get_object(builder, "id-speedvalue"));
+ id_logo1 = GTK_WIDGET(gtk_builder_get_object(builder, "id-logo1"));
+ id_logo2 = GTK_WIDGET(gtk_builder_get_object(builder, "id-logo2"));
+ id_logo3 = GTK_WIDGET(gtk_builder_get_object(builder, "id-logo3"));
+ id_bubble_color = GTK_WIDGET(gtk_builder_get_object(builder, "id-bubble-color"));
+ id_bg_color = GTK_WIDGET(gtk_builder_get_object(builder, "id-bg-color"));
+ id_bubbles = GTK_WIDGET(gtk_builder_get_object(builder, "id-bubbles"));
+ //id_squish = GTK_WIDGET(gtk_builder_get_object(builder, "id-squish"));
+ id_bubblesvalue = GTK_WIDGET(gtk_builder_get_object(builder, "id-bubblesvalue"));
+ //id_squish_picture = GTK_WIDGET(gtk_builder_get_object(builder, "id-squish-picture"));
+}
+
+int do_animate(void *dummy)
+{
+ (void)dummy;
+ int m = drand48()*3;
+ P("m: %d %d\n",m, logo[m]);
+ // needs adjustment if NUM_FRAMES != 2
+ if (logo[m]%NUM_FRAMES)
+ logo[m]--;
+ else
+ logo[m]++;
+ GtkWidget *id;
+ switch(m)
+ {
+ case 0:
+ id = id_logo1;
+ break;
+ case 1:
+ id = id_logo2;
+ break;
+ case 2:
+ id = id_logo3;
+ break;
+ }
+ init_pixmap(id, logo[m], flip[m]);
+
+ return TRUE;
+}
+
+void init_pixmap(GtkWidget *id, int n, int flip)
+{
+ int w,h;
+ float w0,h0;
+
+ GdkPixbuf *pixbuf, *pixbuf1, *pixbuf2;
+ pixbuf = gdk_pixbuf_new_from_xpm_data ((const char **)fishes[n]);
+ w0 = gdk_pixbuf_get_width(pixbuf);
+ h0 = gdk_pixbuf_get_height(pixbuf);
+ if (w0 > h0)
+ {
+ w = 64;
+ h = h0/w0*w;
+ }
+ else
+ {
+ h = 64;
+ w = w0/h0*h;
+ }
+
+ pixbuf1 = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_BILINEAR);
+ g_object_unref(pixbuf);
+
+ if(flip)
+ {
+ pixbuf2 = gdk_pixbuf_flip(pixbuf1,1);
+ gtk_image_set_from_pixbuf(GTK_IMAGE(id),pixbuf2);
+ g_object_unref(pixbuf2);
+ }
+ else
+ gtk_image_set_from_pixbuf(GTK_IMAGE(id),pixbuf1);
+
+ g_object_unref(pixbuf1);
+}
+
+void init_pixmaps()
+{
+ int i;
+ for (i=0; i<3; i++)
+ {
+ logo[i] = NUM_FRAMES*(int)(drand48()*NUM_FISH);
+ flip[i] = drand48()*2;
+ }
+ init_pixmap(id_logo1,logo[0],flip[0]);
+ init_pixmap(id_logo2,logo[1],flip[1]);
+ init_pixmap(id_logo3,logo[2],flip[2]);
+}
+
+void set_buttons()
+{
+ int h = human_action;
+ human_action = 0;
+
+ gtk_range_set_value(GTK_RANGE(id_fishes), fishes_to_scale(flimit));
+ sprintf(buf,"%d",flimit);
+ gtk_label_set_text(GTK_LABEL(id_fishesvalue),buf);
+
+ gtk_range_set_value(GTK_RANGE(id_bubbles),bubblesfactor_to_scale(blimit));
+ sprintf(buf,"%d",(int)(blimit));
+ gtk_label_set_text(GTK_LABEL(id_bubblesvalue),buf);
+
+ gtk_range_set_value(GTK_RANGE(id_speed),speedfactor_to_scale(100*speedfactor));
+ P("speedfactor: %f %f\n",speedfactor, speedfactor_to_scale(100*speedfactor));
+ sprintf(buf,"%d",(int)(100*speedfactor));
+ gtk_label_set_text(GTK_LABEL(id_speedvalue),buf);
+
+ GdkRGBA color;
+ P("bcolor: %s\n",bcolorstring);
+ gdk_rgba_parse(&color, bcolorstring);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(id_bubble_color),&color);
+
+ P("bgcolor: %s\n",bgcolorstring);
+ gdk_rgba_parse(&color, bgcolorstring);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(id_bg_color),&color);
+
+ human_action = h;
+
+ FlagsChanged = 1;
+}
+
+
+MODULE_EXPORT void button_fishes(GtkWidget *w)
+{
+ if (!human_action)
+ return;
+ float value = gtk_range_get_value(GTK_RANGE(w));
+ P("fishes: %f\n",value);
+ create_fishes(scale_to_fishes(value));
+ FlagsChanged = 1;
+
+ sprintf(buf,"%d",flimit);
+ gtk_label_set_text(GTK_LABEL(id_fishesvalue),buf);
+}
+
+MODULE_EXPORT void button_bubbles(GtkWidget *w)
+{
+ if (!human_action)
+ return;
+ float value = gtk_range_get_value(GTK_RANGE(w));
+ P("bubbles: %f %d\n",value,blimit);
+ create_bubbles(scale_to_bubblesfactor(value));
+ FlagsChanged = 1;
+
+ sprintf(buf,"%d",blimit);
+ gtk_label_set_text(GTK_LABEL(id_bubblesvalue),buf);
+}
+
+MODULE_EXPORT void button_speed(GtkWidget *w)
+{
+ if (!human_action)
+ return;
+ float value = gtk_range_get_value(GTK_RANGE(w));
+ P("speedfactor: %f %f\n",value,speedfactor);
+ setspeed(scale_to_speedfactor(value));
+ P("speedfactor: %f\n",speedfactor);
+ FlagsChanged = 1;
+
+ sprintf(buf,"%d",(int)(100*speedfactor));
+ gtk_label_set_text(GTK_LABEL(id_speedvalue),buf);
+}
+
+
+MODULE_EXPORT void button_defaults()
+{
+ set_defaults();
+ create_fishes(flimit);
+ create_bubbles(blimit);
+ setbcolor();
+ setbgcolor();
+ setspeed(speedfactor);
+ if (!human_action)
+ return;
+ set_buttons();
+}
+
+#if 0
+MODULE_EXPORT void button_squish(GtkWidget *w)
+{
+ if (!human_action)
+ return;
+ int value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
+ P("squish: %d\n",value);
+ handle_squish(value);
+ FlagsChanged = 1;
+}
+#endif
+MODULE_EXPORT void button_iconify()
+{
+ if (!human_action)
+ return;
+ gtk_window_iconify(GTK_WINDOW(hauptfenster));
+}
+
+MODULE_EXPORT void button_bubble_color(GtkWidget *w)
+{
+ GdkRGBA color;
+ if (!human_action)
+ return;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(w),&color);
+ free(bcolorstring);
+ rgba2color(&color,&bcolorstring);
+ setbcolor();
+ FlagsChanged = 1;
+}
+
+MODULE_EXPORT void button_bg_color(GtkWidget *w)
+{
+ GdkRGBA color;
+ if (!human_action)
+ return;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(w),&color);
+ free(bgcolorstring);
+ rgba2color(&color,&bgcolorstring);
+ setbgcolor();
+ FlagsChanged = 1;
+}
+
+#if 0
+MODULE_EXPORT void button_guts_color(GtkWidget *w)
+{
+ GdkRGBA color;
+ if (!human_action)
+ return;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(w),&color);
+ free(gutsColor);
+ rgba2color(&color,&gutsColor);
+ setgutsgcColor();
+ FlagsChanged = 1;
+}
+#endif
+
+#if 0
+MODULE_EXPORT void button_squish_color(GtkWidget *w)
+{
+ GdkRGBA color;
+ if (!human_action)
+ return;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(w),&color);
+ free(gutsColor);
+ rgba2color(&color,&gutsColor);
+}
+#endif
+
+FILE *openflagsfile(char *mode)
+{
+ char *h = getenv("HOME");
+ if (h == NULL)
+ return NULL;
+ char *flagsfile = (char*)malloc((strlen(h)+strlen(FLAGSFILE)+2)*sizeof(char));
+ flagsfile[0] = 0;
+ strcat(flagsfile,h);
+ strcat(flagsfile,"/");
+ strcat(flagsfile,FLAGSFILE);
+ FILE *f = fopen(flagsfile,mode);
+ P("openflagsfile %s %s\n",flagsfile,mode);
+ free(flagsfile);
+ return f;
+}
+
+void ReadFlags()
+{
+ FILE *f = openflagsfile(_("r"));
+ if (f == NULL)
+ {
+ I("Cannot read $HOME/%s\n",FLAGSFILE);
+ return;
+ }
+ int lineno = 1;
+ while(1)
+ {
+ char *line = NULL;
+ size_t n = 0;
+ int m = getline(&line,&n,f);
+ if (m<0)
+ break;
+ P("ReadFlags: %d [%s]\n",lineno,line);
+ char *flag = (char*)malloc((strlen(line)+1)*sizeof(char));
+ m = sscanf(line, "%s", flag);
+ if (m == EOF || m == 0)
+ continue;
+ char *rest = line + strlen(flag);
+
+ char *p;
+ p = rest;
+ while (*p == ' ' || *p == '\t' || *p == '\n')
+ p++;
+ rest = p;
+ p = &line[strlen(line)-1];
+ while (*p == ' ' || *p == '\t' || *p == '\n')
+ p--;
+ *(p+1) = 0;
+
+ P("ReadFlags: %s [%s]\n",flag,rest);
+ if(!strcmp(flag,"fishes"))
+ {
+ flimit = atoi(rest);
+ P("flimit: %d\n",flimit);
+ }
+ else if(!strcmp(flag,"bubbles"))
+ {
+ blimit = atoi(rest);
+ P("bubbles: %d\n",blimit);
+ }
+ else if(!strcmp(flag,"speed"))
+ {
+ speedfactor = atoi(rest)/100.0;
+ P("speedfactor: %d\n",(int)(100*speedfactor));
+ }
+ else if(!strcmp(flag,"bc"))
+ {
+ if(bcolorstring)
+ free(bcolorstring);
+ bcolorstring = strdup(rest);
+ P("bcolorstring: %s\n",bcolorstring);
+ }
+ else if(!strcmp(flag,"bgc"))
+ {
+ if(bgcolorstring)
+ free(bgcolorstring);
+ bgcolorstring = strdup(rest);
+ P("bcolorstring: %s\n",bgcolorstring);
+ }
+ lineno++;
+ free(line);
+ free(flag);
+ }
+ fclose(f);
+}
+
+void WriteFlags()
+{
+ FILE *f = openflagsfile(_("w"));
+ if (f == NULL)
+ {
+ I("Cannot write $HOME/%s\n",FLAGSFILE);
+ return;
+ }
+ fprintf(f, "fishes %d\n", flimit);
+ fprintf(f, "bubbles %d\n", blimit);
+ if (bcolorstring && strlen(bcolorstring))
+ fprintf(f, "bc %s\n", bcolorstring);
+ if(bgcolorstring && strlen(bgcolorstring))
+ fprintf(f, "bgc %s\n", bgcolorstring);
+ fprintf(f, "speed %d\n", (int)(100*speedfactor));
+ fclose(f);
+ init_pixmaps();
+}
+
+float bubblesfactor_to_scale(float n)
+{
+ float a,b;
+ ab(MaxBubbles, MinBubbles,&a,&b);
+ return mylog10f((n-b)/(a*MaxBubbles));
+}
+
+
+float scale_to_bubblesfactor(float scale)
+{
+ float a,b;
+ ab(MaxBubbles, MinBubbles,&a,&b);
+ return a * MaxBubbles*myexp10f(scale) + b;
+}
+
+float fishes_to_scale(float n)
+{
+ float a,b;
+ ab(MaxFishes, MinFishes, &a, &b);
+ float rc = mylog10f((n-b)/(a*MaxFishes));
+ P("ftos %f %f\n",n,rc);
+ return rc;
+}
+
+float scale_to_fishes(float scale)
+{
+ float a,b;
+ ab(MaxFishes, MinFishes, &a, &b);
+ return a * MaxFishes*myexp10f(scale) + b;
+}
+
+float speedfactor_to_scale(float n)
+{
+ float a,b;
+ ab(MaxSpeed, MinSpeed, &a, &b);
+ float rc = mylog10f((n-b)/(a*MaxSpeed));
+ P("stos %f %f\n",n,rc);
+ return rc;
+}
+
+float scale_to_speedfactor(float scale)
+{
+ float a,b;
+ ab(MaxSpeed, MinSpeed, &a, &b);
+ return 0.01*(a * MaxSpeed*myexp10f(scale) + b);
+}
+
+void iconify()
+{
+ gtk_window_iconify(GTK_WINDOW(hauptfenster));
+}
+
+void ui()
+{
+ P("here is uiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\n");
+
+ builder = gtk_builder_new_from_string (xfishtank_xml, -1);
+ gtk_builder_connect_signals (builder, builder);
+ hauptfenster = GTK_WIDGET(gtk_builder_get_object(builder, "hauptfenster"));
+
+ hauptfenstersc = gtk_widget_get_style_context(hauptfenster);
+
+ handle_css();
+ char wtitle[100];
+ wtitle[0] = 0;
+ strcat(wtitle,"XfishtanK");
+#ifdef HAVE_CONFIG_H
+ strcat(wtitle,"-");
+ strncat(wtitle,VERSION,99 - strlen(wtitle));
+#endif
+ gtk_window_set_title(GTK_WINDOW(hauptfenster),wtitle);
+ gtk_window_set_resizable(GTK_WINDOW(hauptfenster),False);
+ gtk_widget_show_all (hauptfenster);
+ g_signal_connect (GTK_WINDOW(hauptfenster), "delete-event", G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (GTK_WINDOW(hauptfenster), "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+ init_ids();
+
+ init_pixmaps();
+ set_buttons();
+ g_timeout_add_full(G_PRIORITY_DEFAULT, 150, do_animate ,NULL, NULL);
+}
+