anastasis-gtk

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

anastasis-gtk_helper.c (13271B)


      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   /* AG_expand_name() returns NULL for attribute types we do not know a
    244      widget suffix for; do not pass that on to GTK, which would only log a
    245      critical of its own before the caller gets to report the real problem. */
    246   if (NULL == name)
    247     return NULL;
    248   return ANASTASIS_GTK_main_loop_get_object (AG_ml,
    249                                           name);
    250 }
    251 
    252 
    253 /**
    254  * Make the 'next' button sensitive (and trigger it via 'Return').
    255  */
    256 void
    257 AG_enable_next (void)
    258 {
    259   GtkWidget *fwd;
    260 
    261   AG_show ("anastasis_gtk_main_window_forward_button");
    262   AG_sensitive ("anastasis_gtk_main_window_forward_button");
    263   fwd = GTK_WIDGET (GCG_get_main_window_object (
    264                       "anastasis_gtk_main_window_forward_button"));
    265   gtk_window_set_default_widget (GTK_WINDOW (gtk_widget_get_root (fwd)),
    266                                  fwd);
    267 }
    268 
    269 
    270 void
    271 AG_error_clear ()
    272 {
    273   AG_have_error = false;
    274   AG_hide ("anastasis_gtk_error_label");
    275 }
    276 
    277 
    278 void
    279 AG_error (const char *format,
    280           ...)
    281 {
    282   va_list ap;
    283   char *msg;
    284   int ret;
    285   GtkLabel *l;
    286 
    287   va_start (ap, format);
    288   ret = vasprintf (&msg,
    289                    format,
    290                    ap);
    291   va_end (ap);
    292   if (-1 == ret)
    293   {
    294     GNUNET_break (0);
    295     return;
    296   }
    297   l = GTK_LABEL (GCG_get_main_window_object ("anastasis_gtk_error_label"));
    298   if (NULL == l)
    299   {
    300     GNUNET_break (0);
    301     return;
    302   }
    303   gtk_label_set_text (l,
    304                       msg);
    305   free (msg);
    306   AG_have_error = true;
    307   gtk_widget_set_visible (GTK_WIDGET (l),
    308                           TRUE);
    309 }
    310 
    311 
    312 /**
    313  * Create a the QR code image from a given @a text.
    314  *
    315  * @param scale factor for scaling up the size of the image to create
    316  * @param text text to encode
    317  * @return NULL on error
    318  */
    319 static GdkPixbuf *
    320 create_qrcode (unsigned int scale,
    321                const char *text,
    322                size_t text_size)
    323 {
    324   QRinput *qri;
    325   QRcode *qrc;
    326   GdkPixbuf *pb;
    327   guchar *pixels;
    328   int n_channels;
    329   const char *dir;
    330   char *fn;
    331   unsigned int size;
    332 
    333   qri = QRinput_new2 (0, QR_ECLEVEL_M);
    334   if (NULL == qri)
    335   {
    336     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2");
    337     return NULL;
    338   }
    339   /* first try encoding as uppercase-only alpha-numerical
    340      QR code (much smaller encoding); if that fails, also
    341      try using binary encoding (in case nick contains
    342      special characters). */
    343   if ((0 != QRinput_append (qri,
    344                             QR_MODE_AN,
    345                             text_size,
    346                             (unsigned char *) text)) &&
    347       (0 != QRinput_append (qri,
    348                             QR_MODE_8,
    349                             text_size,
    350                             (unsigned char *) text)))
    351   {
    352     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    353                          "QRinput_append");
    354     return NULL;
    355   }
    356   qrc = QRcode_encodeInput (qri);
    357   if (NULL == qrc)
    358   {
    359     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    360                          "QRcode_encodeInput");
    361     QRinput_free (qri);
    362     return NULL;
    363   }
    364   /* We create the pixbuf by loading a dummy file from disk, which gives us
    365      the pixel layout the renderer expects. */
    366   dir = ANASTASIS_GTK_get_data_dir (ANASTASIS_GTK_project_data ());
    367   GNUNET_asprintf (&fn,
    368                    "%s%s",
    369                    dir,
    370                    "qr_dummy.png");
    371   size = (qrc->width + 8) * scale;
    372   size += 8 - (size % 8);
    373   pb = gdk_pixbuf_new_from_file_at_size (fn,
    374                                          size,
    375                                          size,
    376                                          NULL);
    377   GNUNET_free (fn);
    378   if (NULL == pb)
    379   {
    380     QRcode_free (qrc);
    381     QRinput_free (qri);
    382     return NULL;
    383   }
    384   pixels = gdk_pixbuf_get_pixels (pb);
    385   n_channels = gdk_pixbuf_get_n_channels (pb);
    386   for (unsigned int x = 4 * scale; x < size - 4 * scale; x++)
    387     for (unsigned int y = 4 * scale; y < size - 4 * scale; y++)
    388     {
    389       unsigned int xx = x - 4 * scale;
    390       unsigned int yy = y - 4 * scale;
    391       unsigned int ss = size - 8 * scale;
    392       unsigned int off =
    393         (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width;
    394       for (int c = 0; c < n_channels; c++)
    395         pixels[(y * size + x) * n_channels + c] =
    396           (0 == (qrc->data[off] & 1)) ? 0xFF : 0;
    397     }
    398   QRcode_free (qrc);
    399   QRinput_free (qri);
    400   return pb;
    401 }
    402 
    403 
    404 GdkPixbuf *
    405 AG_setup_qrcode (GtkWidget *w,
    406                  const char *text,
    407                  size_t text_size)
    408 {
    409   GtkSettings *settings;
    410   gint dpi;
    411   int scale;
    412 
    413   if (NULL == w)
    414   {
    415     GNUNET_break (0);
    416     return NULL;
    417   }
    418   /* adjust scale to screen resolution */
    419   settings = gtk_widget_get_settings (w);
    420   g_object_get (G_OBJECT (settings),
    421                 "gtk-xft-dpi",
    422                 &dpi,
    423                 NULL);
    424   if (-1 == dpi)
    425     scale = 2;
    426   else if (dpi >= 122800)
    427     scale = 4;
    428   else if (dpi >= 98304)
    429     scale = 3;
    430   else
    431     scale = 2;
    432   return create_qrcode (scale,
    433                         text,
    434                         text_size);
    435 }
    436 
    437 
    438 void
    439 AG_image_set_from_pixbuf (GtkPicture *img,
    440                           GdkPixbuf *pb)
    441 {
    442   GdkTexture *texture;
    443 
    444   texture = gdk_texture_new_for_pixbuf (pb);
    445   gtk_picture_set_paintable (img,
    446                              GDK_PAINTABLE (texture));
    447   g_object_unref (texture);
    448 }
    449 
    450 
    451 char *
    452 AG_expand_name (const char *name,
    453                 const char *type)
    454 {
    455   static struct
    456   {
    457     const char *type;
    458     const char *suffix;
    459   } type_map [] = {
    460     { .type = "string",
    461       .suffix = "entry" },
    462     { .type = "date",
    463       .suffix = "cal" },
    464     { .type = NULL,
    465       .suffix = NULL }
    466   };
    467   char *data_widget;
    468 
    469   for (unsigned int i = 0; NULL != type_map[i].type; i++)
    470   {
    471     if (0 != strcmp (type_map[i].type,
    472                      type))
    473       continue;
    474     GNUNET_asprintf (&data_widget,
    475                      "%s_%s",
    476                      name,
    477                      type_map[i].suffix);
    478     return data_widget;
    479   }
    480   return NULL;
    481 }
    482 
    483 
    484 /**
    485  * Show or hide the widget watched by @a controller.
    486  *
    487  * The widget to toggle is named by the "name" property the UI file sets
    488  * on the controller.  We deliberately do not connect these signals
    489  * 'swapped' against an "object" attribute: the labels in question are
    490  * defined further down in the UI file, and Cambalache drops such forward
    491  * references when it saves the file, which would silently break the
    492  * case-sensitivity warnings.
    493  *
    494  * @param controller the focus controller of the watched entry
    495  * @param visible true to show the named widget, false to hide it
    496  */
    497 static void
    498 toggle_watched_widget (GtkEventControllerFocus *controller,
    499                        gboolean visible)
    500 {
    501   const char *name;
    502   GtkWidget *w;
    503 
    504   name = gtk_event_controller_get_name (GTK_EVENT_CONTROLLER (controller));
    505   if (NULL == name)
    506   {
    507     GNUNET_break (0);
    508     return;
    509   }
    510   w = GTK_WIDGET (GCG_get_main_window_object (name));
    511   if (NULL == w)
    512   {
    513     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    514                 "Widget `%s' not found, cannot change its visibility!\n",
    515                 name);
    516     return;
    517   }
    518   gtk_widget_set_visible (w,
    519                           visible);
    520 }
    521 
    522 
    523 /**
    524  * Show the watched widget when the entry takes the focus.
    525  *
    526  * @param controller the focus controller of the entry
    527  * @param user_data the builder's current object, unused
    528  */
    529 void
    530 anastasis_gtk_show_on_focus_enter_cb (GtkEventControllerFocus *controller,
    531                                       gpointer user_data)
    532 {
    533   (void) user_data;
    534   toggle_watched_widget (controller,
    535                          TRUE);
    536 }
    537 
    538 
    539 /**
    540  * Hide the watched widget when the entry loses the focus.
    541  *
    542  * @param controller the focus controller of the entry
    543  * @param user_data the builder's current object, unused
    544  */
    545 void
    546 anastasis_gtk_hide_on_focus_leave_cb (GtkEventControllerFocus *controller,
    547                                       gpointer user_data)
    548 {
    549   (void) user_data;
    550   toggle_watched_widget (controller,
    551                          FALSE);
    552 }