anastasis-gtk

Demonstrator GUI for Anastasis
Log | Files | Refs | README | LICENSE

anastasis-gtk_helper.c (11785B)


      1 /*
      2      This file is part of anastasis-gtk.
      3      Copyright (C) 2020 Anastasis SARL
      4 
      5      Anastasis is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      Anastasis is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with Anastasis; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file src/anastasis/anastasis-gtk_helper.c
     23  * @brief Helper functions of anastasis-gtk
     24  * @author Christian Grothoff
     25  * @author Dennis Neufeld
     26  */
     27 #define _GNU_SOURCE
     28 #include <stdio.h>
     29 #include "anastasis_gtk_util.h"
     30 #include <gnunet/gnunet_util_lib.h>
     31 #include "anastasis-gtk_helper.h"
     32 #include <jansson.h>
     33 #include <qrencode.h>
     34 #include <gdk-pixbuf/gdk-pixbuf.h>
     35 
     36 
     37 /**
     38  * true if we are currently showing an error message.
     39  */
     40 bool AG_have_error;
     41 
     42 
     43 void
     44 AG_thaw ()
     45 {
     46   AG_error_clear ();
     47   AG_sensitive ("anastasis_gtk_main_window");
     48   GNUNET_assert (NULL == AG_ra);
     49 }
     50 
     51 
     52 void
     53 AG_freeze ()
     54 {
     55   AG_insensitive ("anastasis_gtk_main_window");
     56   AG_stop_long_action ();
     57   GNUNET_assert (NULL == AG_ra);
     58 }
     59 
     60 
     61 void
     62 AG_sensitive (const char *name)
     63 {
     64   GtkWidget *w;
     65 
     66   w = GTK_WIDGET (GCG_get_main_window_object (name));
     67   if (NULL == w)
     68   {
     69     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     70                 "Widget `%s' not found, cannot make it sensitive!\n",
     71                 name);
     72     return;
     73   }
     74   gtk_widget_set_sensitive (w,
     75                             true);
     76 }
     77 
     78 
     79 void
     80 AG_focus (const char *name)
     81 {
     82   GtkWidget *w;
     83 
     84   w = GTK_WIDGET (GCG_get_main_window_object (name));
     85   if (NULL == w)
     86   {
     87     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     88                 "Widget `%s' not found, cannot focus on it!\n",
     89                 name);
     90     return;
     91   }
     92   gtk_widget_grab_focus (w);
     93 }
     94 
     95 
     96 void
     97 AG_insensitive (const char *name)
     98 {
     99   GtkWidget *w;
    100 
    101   w = GTK_WIDGET (GCG_get_main_window_object (name));
    102   if (NULL == w)
    103   {
    104     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    105                 "Widget `%s' not found, cannot make it sensitive!\n",
    106                 name);
    107     return;
    108   }
    109   gtk_widget_set_sensitive (w,
    110                             false);
    111 }
    112 
    113 
    114 void
    115 AG_hide (const char *name)
    116 {
    117   GtkWidget *w;
    118 
    119   w = GTK_WIDGET (GCG_get_main_window_object (name));
    120   if (NULL == w)
    121   {
    122     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    123                 "Widget `%s' not found, cannot hide it!\n",
    124                 name);
    125     return;
    126   }
    127   gtk_widget_set_visible (w,
    128                           FALSE);
    129 }
    130 
    131 
    132 void
    133 AG_show (const char *name)
    134 {
    135   GtkWidget *w;
    136 
    137   w = GTK_WIDGET (GCG_get_main_window_object (name));
    138   if (NULL == w)
    139   {
    140     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    141                 "Widget `%s' not found, cannot show it!\n",
    142                 name);
    143     return;
    144   }
    145   gtk_widget_set_visible (w,
    146                           TRUE);
    147 }
    148 
    149 
    150 /**
    151  * Look up the widget @a name in the main window and return its first child.
    152  *
    153  * @param name widget to inspect
    154  * @return NULL if the widget does not exist or has no children
    155  */
    156 static GtkWidget *
    157 first_child (const char *name)
    158 {
    159   GObject *w;
    160 
    161   w = GCG_get_main_window_object (name);
    162   if (NULL == w)
    163   {
    164     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    165                 "Widget `%s' not found, cannot iterate over its children!\n",
    166                 name);
    167     return NULL;
    168   }
    169   return gtk_widget_get_first_child (GTK_WIDGET (w));
    170 }
    171 
    172 
    173 void
    174 AG_insensitive_children (const char *name)
    175 {
    176   for (GtkWidget *child = first_child (name);
    177        NULL != child;
    178        child = gtk_widget_get_next_sibling (child))
    179     gtk_widget_set_sensitive (child,
    180                               false);
    181 }
    182 
    183 
    184 void
    185 AG_hide_children (const char *name)
    186 {
    187   for (GtkWidget *child = first_child (name);
    188        NULL != child;
    189        child = gtk_widget_get_next_sibling (child))
    190     gtk_widget_set_visible (child,
    191                             FALSE);
    192 }
    193 
    194 
    195 void
    196 AG_show_children (const char *name)
    197 {
    198   for (GtkWidget *child = first_child (name);
    199        NULL != child;
    200        child = gtk_widget_get_next_sibling (child))
    201     gtk_widget_set_visible (child,
    202                             TRUE);
    203 }
    204 
    205 
    206 void
    207 AG_hide_all_frames (void)
    208 {
    209   AG_hide ("anastasis_gtk_start_frame");
    210   AG_hide_children ("anastasis_gtk_super_vbox");
    211   if (AG_have_error)
    212     AG_show ("anastasis_gtk_error_label");
    213 }
    214 
    215 
    216 bool
    217 AG_check_state (json_t *state,
    218                 const char *expected_state)
    219 {
    220   const char *state_name = json_string_value (json_object_get (state,
    221                                                                "backup_state"));
    222   if (NULL == state_name)
    223     state_name = json_string_value (json_object_get (state,
    224                                                      "recovery_state"));
    225   if (NULL == state_name)
    226     return false;
    227   return (0 == strcasecmp (state_name,
    228                            expected_state));
    229 }
    230 
    231 
    232 /**
    233  * Get an object from the main window.
    234  *
    235  * @param name name of the object
    236  * @return NULL on error
    237  */
    238 GObject *
    239 GCG_get_main_window_object (const char *name)
    240 {
    241   if (NULL == AG_ml)
    242     return NULL;
    243   return ANASTASIS_GTK_main_loop_get_object (AG_ml,
    244                                           name);
    245 }
    246 
    247 
    248 /**
    249  * Make the 'next' button sensitive (and trigger it via 'Return').
    250  */
    251 void
    252 AG_enable_next (void)
    253 {
    254   GtkWidget *fwd;
    255 
    256   AG_show ("anastasis_gtk_main_window_forward_button");
    257   AG_sensitive ("anastasis_gtk_main_window_forward_button");
    258   fwd = GTK_WIDGET (GCG_get_main_window_object (
    259                       "anastasis_gtk_main_window_forward_button"));
    260   gtk_window_set_default_widget (GTK_WINDOW (gtk_widget_get_root (fwd)),
    261                                  fwd);
    262 }
    263 
    264 
    265 void
    266 AG_error_clear ()
    267 {
    268   AG_have_error = false;
    269   AG_hide ("anastasis_gtk_error_label");
    270 }
    271 
    272 
    273 void
    274 AG_error (const char *format,
    275           ...)
    276 {
    277   va_list ap;
    278   char *msg;
    279   int ret;
    280   GtkLabel *l;
    281 
    282   va_start (ap, format);
    283   ret = vasprintf (&msg,
    284                    format,
    285                    ap);
    286   va_end (ap);
    287   if (-1 == ret)
    288   {
    289     GNUNET_break (0);
    290     return;
    291   }
    292   l = GTK_LABEL (GCG_get_main_window_object ("anastasis_gtk_error_label"));
    293   if (NULL == l)
    294   {
    295     GNUNET_break (0);
    296     return;
    297   }
    298   gtk_label_set_text (l,
    299                       msg);
    300   free (msg);
    301   AG_have_error = true;
    302   gtk_widget_set_visible (GTK_WIDGET (l),
    303                           TRUE);
    304 }
    305 
    306 
    307 /**
    308  * Create a the QR code image from a given @a text.
    309  *
    310  * @param scale factor for scaling up the size of the image to create
    311  * @param text text to encode
    312  * @return NULL on error
    313  */
    314 static GdkPixbuf *
    315 create_qrcode (unsigned int scale,
    316                const char *text,
    317                size_t text_size)
    318 {
    319   QRinput *qri;
    320   QRcode *qrc;
    321   GdkPixbuf *pb;
    322   guchar *pixels;
    323   int n_channels;
    324   const char *dir;
    325   char *fn;
    326   unsigned int size;
    327 
    328   qri = QRinput_new2 (0, QR_ECLEVEL_M);
    329   if (NULL == qri)
    330   {
    331     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2");
    332     return NULL;
    333   }
    334   /* first try encoding as uppercase-only alpha-numerical
    335      QR code (much smaller encoding); if that fails, also
    336      try using binary encoding (in case nick contains
    337      special characters). */
    338   if ((0 != QRinput_append (qri,
    339                             QR_MODE_AN,
    340                             text_size,
    341                             (unsigned char *) text)) &&
    342       (0 != QRinput_append (qri,
    343                             QR_MODE_8,
    344                             text_size,
    345                             (unsigned char *) text)))
    346   {
    347     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    348                          "QRinput_append");
    349     return NULL;
    350   }
    351   qrc = QRcode_encodeInput (qri);
    352   if (NULL == qrc)
    353   {
    354     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    355                          "QRcode_encodeInput");
    356     QRinput_free (qri);
    357     return NULL;
    358   }
    359   /* We create the pixbuf by loading a dummy file from disk, which gives us
    360      the pixel layout the renderer expects. */
    361   dir = ANASTASIS_GTK_get_data_dir (ANASTASIS_GTK_project_data ());
    362   GNUNET_asprintf (&fn,
    363                    "%s%s",
    364                    dir,
    365                    "qr_dummy.png");
    366   size = (qrc->width + 8) * scale;
    367   size += 8 - (size % 8);
    368   pb = gdk_pixbuf_new_from_file_at_size (fn,
    369                                          size,
    370                                          size,
    371                                          NULL);
    372   GNUNET_free (fn);
    373   if (NULL == pb)
    374   {
    375     QRcode_free (qrc);
    376     QRinput_free (qri);
    377     return NULL;
    378   }
    379   pixels = gdk_pixbuf_get_pixels (pb);
    380   n_channels = gdk_pixbuf_get_n_channels (pb);
    381   for (unsigned int x = 4 * scale; x < size - 4 * scale; x++)
    382     for (unsigned int y = 4 * scale; y < size - 4 * scale; y++)
    383     {
    384       unsigned int xx = x - 4 * scale;
    385       unsigned int yy = y - 4 * scale;
    386       unsigned int ss = size - 8 * scale;
    387       unsigned int off =
    388         (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width;
    389       for (int c = 0; c < n_channels; c++)
    390         pixels[(y * size + x) * n_channels + c] =
    391           (0 == (qrc->data[off] & 1)) ? 0xFF : 0;
    392     }
    393   QRcode_free (qrc);
    394   QRinput_free (qri);
    395   return pb;
    396 }
    397 
    398 
    399 GdkPixbuf *
    400 AG_setup_qrcode (GtkWidget *w,
    401                  const char *text,
    402                  size_t text_size)
    403 {
    404   GtkSettings *settings;
    405   gint dpi;
    406   int scale;
    407 
    408   if (NULL == w)
    409   {
    410     GNUNET_break (0);
    411     return NULL;
    412   }
    413   /* adjust scale to screen resolution */
    414   settings = gtk_widget_get_settings (w);
    415   g_object_get (G_OBJECT (settings),
    416                 "gtk-xft-dpi",
    417                 &dpi,
    418                 NULL);
    419   if (-1 == dpi)
    420     scale = 2;
    421   else if (dpi >= 122800)
    422     scale = 4;
    423   else if (dpi >= 98304)
    424     scale = 3;
    425   else
    426     scale = 2;
    427   return create_qrcode (scale,
    428                         text,
    429                         text_size);
    430 }
    431 
    432 
    433 void
    434 AG_image_set_from_pixbuf (GtkImage *img,
    435                           GdkPixbuf *pb)
    436 {
    437   GdkTexture *texture;
    438 
    439   texture = gdk_texture_new_for_pixbuf (pb);
    440   gtk_image_set_from_paintable (img,
    441                                 GDK_PAINTABLE (texture));
    442   g_object_unref (texture);
    443 }
    444 
    445 
    446 char *
    447 AG_expand_name (const char *name,
    448                 const char *type)
    449 {
    450   static struct
    451   {
    452     const char *type;
    453     const char *suffix;
    454   } type_map [] = {
    455     { .type = "string",
    456       .suffix = "entry" },
    457     { .type = "date",
    458       .suffix = "cal" },
    459     { .type = NULL,
    460       .suffix = NULL }
    461   };
    462   char *data_widget;
    463 
    464   for (unsigned int i = 0; NULL != type_map[i].type; i++)
    465   {
    466     if (0 != strcmp (type_map[i].type,
    467                      type))
    468       continue;
    469     GNUNET_asprintf (&data_widget,
    470                      "%s_%s",
    471                      name,
    472                      type_map[i].suffix);
    473     return data_widget;
    474   }
    475   return NULL;
    476 }
    477 
    478 
    479 /**
    480  * Show widget when the watched entry takes the focus.
    481  *
    482  * @param w widget to show (the UI file connects this 'swapped')
    483  * @param controller the focus controller of the entry
    484  */
    485 void
    486 anastasis_gtk_show_on_focus_enter_cb (GtkWidget *w,
    487                                       GtkEventControllerFocus *controller)
    488 {
    489   gtk_widget_set_visible (w,
    490                           TRUE);
    491 }
    492 
    493 
    494 /**
    495  * Hide widget when the watched entry loses the focus.
    496  *
    497  * @param w widget to hide (the UI file connects this 'swapped')
    498  * @param controller the focus controller of the entry
    499  */
    500 void
    501 anastasis_gtk_hide_on_focus_leave_cb (GtkWidget *w,
    502                                       GtkEventControllerFocus *controller)
    503 {
    504   gtk_widget_set_visible (w,
    505                           FALSE);
    506 }