anastasis-gtk

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

anastasis-gtk_rows.c (13463B)


      1 /*
      2      This file is part of anastasis-gtk.
      3      Copyright (C) 2025 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  * @file src/anastasis/anastasis-gtk_rows.c
     22  * @brief Rows of the GtkColumnViews and the models holding them
     23  * @author Christian Grothoff
     24  */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include "anastasis-gtk_helper.h"
     27 #include "anastasis-gtk_rows.h"
     28 
     29 
     30 /**
     31  * Key under which the row a cell shows is attached to the widget of that
     32  * cell.  This is how #AG_row_at() finds the row that was clicked.
     33  */
     34 #define ROW_KEY "anastasis-row"
     35 
     36 
     37 /**
     38  * One row of one of our lists.
     39  */
     40 struct _AGRow
     41 {
     42   /**
     43    * We are a GObject.
     44    */
     45   GObject parent_instance;
     46 
     47   /**
     48    * The values of this row.
     49    */
     50   json_t *fields;
     51 
     52   /**
     53    * Image to render this row with, or NULL.
     54    */
     55   GdkPaintable *image;
     56 
     57   /**
     58    * Rows nested under this one, or NULL if there are none.
     59    */
     60   GListStore *children;
     61 };
     62 
     63 G_DEFINE_TYPE (AGRow,
     64                AG_row,
     65                G_TYPE_OBJECT);
     66 
     67 
     68 static void
     69 AG_row_finalize (GObject *object)
     70 {
     71   AGRow *row = AG_ROW (object);
     72 
     73   json_decref (row->fields);
     74   row->fields = NULL;
     75   g_clear_object (&row->image);
     76   g_clear_object (&row->children);
     77   G_OBJECT_CLASS (AG_row_parent_class)->finalize (object);
     78 }
     79 
     80 
     81 static void
     82 AG_row_class_init (AGRowClass *klass)
     83 {
     84   G_OBJECT_CLASS (klass)->finalize = &AG_row_finalize;
     85 }
     86 
     87 
     88 static void
     89 AG_row_init (AGRow *row)
     90 {
     91   row->fields = json_object ();
     92   GNUNET_assert (NULL != row->fields);
     93 }
     94 
     95 
     96 AGRow *
     97 AG_row_new (json_t *fields)
     98 {
     99   AGRow *row;
    100 
    101   GNUNET_assert (json_is_object (fields));
    102   row = g_object_new (AG_TYPE_ROW,
    103                       NULL);
    104   json_decref (row->fields);
    105   row->fields = fields;
    106   return row;
    107 }
    108 
    109 
    110 void
    111 AG_row_set (AGRow *row,
    112             const char *field,
    113             json_t *value)
    114 {
    115   GNUNET_assert (0 ==
    116                  json_object_set_new (row->fields,
    117                                       field,
    118                                       value));
    119 }
    120 
    121 
    122 const char *
    123 AG_row_string (AGRow *row,
    124                const char *field)
    125 {
    126   return json_string_value (json_object_get (row->fields,
    127                                              field));
    128 }
    129 
    130 
    131 bool
    132 AG_row_bool (AGRow *row,
    133              const char *field)
    134 {
    135   return json_boolean_value (json_object_get (row->fields,
    136                                               field));
    137 }
    138 
    139 
    140 uint64_t
    141 AG_row_uint (AGRow *row,
    142              const char *field)
    143 {
    144   return (uint64_t) json_integer_value (json_object_get (row->fields,
    145                                                          field));
    146 }
    147 
    148 
    149 json_t *
    150 AG_row_json (AGRow *row,
    151              const char *field)
    152 {
    153   return json_object_get (row->fields,
    154                           field);
    155 }
    156 
    157 
    158 void
    159 AG_row_set_image (AGRow *row,
    160                   GdkPaintable *image)
    161 {
    162   g_clear_object (&row->image);
    163   row->image = image;
    164 }
    165 
    166 
    167 GdkPaintable *
    168 AG_row_image (AGRow *row)
    169 {
    170   return row->image;
    171 }
    172 
    173 
    174 GListStore *
    175 AG_row_children (AGRow *row)
    176 {
    177   if (NULL == row->children)
    178     row->children = g_list_store_new (AG_TYPE_ROW);
    179   return row->children;
    180 }
    181 
    182 
    183 AGRow *
    184 AG_row_of (gpointer item)
    185 {
    186   if (NULL == item)
    187     return NULL;
    188   if (GTK_IS_TREE_LIST_ROW (item))
    189   {
    190     AGRow *row;
    191 
    192     row = gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (item));
    193     if (NULL == row)
    194       return NULL;
    195     /* we hand out a borrowed reference; the tree list row holds one of
    196        its own for as long as it exists */
    197     g_object_unref (row);
    198     return AG_ROW (row);
    199   }
    200   return AG_ROW (item);
    201 }
    202 
    203 
    204 GListStore *
    205 AG_list_of (GtkBuilder *builder,
    206             const char *name)
    207 {
    208   GObject *model;
    209 
    210   model = gtk_builder_get_object (builder,
    211                                   name);
    212   if (NULL == model)
    213   {
    214     GNUNET_break (0);
    215     return NULL;
    216   }
    217   return G_LIST_STORE (model);
    218 }
    219 
    220 
    221 GListStore *
    222 AG_list (const char *name)
    223 {
    224   GObject *model;
    225 
    226   model = GCG_get_main_window_object (name);
    227   if (NULL == model)
    228   {
    229     GNUNET_break (0);
    230     return NULL;
    231   }
    232   return G_LIST_STORE (model);
    233 }
    234 
    235 
    236 void
    237 AG_list_clear (const char *name)
    238 {
    239   GListStore *list = AG_list (name);
    240 
    241   if (NULL == list)
    242     return;
    243   g_list_store_remove_all (list);
    244 }
    245 
    246 
    247 void
    248 AG_list_add_row (GListStore *list,
    249                  AGRow *row)
    250 {
    251   g_list_store_append (list,
    252                        row);
    253   g_object_unref (row);
    254 }
    255 
    256 
    257 AGRow *
    258 AG_list_append (GListStore *list,
    259                 json_t *fields)
    260 {
    261   AGRow *row = AG_row_new (fields);
    262 
    263   g_list_store_append (list,
    264                        row);
    265   g_object_unref (row);
    266   return row;
    267 }
    268 
    269 
    270 AGRow *
    271 AG_list_add (const char *name,
    272              json_t *fields)
    273 {
    274   GListStore *list = AG_list (name);
    275 
    276   if (NULL == list)
    277   {
    278     json_decref (fields);
    279     return NULL;
    280   }
    281   return AG_list_append (list,
    282                          fields);
    283 }
    284 
    285 
    286 AGRow *
    287 AG_selected (GtkSingleSelection *selection)
    288 {
    289   return AG_row_of (gtk_single_selection_get_selected_item (selection));
    290 }
    291 
    292 
    293 /**
    294  * Return the selection called @a name of the main window.
    295  *
    296  * @param name name of the selection in the UI file
    297  * @return NULL on error
    298  */
    299 static GtkSingleSelection *
    300 main_window_selection (const char *name)
    301 {
    302   GObject *sel;
    303 
    304   sel = GCG_get_main_window_object (name);
    305   if (NULL == sel)
    306   {
    307     GNUNET_break (0);
    308     return NULL;
    309   }
    310   return GTK_SINGLE_SELECTION (sel);
    311 }
    312 
    313 
    314 AGRow *
    315 AG_selected_row (const char *name)
    316 {
    317   GtkSingleSelection *sel = main_window_selection (name);
    318 
    319   if (NULL == sel)
    320     return NULL;
    321   return AG_selected (sel);
    322 }
    323 
    324 
    325 void
    326 AG_select_row (const char *name,
    327                guint position)
    328 {
    329   GtkSingleSelection *sel = main_window_selection (name);
    330 
    331   if (NULL == sel)
    332     return;
    333   gtk_selection_model_select_item (GTK_SELECTION_MODEL (sel),
    334                                    position,
    335                                    TRUE);
    336 }
    337 
    338 
    339 /**
    340  * Search the descendants of @a widget for the cell of a row that covers
    341  * @a y in the coordinates of @a view.
    342  *
    343  * @param widget widget to search below
    344  * @param view the column view @a y refers to
    345  * @param y vertical position to look for
    346  * @return NULL if no cell of any row covers @a y
    347  */
    348 static AGRow *
    349 find_row_at (GtkWidget *widget,
    350              GtkWidget *view,
    351              double y)
    352 {
    353   for (GtkWidget *child = gtk_widget_get_first_child (widget);
    354        NULL != child;
    355        child = gtk_widget_get_next_sibling (child))
    356   {
    357     AGRow *row;
    358     GtkWidget *cell;
    359     graphene_rect_t bounds;
    360 
    361     row = g_object_get_data (G_OBJECT (child),
    362                              ROW_KEY);
    363     if (NULL == row)
    364     {
    365       row = find_row_at (child,
    366                          view,
    367                          y);
    368       if (NULL != row)
    369         return row;
    370       continue;
    371     }
    372     /* our widget does not fill the cell it was put into, so we measure
    373        the cell, which does tile the list without gaps */
    374     cell = gtk_widget_get_parent (child);
    375     if (! gtk_widget_compute_bounds ((NULL != cell) ? cell : child,
    376                                      view,
    377                                      &bounds))
    378       continue;
    379     if ( (y >= bounds.origin.y) &&
    380          (y < bounds.origin.y + bounds.size.height) )
    381       return row;
    382   }
    383   return NULL;
    384 }
    385 
    386 
    387 AGRow *
    388 AG_row_at (GtkWidget *view,
    389            double y)
    390 {
    391   return find_row_at (view,
    392                       view,
    393                       y);
    394 }
    395 
    396 
    397 /**
    398  * Return the @a field of @a row as a number, mapping true to 1.
    399  *
    400  * @param row the row to inspect
    401  * @param field name of the field
    402  * @return 0 if there is no such field
    403  */
    404 static json_int_t
    405 row_number (AGRow *row,
    406             const char *field)
    407 {
    408   json_t *val = AG_row_json (row,
    409                              field);
    410 
    411   if (json_is_boolean (val))
    412     return json_is_true (val) ? 1 : 0;
    413   return json_integer_value (val);
    414 }
    415 
    416 
    417 /**
    418  * Compare two rows by the field named in @a cls.
    419  *
    420  * @param a first row
    421  * @param b second row
    422  * @param cls the `struct AG_SortSpec` of the column being sorted
    423  * @return -1, 0 or 1
    424  */
    425 static gint
    426 compare_rows (gconstpointer a,
    427               gconstpointer b,
    428               gpointer cls)
    429 {
    430   const struct AG_SortSpec *spec = cls;
    431   AGRow *ra = AG_row_of ((gpointer) a);
    432   AGRow *rb = AG_row_of ((gpointer) b);
    433 
    434   if (spec->numeric)
    435   {
    436     json_int_t na = row_number (ra,
    437                                 spec->field);
    438     json_int_t nb = row_number (rb,
    439                                 spec->field);
    440 
    441     if (na < nb)
    442       return -1;
    443     if (na > nb)
    444       return 1;
    445     return 0;
    446   }
    447   {
    448     const char *sa = AG_row_string (ra,
    449                                     spec->field);
    450     const char *sb = AG_row_string (rb,
    451                                     spec->field);
    452 
    453     if (NULL == sa)
    454       sa = "";
    455     if (NULL == sb)
    456       sb = "";
    457     return g_utf8_collate (sa,
    458                            sb);
    459   }
    460 }
    461 
    462 
    463 void
    464 AG_list_make_sortable (GtkBuilder *builder,
    465                        const char *view,
    466                        const char *sort_model,
    467                        const struct AG_SortSpec *specs)
    468 {
    469   GtkColumnView *cv;
    470   GtkSortListModel *slm;
    471 
    472   cv = GTK_COLUMN_VIEW (gtk_builder_get_object (builder,
    473                                                 view));
    474   slm = GTK_SORT_LIST_MODEL (gtk_builder_get_object (builder,
    475                                                      sort_model));
    476   if ( (NULL == cv) ||
    477        (NULL == slm) )
    478   {
    479     GNUNET_break (0);
    480     return;
    481   }
    482   for (unsigned int i = 0; NULL != specs[i].column; i++)
    483   {
    484     GtkColumnViewColumn *col;
    485     GtkSorter *sorter;
    486 
    487     col = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder,
    488                                                           specs[i].column));
    489     if (NULL == col)
    490     {
    491       GNUNET_break (0);
    492       continue;
    493     }
    494     sorter = GTK_SORTER (gtk_custom_sorter_new (&compare_rows,
    495                                                 (gpointer) &specs[i],
    496                                                 NULL));
    497     gtk_column_view_column_set_sorter (col,
    498                                        sorter);
    499     g_object_unref (sorter);
    500   }
    501   /* clicking a column header now sorts the rows the view shows */
    502   gtk_sort_list_model_set_sorter (slm,
    503                                   gtk_column_view_get_sorter (cv));
    504 }
    505 
    506 
    507 /**
    508  * Return the rows nested under @a item, if any.
    509  *
    510  * @param item the parent row
    511  * @param cls NULL
    512  * @return NULL if @a item has no children
    513  */
    514 static GListModel *
    515 create_child_model (gpointer item,
    516                     gpointer cls)
    517 {
    518   AGRow *row = AG_ROW (item);
    519 
    520   (void) cls;
    521   if (NULL == row->children)
    522     return NULL;
    523   return G_LIST_MODEL (g_object_ref (row->children));
    524 }
    525 
    526 
    527 /**
    528  * Columns of the list of authentication methods that can be sorted by
    529  * clicking their header.
    530  */
    531 static const struct AG_SortSpec auth_method_sorts[] = {
    532   { .column = "auth_method_type",
    533     .field = "type" },
    534   { .column = NULL }
    535 };
    536 
    537 
    538 /**
    539  * Columns of the list of secrets that can be sorted by clicking their
    540  * header.
    541  */
    542 static const struct AG_SortSpec secret_sorts[] = {
    543   { .column = "secret_name_column",
    544     .field = "secret_name" },
    545   { .column = "backup_time_column",
    546     .field = "date",
    547     .numeric = true },
    548   { .column = NULL }
    549 };
    550 
    551 
    552 void
    553 AG_lists_setup (void)
    554 {
    555   GtkBuilder *builder = ANASTASIS_GTK_main_loop_get_builder (AG_ml);
    556 
    557   /* the policy review shows a tree, which the UI file cannot express */
    558   {
    559     GListStore *policies;
    560     GtkTreeListModel *tlm;
    561     GtkSingleSelection *sel;
    562 
    563     policies = AG_list ("policy_review_treestore");
    564     sel = main_window_selection ("anastasis_gtk_review_policy_selection");
    565     if ( (NULL == policies) ||
    566          (NULL == sel) )
    567     {
    568       GNUNET_break (0);
    569       return;
    570     }
    571     tlm = gtk_tree_list_model_new (G_LIST_MODEL (g_object_ref (policies)),
    572                                    FALSE, /* we want the GtkTreeListRows */
    573                                    TRUE, /* expand all */
    574                                    &create_child_model,
    575                                    NULL,
    576                                    NULL);
    577     gtk_single_selection_set_model (sel,
    578                                     G_LIST_MODEL (tlm));
    579     g_object_unref (tlm);
    580   }
    581   AG_list_make_sortable (builder,
    582                          "anastasis_gtk_authentication_methods_list",
    583                          "authentication_methods_sorted",
    584                          auth_method_sorts);
    585   AG_list_make_sortable (builder,
    586                          "anastasis_secret_selection_treeview",
    587                          "secret_selection_sorted",
    588                          secret_sorts);
    589 }
    590 
    591 
    592 void
    593 AG_cell_set_row (GtkWidget *widget,
    594                  AGRow *row)
    595 {
    596   g_object_set_data (G_OBJECT (widget),
    597                      ROW_KEY,
    598                      row);
    599 }
    600 
    601 
    602 AGRow *
    603 AG_cell_get_row (GtkWidget *widget)
    604 {
    605   return g_object_get_data (G_OBJECT (widget),
    606                             ROW_KEY);
    607 }
    608 
    609 
    610 /* end of anastasis-gtk_rows.c */