anastasis-gtk

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

commit eb20d9677148594c6e6f94525a15a455bc15482d
parent b02e4d69ddb4aa63c4c971e08caf4ee8e95ae3c7
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 30 Jul 2026 08:55:11 +0200

get rid of deprecated Gtk3 artifacts

Diffstat:
Mcontrib/anastasis-gtk.css | 9+++++++++
Dcontrib/anastasis_gtk_deny_singlefactor.ui | 42------------------------------------------
Mcontrib/anastasis_gtk_edit_providers.ui | 113++++++++++++++++++++++++++++++++-----------------------------------------------
Mcontrib/anastasis_gtk_main_window.ui | 693+++++++++++++++++++++++++++----------------------------------------------------
Dcontrib/anastasis_gtk_open_file_dialog.ui | 106-------------------------------------------------------------------------------
Dcontrib/anastasis_gtk_open_secret_dialog.ui | 100-------------------------------------------------------------------------------
Dcontrib/anastasis_gtk_save_file_dialog.ui | 105-------------------------------------------------------------------------------
Dcontrib/anastasis_gtk_save_secret_dialog.ui | 105-------------------------------------------------------------------------------
Dcontrib/anastasis_gtk_warn_multifactor.ui | 70----------------------------------------------------------------------
Mcontrib/meson.build | 6------
Mpo/POTFILES.in | 9++-------
Msrc/anastasis/anastasis-gtk.c | 7+++++++
Msrc/anastasis/anastasis-gtk_action.c | 422++++++++++++++++++++++++++++++++-----------------------------------------------
Asrc/anastasis/anastasis-gtk_cells.c | 569+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/anastasis/anastasis-gtk_handle-auth-delete-button-clicked.c | 39++++++++++++++++++---------------------
Msrc/anastasis/anastasis-gtk_handle-auth-edit-provider-clicked.c | 244+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/anastasis/anastasis-gtk_handle-continent-selected.c | 26++++++++++++--------------
Msrc/anastasis/anastasis-gtk_handle-country-activated.c | 15+++++++--------
Dsrc/anastasis/anastasis-gtk_handle-country-unselected.c | 50--------------------------------------------------
Msrc/anastasis/anastasis-gtk_handle-main-window-forward-clicked.c | 247+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/anastasis/anastasis-gtk_handle-payqr-selection-changed.c | 37+++++++++++++++++--------------------
Msrc/anastasis/anastasis-gtk_handle-policy-activate.c | 45+++++++++++++++++++++++----------------------
Msrc/anastasis/anastasis-gtk_handle-policy-button.c | 35+++++++++++------------------------
Msrc/anastasis/anastasis-gtk_handle-policy-meta.c | 146+++++++++++++++++--------------------------------------------------------------
Msrc/anastasis/anastasis-gtk_handle-policy-selection-changed.c | 16++++++++--------
Msrc/anastasis/anastasis-gtk_handle-policy-version-changed.c | 46+++++++++++++++++++++++++++++-----------------
Msrc/anastasis/anastasis-gtk_handle-print.c | 82++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/anastasis/anastasis-gtk_handle-secret-buttons.c | 154++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/anastasis/anastasis-gtk_helper.h | 381-------------------------------------------------------------------------------
Msrc/anastasis/anastasis-gtk_io.c | 222++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/anastasis/anastasis-gtk_pe-edit-policy.c | 140+++++++++++++++++++++++++++----------------------------------------------------
Msrc/anastasis/anastasis-gtk_progress.c | 199++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/anastasis/anastasis-gtk_progress.h | 8++++++++
Asrc/anastasis/anastasis-gtk_rows.c | 610++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/anastasis/anastasis-gtk_rows.h | 354+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/anastasis/meson.build | 3++-
Msrc/include/anastasis_gtk_util.h | 14++++++++++----
Msrc/util/builder.c | 16----------------
Msrc/util/nls.c | 5+----
39 files changed, 2827 insertions(+), 2663 deletions(-)

diff --git a/contrib/anastasis-gtk.css b/contrib/anastasis-gtk.css @@ -23,6 +23,15 @@ frame.flat { border-style: none; } +/* The theme gives the cells of a GtkColumnView far more vertical padding than + a GtkCellRenderer had, which would make every one of our lists a third + taller than it used to be. The progress lists ask for their padding through + the margins of their labels instead. */ +columnview > listview > row > cell { + padding-top: 2px; + padding-bottom: 2px; +} + /* AG_mark_invalid_input() adds this class to the identity attribute widget whose contents the reducer rejected. */ .anastasis-invalid, diff --git a/contrib/anastasis_gtk_deny_singlefactor.ui b/contrib/anastasis_gtk_deny_singlefactor.ui @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkMessageDialog" id="anastasis_gtk_deny_singlefactor_dialog"> - <property name="title" translatable="1">Single-factor authentication is not allowed!</property> - <property name="modal">1</property> - <property name="message-type">warning</property> - <property name="text" translatable="1">To keep your secret secure, we require at least two authentication options, and recommend you specify at least three authentication options before proceeding.</property> - <property name="secondary-text" translatable="1">Select &quot;back&quot; to add additional authentication options.</property> - <signal name="response" handler="anastasis_gtk_deny_singlefactor_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="back_button"> - <property name="label" translatable="1">_Back</property> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="tooltip-text" translatable="1">Go back to the previous dialog to add more authentication options.</property> - <property name="margin-start">5</property> - <property name="margin-end">5</property> - <property name="use-underline">1</property> - </object> - </child> - </object> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">back_button</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/anastasis_gtk_edit_providers.ui b/contrib/anastasis_gtk_edit_providers.ui @@ -20,17 +20,16 @@ along with Anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. Author: Christian Grothoff--> <interface> <requires lib="gtk" version="4.0"/> - <object class="GtkListStore" id="provider_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gboolean"/> - <column type="gboolean"/> - <column type="gboolean"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="provider_liststore"> + <property name="item-type">AGRow</property> + </object> + <object class="GtkSortListModel" id="provider_sorted"> + <property name="model">provider_liststore</property> + </object> + <object class="GtkSingleSelection" id="provider_selection"> + <property name="model">provider_sorted</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> </object> <object class="GtkDialog" id="edit_provider_dialog"> <property name="modal">1</property> @@ -96,77 +95,58 @@ Author: Christian Grothoff--> <property name="hexpand">1</property> <property name="margin-top">10</property> <property name="child"> - <object class="GtkViewport"> + <object class="GtkScrolledWindow"> + <property name="height-request">300</property> <property name="child"> - <object class="GtkTreeView" id="provider_tree_view"> - <property name="height-request">300</property> - <property name="model">provider_liststore</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"> - <property name="mode">browse</property> - </object> - </child> + <object class="GtkColumnView" id="provider_tree_view"> + <property name="model">provider_selection</property> <child> - <object class="GtkTreeViewColumn" id="enabled_column"> - <property name="spacing">5</property> + <object class="GtkColumnViewColumn" id="enabled_column"> <property name="title" translatable="1">Use</property> - <property name="reorderable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-column-id">6</property> - <child> - <object class="GtkCellRendererToggle" id="use_toggle"> - <signal name="toggled" handler="provider_toggle_callback" swapped="no"/> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_provider_use"/> + <signal name="bind" handler="AG_cell_bind_provider_use"/> + <signal name="unbind" handler="AG_cell_unbind"/> </object> - <attributes> - <attribute name="sensitive">5</attribute> - <attribute name="activatable">5</attribute> - <attribute name="active">4</attribute> - <attribute name="inconsistent">6</attribute> - </attributes> - </child> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="url_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="url_column"> <property name="title" translatable="1">Provider</property> - <child> - <object class="GtkCellRendererText" id="url_renderer"/> - <attributes> - <attribute name="text">7</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_provider_name"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="liability_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="liability_column"> <property name="title" translatable="1">Liability limit</property> - <child> - <object class="GtkCellRendererText" id="liability_limit_text"/> - <attributes> - <attribute name="visible">5</attribute> - <attribute name="text">3</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_provider_liability"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn"> - <property name="spacing">5</property> + <object class="GtkColumnViewColumn" id="status_column"> + <property name="expand">1</property> <property name="title" translatable="1">Status</property> - <property name="reorderable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-column-id">6</property> - <child> - <object class="GtkCellRendererText" id="status"/> - <attributes> - <attribute name="foreground">2</attribute> - <attribute name="text">1</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_provider_status"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> @@ -268,4 +248,3 @@ Author: Christian Grothoff--> </child> </object> </interface> - diff --git a/contrib/anastasis_gtk_main_window.ui b/contrib/anastasis_gtk_main_window.ui @@ -20,49 +20,8 @@ along with anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. Author: Christian Grothoff, Dennis Neufeld--> <interface> <requires lib="gtk" version="4.0"/> - <object class="GtkListStore" id="anastasis_gtk_backup_progress_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">1. Location &amp; currency</col> - <col id="1">^(CONTINENT|COUNTRY)_SELECTING$</col> - <col id="2" translatable="yes">Where you live will determine which personal information can be used to identify your backup.</col> - </row> - <row> - <col id="0" translatable="yes">2. Personal information</col> - <col id="1">^USER_ATTRIBUTES_COLLECTING$</col> - <col id="2" translatable="yes">Your personal information will not be shared with anyone. But you must provide the same information during recovery.\</col> - </row> - <row> - <col id="0" translatable="yes">3. Authorization methods</col> - <col id="1">^AUTHENTICATIONS_EDITING$</col> - <col id="2" translatable="yes">Specify which methods could be used to authenticate you during recovery.</col> - </row> - <row> - <col id="0" translatable="yes">4. Recovery policies</col> - <col id="1">^POLICIES_REVIEWING$</col> - <col id="2" translatable="yes">These are possible ways how the secret could be recovered. Please review and possibly edit the policies.</col> - </row> - <row> - <col id="0" translatable="yes">5. Enter secret</col> - <col id="1">^SECRET_EDITING$</col> - <col id="2" translatable="yes">Please specify the secret to be backed up.</col> - </row> - <row> - <col id="0" translatable="yes">6. Payment (optional)</col> - <col id="1">^.*_PAYING$</col> - <col id="2" translatable="yes">Some Anastasis providers require payment for their service. Please pay.</col> - </row> - <row> - <col id="0" translatable="yes">7. Backup completed</col> - <col id="1">BACKUP_FINISHED</col> - <col id="2" translatable="yes">Your backup is complete. Please do test the recovery on occasion.</col> - </row> - </data> + <object class="GListStore" id="anastasis_gtk_backup_progress_liststore"> + <property name="item-type">AGRow</property> </object> <object class="GtkAdjustment" id="anastasis_gtk_enter_secret_hadjustment"> <property name="upper">100</property> @@ -87,46 +46,11 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="step-increment">1</property> <property name="page-increment">10</property> </object> - <object class="GtkListStore" id="anastasis_gtk_recovery_progress_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">1. Location &amp; currency</col> - <col id="1">^(CONTINENT|COUNTRY)_SELECTING$</col> - <col id="2" translatable="yes">Where you live will determine which personal information can be used to identify your backup.</col> - </row> - <row> - <col id="0" translatable="yes">2. Personal information</col> - <col id="1">^USER_ATTRIBUTES_COLLECTING$</col> - <col id="2" translatable="yes">Your personal information will not be shared with anyone. But you must provide the same information during recovery.</col> - </row> - <row> - <col id="0" translatable="yes">3. Select secret</col> - <col id="1">^SECRET_SELECTING$</col> - <col id="2" translatable="yes">Please select which secret to recover. You may switch to a different version or provider.</col> - </row> - <row> - <col id="0" translatable="yes">4. Solve challenges</col> - <col id="1">^CHALLENGE_SELECTING$</col> - <col id="2" translatable="yes">Please select an authentication challenge to pass to recover the secret.</col> - </row> - <row> - <col id="0" translatable="yes">5. Secret recovered</col> - <col id="1">^RECOVERY_FINISHED$</col> - <col id="2" translatable="yes">Your secret was successfully recovered.</col> - </row> - </data> + <object class="GListStore" id="anastasis_gtk_recovery_progress_liststore"> + <property name="item-type">AGRow</property> </object> - <object class="GtkListStore" id="authentication_methods_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="guint"/> - </columns> + <object class="GListStore" id="authentication_methods_liststore"> + <property name="item-type">AGRow</property> </object> <object class="GtkAdjustment" id="backup_adjustmenth"> <property name="upper">100</property> @@ -138,46 +62,14 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="step-increment">1</property> <property name="page-increment">10</property> </object> - <object class="GtkListStore" id="backup_provider_liststore"> - <columns> - <column type="gchararray"/> - <column type="guint64"/> - <column type="gchararray"/> - <column type="gboolean"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="backup_provider_liststore"> + <property name="item-type">AGRow</property> </object> - <object class="GtkListStore" id="challenge_status_liststore"> - <columns> - <column type="guint"/> - <column type="gchararray"/> - <column type="gboolean"/> - <column type="gchararray"/> - <column type="GdkPixbuf"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gboolean"/> - <column type="gboolean"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gboolean"/> - <column type="gboolean"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="continent_liststore"> + <property name="item-type">AGRow</property> </object> - <object class="GtkListStore" id="continent_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> - </object> - <object class="GtkListStore" id="country_liststore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="country_liststore"> + <property name="item-type">AGRow</property> </object> <object class="GtkAdjustment" id="expiration_year_adjustment"> <property name="upper">9999</property> @@ -190,19 +82,8 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="can-shrink">0</property> <property name="content-fit">scale-down</property> </object> - <object class="GtkTreeStore" id="policy_review_treestore"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="guint"/> - <column type="gboolean"/> - <column type="guint"/> - <column type="gboolean"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="policy_review_treestore"> + <property name="item-type">AGRow</property> </object> <object class="GtkAdjustment" id="policy_version_adjustment"> <property name="upper">4294967295</property> @@ -237,28 +118,71 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="step-increment">1</property> <property name="page-increment">10</property> </object> - <object class="GtkListStore" id="secret_selection_liststore"> - <columns> - <column type="gchararray"/> - <column type="gint"/> - <column type="gint"/> - <column type="gchararray"/> - <column type="gchararray"/> - <column type="guint64"/> - <column type="gpointer"/> - </columns> + <object class="GListStore" id="secret_selection_liststore"> + <property name="item-type">AGRow</property> </object> <object class="GtkAdjustment" id="secret_selection_v_adjustment"> <property name="upper">100</property> <property name="step-increment">1</property> <property name="page-increment">10</property> </object> - <object class="GtkListStore" id="unpaid_qrcodes_liststore"> - <columns> - <column type="GdkPixbuf"/> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> + <object class="GListStore" id="unpaid_qrcodes_liststore"> + <property name="item-type">AGRow</property> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_backup_progress_tree_selection"> + <property name="model">anastasis_gtk_backup_progress_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_recovery_progress_tree_selection"> + <property name="model">anastasis_gtk_recovery_progress_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_continent_selection"> + <property name="model">continent_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + <signal name="notify::selected" handler="anastasis_gtk_continent_selection_changed_cb"/> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_country_selection"> + <property name="model">country_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + <signal name="notify::selected" handler="anastasis_gtk_country_selection_changed_cb"/> + </object> + <object class="GtkSortListModel" id="authentication_methods_sorted"> + <property name="model">authentication_methods_liststore</property> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_authentication_methods_selection"> + <property name="model">authentication_methods_sorted</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + <signal name="notify::selected" handler="auth_method_selection_changed_cb"/> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_review_policy_selection"> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + </object> + <object class="GtkSingleSelection" id="unpaid_qr_tree_selection"> + <property name="model">unpaid_qrcodes_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + <signal name="notify::selected" handler="unpaid_qr_tree_selection_changed_cb"/> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_backup_complete_selection"> + <property name="model">backup_provider_liststore</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + </object> + <object class="GtkSortListModel" id="secret_selection_sorted"> + <property name="model">secret_selection_liststore</property> + </object> + <object class="GtkSingleSelection" id="anastasis_gtk_secret_selection_treeselection"> + <property name="model">secret_selection_sorted</property> + <property name="autoselect">0</property> + <property name="can-unselect">1</property> + <signal name="notify::selected" handler="anastasis_gtk_secret_selection_treeselection_changed_cb"/> </object> <object class="GtkWindow" id="anastasis_gtk_main_window"> <property name="default-width">700</property> @@ -289,31 +213,19 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="vadjustment">backup_adjustmentv</property> <property name="propagate-natural-width">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_backup_progress_treeview"> - <property name="hadjustment">backup_adjustmenth</property> - <property name="vadjustment">backup_adjustmentv</property> - <property name="model">anastasis_gtk_backup_progress_liststore</property> - <property name="headers-clickable">0</property> - <property name="enable-search">0</property> - <property name="search-column">0</property> - <property name="show-expanders">0</property> - <property name="tooltip-column">2</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_backup_progress_tree_selection"/> - </child> + <object class="GtkColumnView" id="anastasis_gtk_backup_progress_treeview"> + <property name="model">anastasis_gtk_backup_progress_tree_selection</property> <child> - <object class="GtkTreeViewColumn" id="anastasis_gtk_backup_progress_description_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="anastasis_gtk_backup_progress_description_column"> + <property name="expand">1</property> <property name="title" translatable="1">Back up a secret</property> - <child> - <object class="GtkCellRendererText" id="anastasis_gtk_backup_progress_description"> - <property name="ypad">15</property> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_progress"/> + <signal name="bind" handler="AG_cell_bind_progress"/> + <signal name="unbind" handler="AG_cell_unbind"/> </object> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + </property> </object> </child> <child> @@ -337,31 +249,19 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="vadjustment">recovery_adjustmentv</property> <property name="propagate-natural-width">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_recovery_progress_treeview"> - <property name="hadjustment">recovery_adjustmenth</property> - <property name="vadjustment">recovery_adjustmentv</property> - <property name="model">anastasis_gtk_recovery_progress_liststore</property> - <property name="headers-clickable">0</property> - <property name="enable-search">0</property> - <property name="search-column">0</property> - <property name="show-expanders">0</property> - <property name="tooltip-column">2</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_recovery_progress_tree_selection"/> - </child> + <object class="GtkColumnView" id="anastasis_gtk_recovery_progress_treeview"> + <property name="model">anastasis_gtk_recovery_progress_tree_selection</property> <child> - <object class="GtkTreeViewColumn" id="anastasis_gtk_recovery_progress_description_colum"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="anastasis_gtk_recovery_progress_description_colum"> + <property name="expand">1</property> <property name="title" translatable="1">Recover a secret</property> - <child> - <object class="GtkCellRendererText" id="anastasis_gtk_recovery_progress_description"> - <property name="ypad">15</property> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_progress"/> + <signal name="bind" handler="AG_cell_bind_progress"/> + <signal name="unbind" handler="AG_cell_unbind"/> </object> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + </property> </object> </child> <child> @@ -576,26 +476,20 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="hexpand">1</property> <property name="focusable">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_continent_treeview"> + <object class="GtkColumnView" id="anastasis_gtk_continent_treeview"> <property name="focusable">1</property> - <property name="model">continent_liststore</property> - <property name="enable-search">0</property> - <property name="search-column">0</property> - <property name="activate-on-single-click">1</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_continent_selection"> - <signal name="changed" handler="anastasis_gtk_continent_selection_changed_cb" swapped="no"/> - </object> - </child> + <property name="model">anastasis_gtk_continent_selection</property> <child> - <object class="GtkTreeViewColumn" id="continent_column"> + <object class="GtkColumnViewColumn" id="continent_column"> + <property name="expand">1</property> <property name="title" translatable="1">Continent</property> - <child> - <object class="GtkCellRendererText" id="continent_name_cell_renderer"/> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_continent"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> </object> @@ -610,26 +504,20 @@ Author: Christian Grothoff, Dennis Neufeld--> <property name="hexpand">1</property> <property name="focusable">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_country_treeview"> + <object class="GtkColumnView" id="anastasis_gtk_country_treeview"> <property name="focusable">1</property> - <property name="model">country_liststore</property> - <property name="enable-search">0</property> - <property name="search-column">0</property> - <property name="activate-on-single-click">1</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_country_selection"> - <signal name="changed" handler="anastasis_gtk_country_selection_changed_cb" swapped="no"/> - </object> - </child> + <property name="model">anastasis_gtk_country_selection</property> <child> - <object class="GtkTreeViewColumn" id="country_column"> + <object class="GtkColumnViewColumn" id="country_column"> + <property name="expand">1</property> <property name="title" translatable="1">Country</property> - <child> - <object class="GtkCellRendererText" id="country_name_cell_renderer"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_country"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> </object> @@ -1439,43 +1327,32 @@ with &lt;a href=&quot;https://www.ngi.eu/ngi-projects/ledger/&quot;&gt;funding&l <object class="GtkScrolledWindow"> <property name="focusable">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_authentication_methods_list"> + <object class="GtkColumnView" id="anastasis_gtk_authentication_methods_list"> <property name="focusable">1</property> - <property name="model">authentication_methods_liststore</property> - <property name="enable-search">0</property> - <property name="search-column">0</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_authentication_methods_selection"> - <signal name="changed" handler="auth_method_selection_changed_cb" swapped="no"/> - </object> - </child> + <property name="model">anastasis_gtk_authentication_methods_selection</property> <child> - <object class="GtkTreeViewColumn" id="auth_method_type"> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="auth_method_type"> <property name="title" translatable="1">Type</property> - <property name="clickable">1</property> - <property name="reorderable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-column-id">0</property> - <child> - <object class="GtkCellRendererText" id="type"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_auth_type"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="auth_method_visual"> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="auth_method_visual"> <property name="title" translatable="1">Details</property> <property name="expand">1</property> - <child> - <object class="GtkCellRendererText" id="visualization"/> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_auth_details"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> </object> @@ -1619,78 +1496,56 @@ You can also use &quot;back&quot; to add additional authentication methods.</pro <property name="vexpand">1</property> <property name="focusable">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_review_policy_treeview"> + <object class="GtkColumnView" id="anastasis_gtk_review_policy_treeview"> <property name="focusable">1</property> - <property name="model">policy_review_treestore</property> - <property name="search-column">0</property> - <signal name="row-activated" handler="anastasis_gtk_review_policy_treeview_row_activated_cb" swapped="no"/> - <child internal-child="selection"> - <object class="GtkTreeSelection"/> - </child> + <property name="model">anastasis_gtk_review_policy_selection</property> <child> - <object class="GtkTreeViewColumn" id="policy_name_column0"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="policy_name_column0"> <property name="title" translatable="1">Policy</property> - <child> - <object class="GtkCellRendererText" id="policy_name_column"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_policy_name"/> + <signal name="bind" handler="AG_cell_bind_policy_name"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="challenge_type_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="challenge_type_column"> <property name="title" translatable="1">Type</property> - <child> - <object class="GtkCellRendererText" id="challenge_type_column1"/> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_policy_type"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="cost_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="cost_column"> <property name="title" translatable="1">Cost</property> - <child> - <object class="GtkCellRendererText" id="cost_text_column"/> - <attributes> - <attribute name="text">2</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_policy_cost"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="provider_column"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="provider_column"> + <property name="expand">1</property> <property name="title" translatable="1">Provider</property> - <child> - <object class="GtkCellRendererText" id="provider_url_column"/> - <attributes> - <attribute name="text">9</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="expiration_column"> - <property name="visible">0</property> - <property name="spacing">5</property> - <property name="sizing">autosize</property> - <property name="title" translatable="1">Expiration</property> - <child> - <object class="GtkCellRendererText" id="expiration_time_str"/> - <attributes> - <attribute name="text">4</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_policy_provider"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> @@ -1704,6 +1559,7 @@ You can also use &quot;back&quot; to add additional authentication methods.</pro <signal name="key-pressed" handler="anastasis_gtk_review_policy_treeview_key_pressed_cb"/> </object> </child> + <signal name="activate" handler="anastasis_gtk_review_policy_treeview_row_activated_cb"/> </object> </property> <property name="has-frame">1</property> @@ -2177,45 +2033,37 @@ date of your secret&apos;s recovery data.</property> <property name="orientation">vertical</property> <property name="spacing">5</property> <child> - <object class="GtkTreeView" id="unpaid_qr_treeview"> + <object class="GtkColumnView" id="unpaid_qr_treeview"> <property name="vexpand">1</property> <property name="focusable">1</property> - <property name="model">unpaid_qrcodes_liststore</property> - <property name="enable-search">0</property> - <property name="search-column">1</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="unpaid_qr_tree_selection"> - <signal name="changed" handler="unpaid_qr_tree_selection_changed_cb" swapped="no"/> - </object> - </child> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="model">unpaid_qr_tree_selection</property> <child> - <object class="GtkTreeViewColumn"> - <property name="spacing">10</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="unpaid_qr_image_column"> <property name="title" translatable="1">Payment QR Code</property> <property name="expand">1</property> - <child> - <object class="GtkCellRendererPixbuf" id="unpaid_pixbuf"/> - <attributes> - <attribute name="pixbuf">0</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_qr_image"/> + <signal name="bind" handler="AG_cell_bind_qr_image"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="Pay URI"> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="pay_uri_column"> <property name="title" translatable="1">Taler Pay URI</property> - <child> - <object class="GtkCellRendererText" id="pay_uri"/> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_qr_uri"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> - <property name="margin-top">5</property> - <property name="margin-bottom">5</property> </object> </child> </object> @@ -2317,50 +2165,32 @@ date of your secret&apos;s recovery data.</property> <property name="vexpand">1</property> <property name="focusable">1</property> <property name="child"> - <object class="GtkTreeView" id="anastasis_gtk_backup_complete_treeview"> + <object class="GtkColumnView" id="anastasis_gtk_backup_complete_treeview"> <property name="focusable">1</property> - <property name="model">backup_provider_liststore</property> - <property name="search-column">0</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"/> - </child> + <property name="model">anastasis_gtk_backup_complete_selection</property> <child> - <object class="GtkTreeViewColumn"> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="backup_provider_column"> <property name="title" translatable="1">Provider</property> - <child> - <object class="GtkCellRendererText" id="provider_url"/> - <attributes> - <attribute name="text">4</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_backup_provider"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> <child> - <object class="GtkTreeViewColumn"> - <property name="spacing">5</property> - <property name="sizing">autosize</property> + <object class="GtkColumnViewColumn" id="backup_version_column"> + <property name="expand">1</property> <property name="title" translatable="1">Version</property> - <child> - <object class="GtkCellRendererText" id="policy_version"/> - <attributes> - <attribute name="visible">3</attribute> - <attribute name="text">1</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn"> - <property name="visible">0</property> - <property name="title" translatable="1">Expiration time</property> - <child> - <object class="GtkCellRendererText" id="expiration"/> - <attributes> - <attribute name="visible">3</attribute> - <attribute name="text">2</attribute> - </attributes> - </child> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_backup_version"/> + <signal name="unbind" handler="AG_cell_unbind"/> + </object> + </property> </object> </child> </object> @@ -2582,91 +2412,39 @@ explicitly add the provider which you used before for the backup.</property> <property name="focusable">1</property> <property name="hadjustment">secret_selection_h_adjustment</property> <property name="vadjustment">secret_selection_v_adjustment</property> + <property name="has-frame">1</property> <property name="child"> - <object class="GtkViewport" id="anastasis_secret_selection_viewport"> - <property name="hadjustment">secret_selection_h_adjustment</property> - <property name="vadjustment">secret_selection_v_adjustment</property> - <property name="child"> - <object class="GtkTreeView" id="anastasis_secret_selection_treeview"> - <property name="focusable">1</property> - <property name="hadjustment">secret_selection_h_adjustment</property> - <property name="vadjustment">secret_selection_v_adjustment</property> - <property name="model">secret_selection_liststore</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="anastasis_gtk_secret_selection_treeselection"> - <signal name="changed" handler="anastasis_gtk_secret_selection_treeselection_changed_cb" swapped="no"/> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="secret_name_column"> - <property name="resizable">1</property> - <property name="sizing">autosize</property> - <property name="title" translatable="1">Secret name</property> - <property name="clickable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-column-id">3</property> - <child> - <object class="GtkCellRendererText" id="backup_name_renderer"/> - <attributes> - <attribute name="text">3</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="backup_time_column"> - <property name="sizing">fixed</property> - <property name="title" translatable="1">Backup time</property> - <property name="clickable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-column-id">5</property> - <child> - <object class="GtkCellRendererText" id="backup_time_renderer"/> - <attributes> - <attribute name="text">4</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="backup_version_column"> - <property name="visible">0</property> - <property name="title" translatable="1">Version</property> - <property name="clickable">1</property> - <property name="sort-indicator">1</property> - <property name="sort-order">descending</property> - <property name="sort-column-id">1</property> - <child> - <object class="GtkCellRendererText" id="backup_version_renderer"> - <property name="visible">0</property> - </object> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> + <object class="GtkColumnView" id="anastasis_secret_selection_treeview"> + <property name="focusable">1</property> + <property name="model">anastasis_gtk_secret_selection_treeselection</property> + <child> + <object class="GtkColumnViewColumn" id="secret_name_column"> + <property name="title" translatable="1">Secret name</property> + <property name="resizable">1</property> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_secret_name"/> + <signal name="unbind" handler="AG_cell_unbind"/> </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="provider_url_column1"> - <property name="visible">0</property> - <property name="resizable">1</property> - <property name="title" translatable="1">Provider</property> - <property name="clickable">1</property> - <child> - <object class="GtkCellRendererText" id="provider_url_renderer"> - <property name="visible">0</property> - </object> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> + </property> + </object> + </child> + <child> + <object class="GtkColumnViewColumn" id="backup_time_column"> + <property name="expand">1</property> + <property name="title" translatable="1">Backup time</property> + <property name="factory"> + <object class="GtkSignalListItemFactory"> + <signal name="setup" handler="AG_cell_setup_text"/> + <signal name="bind" handler="AG_cell_bind_secret_time"/> + <signal name="unbind" handler="AG_cell_unbind"/> </object> - </child> + </property> </object> - </property> + </child> </object> </property> - <property name="has-frame">1</property> </object> </child> </object> @@ -2837,4 +2615,3 @@ explicitly add the provider which you used before for the backup.</property> <property name="default-widget">anastasis_gtk_main_window_forward_button</property> </object> </interface> - diff --git a/contrib/anastasis_gtk_open_file_dialog.ui b/contrib/anastasis_gtk_open_file_dialog.ui @@ -1,106 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Copyright (C) 2019-2021 Anastasis SARL - -This file is part of anastasis-gtk. - -anastasis-gtk 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. - -anastasis-gtk 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 anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. - -Author: Christian Grothoff--> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkFileFilter" id="anastasis_file_filter"> - <patterns> - <pattern>*.ana</pattern> - </patterns> - </object> - <object class="GtkFileChooserDialog" id="open_file_dialog"> - <property name="modal">1</property> - <property name="destroy-with-parent">1</property> - <property name="filter">anastasis_file_filter</property> - <property name="default-widget">button1</property> - <signal name="response" handler="open_directory_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="button2"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">gtk-cancel</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Cancel</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button2</property> - </object> - </child> - </object> - </property> - </object> - </child> - <child> - <object class="GtkButton" id="button1"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">document-open</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Open</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button1</property> - </object> - </child> - </object> - </property> - </object> - </child> - </object> - </child> - <child> - <placeholder/> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">button2</action-widget> - <action-widget response="-5">button1</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/anastasis_gtk_open_secret_dialog.ui b/contrib/anastasis_gtk_open_secret_dialog.ui @@ -1,100 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Copyright (C) 2019-2021 Anastasis SARL - -This file is part of anastasis-gtk. - -anastasis-gtk 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. - -anastasis-gtk 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 anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. - -Author: Christian Grothoff--> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkFileChooserDialog" id="open_file_dialog"> - <property name="modal">1</property> - <property name="destroy-with-parent">1</property> - <property name="default-widget">button1</property> - <signal name="response" handler="open_secret_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="button2"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">gtk-cancel</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Cancel</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button2</property> - </object> - </child> - </object> - </property> - </object> - </child> - <child> - <object class="GtkButton" id="button1"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">document-open</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Open</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button1</property> - </object> - </child> - </object> - </property> - </object> - </child> - </object> - </child> - <child> - <placeholder/> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">button2</action-widget> - <action-widget response="-5">button1</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/anastasis_gtk_save_file_dialog.ui b/contrib/anastasis_gtk_save_file_dialog.ui @@ -1,105 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Copyright (C) 2019-2021 Anastasis SARL - -This file is part of anastasis-gtk. - -anastasis-gtk 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. - -anastasis-gtk 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 anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. - -Author: Christian Grothoff--> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkFileFilter" id="anastasis_file_filter"> - <patterns> - <pattern>*.ana</pattern> - </patterns> - </object> - <object class="GtkFileChooserDialog" id="save_file_dialog"> - <property name="action">save</property> - <property name="filter">anastasis_file_filter</property> - <property name="default-widget">button2</property> - <signal name="response" handler="save_directory_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="button1"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">gtk-cancel</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Cancel</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button1</property> - </object> - </child> - </object> - </property> - </object> - </child> - <child> - <object class="GtkButton" id="button2"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">document-save</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Save</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button2</property> - </object> - </child> - </object> - </property> - </object> - </child> - </object> - </child> - <child> - <placeholder/> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">button1</action-widget> - <action-widget response="-3">button2</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/anastasis_gtk_save_secret_dialog.ui b/contrib/anastasis_gtk_save_secret_dialog.ui @@ -1,105 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -Copyright (C) 2019-2021 Anastasis SARL - -This file is part of anastasis-gtk. - -anastasis-gtk 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. - -anastasis-gtk 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 anastasis-gtk. If not, see <http://www.gnu.org/licenses/>. - -Author: Christian Grothoff--> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkFileFilter" id="anastasis_file_filter"> - <patterns> - <pattern>*.secret</pattern> - </patterns> - </object> - <object class="GtkFileChooserDialog" id="save_file_dialog"> - <property name="action">save</property> - <property name="filter">anastasis_file_filter</property> - <property name="default-widget">button2</property> - <signal name="response" handler="save_secret_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="button1"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">gtk-cancel</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Cancel</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button1</property> - </object> - </child> - </object> - </property> - </object> - </child> - <child> - <object class="GtkButton" id="button2"> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">document-save</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Save</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">button2</property> - </object> - </child> - </object> - </property> - </object> - </child> - </object> - </child> - <child> - <placeholder/> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">button1</action-widget> - <action-widget response="-3">button2</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/anastasis_gtk_warn_multifactor.ui b/contrib/anastasis_gtk_warn_multifactor.ui @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<interface> - <requires lib="gtk" version="4.0"/> - <object class="GtkMessageDialog" id="anastasis_gtk_confirm_multifactor_dialog"> - <property name="title" translatable="1">Confirm use of few authentication methods.</property> - <property name="modal">1</property> - <property name="message-type">warning</property> - <property name="text" translatable="1">To keep your secret secure, we recommend you specify at least three authentication options before proceeding.</property> - <property name="secondary-text" translatable="1">Select &quot;back&quot; to add additional authentication options.</property> - <property name="focus-widget">next_button</property> - <signal name="response" handler="anastasis_gtk_confirm_multifactor_dialog_response_cb" swapped="no"/> - <child internal-child="content_area"> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkBox"> - <property name="valign">center</property> - <property name="can-focus">0</property> - <property name="homogeneous">1</property> - <property name="spacing">6</property> - <property name="halign">end</property> - <child> - <object class="GtkButton" id="back_button"> - <property name="label" translatable="1">_Back</property> - <property name="focusable">1</property> - <property name="receives-default">1</property> - <property name="tooltip-text" translatable="1">Go back to the previous dialog to add more authentication options.</property> - <property name="margin-start">5</property> - <property name="margin-end">5</property> - <property name="use-underline">1</property> - </object> - </child> - <child> - <object class="GtkButton" id="next_button"> - <property name="focusable">1</property> - <property name="tooltip-text" translatable="1">Proceed without adding additional authentication options.</property> - <property name="margin-start">5</property> - <property name="margin-end">5</property> - <property name="child"> - <object class="GtkBox"> - <property name="spacing">2</property> - <property name="halign">center</property> - <child> - <object class="GtkImage"> - <property name="icon-name">media-skip-forward</property> - </object> - </child> - <child> - <object class="GtkLabel"> - <property name="label" translatable="1">_Next</property> - <property name="use-underline">1</property> - <property name="mnemonic-widget">next_button</property> - </object> - </child> - </object> - </property> - </object> - </child> - </object> - </child> - </object> - </child> - <action-widgets> - <action-widget response="-6">back_button</action-widget> - <action-widget response="-5">next_button</action-widget> - </action-widgets> - </object> -</interface> - diff --git a/contrib/meson.build b/contrib/meson.build @@ -16,17 +16,11 @@ install_data( 'anastasis_gtk_challenge_question.ui', 'anastasis_gtk_challenge_template.ui', 'anastasis_gtk_challenge_totp.ui', - 'anastasis_gtk_deny_singlefactor.ui', 'anastasis_gtk_edit_providers.ui', 'anastasis_gtk_edit_policy.ui', 'anastasis_gtk_main_window.ui', - 'anastasis_gtk_open_file_dialog.ui', - 'anastasis_gtk_open_secret_dialog.ui', 'anastasis_gtk_policy_template.ui', - 'anastasis_gtk_save_file_dialog.ui', - 'anastasis_gtk_save_secret_dialog.ui', 'anastasis_gtk_user_sure.ui', - 'anastasis_gtk_warn_multifactor.ui', 'bandiera_stelle.png', 'checkmark.svgz', 'freeotp.png', diff --git a/po/POTFILES.in b/po/POTFILES.in @@ -2,6 +2,7 @@ src/anastasis/anastasis-gtk_action.c src/anastasis/anastasis-gtk_attributes.c src/anastasis/anastasis-gtk_autocomplete.c src/anastasis/anastasis-gtk.c +src/anastasis/anastasis-gtk_cells.c src/anastasis/anastasis-gtk_dispatch.c src/anastasis/anastasis-gtk_handle-add-provider.c src/anastasis/anastasis-gtk_handle-auth-delete-button-clicked.c @@ -16,7 +17,6 @@ src/anastasis/anastasis-gtk_handle-continent-selected.c src/anastasis/anastasis-gtk_handle-core-secret-changed.c src/anastasis/anastasis-gtk_handle-core-secret-name-changed.c src/anastasis/anastasis-gtk_handle-country-activated.c -src/anastasis/anastasis-gtk_handle-country-unselected.c src/anastasis/anastasis-gtk_handle-expiration-change.c src/anastasis/anastasis-gtk_handle-identity-changed.c src/anastasis/anastasis-gtk_handle-main-window-back-clicked.c @@ -44,6 +44,7 @@ src/anastasis/anastasis-gtk_pe-delete-challenge.c src/anastasis/anastasis-gtk_pe-delete-policy.c src/anastasis/anastasis-gtk_pe-edit-policy.c src/anastasis/anastasis-gtk_progress.c +src/anastasis/anastasis-gtk_rows.c src/anastasis/print.c src/anastasis/test-print.c src/util/builder.c @@ -63,15 +64,9 @@ contrib/anastasis_gtk_challenge_iban.ui contrib/anastasis_gtk_challenge_question.ui contrib/anastasis_gtk_challenge_template.ui contrib/anastasis_gtk_challenge_totp.ui -contrib/anastasis_gtk_deny_singlefactor.ui contrib/anastasis_gtk_edit_policy.ui contrib/anastasis_gtk_edit_providers.ui contrib/anastasis_gtk_main_window.ui -contrib/anastasis_gtk_open_file_dialog.ui -contrib/anastasis_gtk_open_secret_dialog.ui contrib/anastasis_gtk_policy_template.ui -contrib/anastasis_gtk_save_file_dialog.ui -contrib/anastasis_gtk_save_secret_dialog.ui contrib/anastasis_gtk_user_sure.ui -contrib/anastasis_gtk_warn_multifactor.ui contrib/this_stays_private.ui diff --git a/src/anastasis/anastasis-gtk.c b/src/anastasis/anastasis-gtk.c @@ -28,6 +28,8 @@ #include "anastasis_gtk_util.h" #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" +#include "anastasis-gtk_progress.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** @@ -223,11 +225,16 @@ run (void *cls) AG_entry_attributes = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_NO); ANASTASIS_GTK_setup_nls (); + /* the UI files name AGRow as the item type of their models, so the type + has to be registered before GtkBuilder parses them */ + g_type_ensure (AG_TYPE_ROW); if (GNUNET_OK != ANASTASIS_GTK_main_loop_build_window (ANASTASIS_GTK_project_data (), AG_ml, NULL)) return; + AG_lists_setup (); + AG_progress_setup (); AG_cfg = ANASTASIS_GTK_main_loop_get_gtk_configuration (AG_ml); ANASTASIS_GTK_main_loop_get_args (AG_ml, &argc, diff --git a/src/anastasis/anastasis-gtk_action.c b/src/anastasis/anastasis-gtk_action.c @@ -32,6 +32,7 @@ #include "anastasis-gtk_helper.h" #include "anastasis-gtk_handle-identity-changed.h" #include "anastasis-gtk_progress.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> #include <qrencode.h> #include <gdk-pixbuf/gdk-pixbuf.h> @@ -140,17 +141,7 @@ lookup_provider_name (const char *provider_url) static void set_country_selection_next (void) { - GtkTreeView *tv; - GtkTreeSelection *sel; - GtkTreeModel *model; - GtkTreeIter iter; - - tv = GTK_TREE_VIEW (GCG_get_main_window_object ( - "anastasis_gtk_country_treeview")); - sel = gtk_tree_view_get_selection (tv); - if (gtk_tree_selection_get_selected (sel, - &model, - &iter)) + if (NULL != AG_selected_row ("anastasis_gtk_country_selection")) AG_enable_next (); else AG_insensitive ("anastasis_gtk_main_window_forward_button"); @@ -163,19 +154,12 @@ set_country_selection_next (void) static void action_continent_selecting (void) { - GtkListStore *country_liststore = GTK_LIST_STORE ( - GCG_get_main_window_object ("country_liststore")); - AG_hide_all_frames (); - gtk_list_store_clear (country_liststore); + AG_list_clear ("country_liststore"); { - GtkListStore *continent_liststore; json_t *continents; - continent_liststore - = GTK_LIST_STORE ( - GCG_get_main_window_object ("continent_liststore")); - gtk_list_store_clear (continent_liststore); + AG_list_clear ("continent_liststore"); continents = json_object_get (AG_redux_state, "continents"); if (NULL != continents) @@ -204,15 +188,13 @@ action_continent_selecting (void) continue; } - gtk_list_store_insert_with_values (continent_liststore, - NULL, - -1, - AG_CMC_CONTINENT_NAME, - name, - AG_CMC_CONTINENT_NAME_I18N, - dgettext ("anastasis", - name), - -1); + AG_list_add ("continent_liststore", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + name), + GNUNET_JSON_pack_string ("name_i18n", + dgettext ("anastasis", + name)))); GNUNET_JSON_parse_free (spec); } } @@ -246,7 +228,6 @@ action_continent_selecting (void) static void action_country_selecting (void) { - GtkListStore *country_liststore; json_t *countries; const char *selected_country; @@ -256,16 +237,14 @@ action_country_selecting (void) selected_country = json_string_value (json_object_get (AG_redux_state, "selected_country")); - country_liststore = GTK_LIST_STORE ( - GCG_get_main_window_object ("country_liststore")); - gtk_list_store_clear (country_liststore); + AG_list_clear ("country_liststore"); { json_t *country; size_t index; + guint position = 0; json_array_foreach (countries, index, country) { - GtkTreeIter iter; const char *code; const char *name; struct GNUNET_JSON_Specification spec[] = { @@ -286,30 +265,20 @@ action_country_selecting (void) continue; } - gtk_list_store_insert_with_values ( - country_liststore, - &iter, - -1, - AG_CCMC_COUNTRY_NAME, - dgettext ("anastasis", - name), - AG_CCMC_COUNTRY_CODE, - code, - -1); + AG_list_add ("country_liststore", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + dgettext ("anastasis", + name)), + GNUNET_JSON_pack_string ("code", + code))); if ( (NULL != selected_country) && (NULL != code) && (0 == strcmp (code, selected_country)) ) - { - GtkTreeView *tv; - GtkTreeSelection *sel; - - tv = GTK_TREE_VIEW (GCG_get_main_window_object ( - "anastasis_gtk_country_treeview")); - sel = gtk_tree_view_get_selection (tv); - gtk_tree_selection_select_iter (sel, - &iter); - } + AG_select_row ("anastasis_gtk_country_selection", + position); + position++; GNUNET_JSON_parse_free (spec); } } @@ -937,14 +906,11 @@ action_authentications_editing (void) have_auth = false; { - GtkListStore *ls; json_t *ams; size_t index; json_t *am; - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "authentication_methods_liststore")); - gtk_list_store_clear (ls); + AG_list_clear ("authentication_methods_liststore"); ams = json_object_get (AG_redux_state, "authentication_methods"); json_array_foreach (ams, @@ -965,14 +931,14 @@ action_authentications_editing (void) GNUNET_JSON_parse (am, spec, NULL, NULL)); - gtk_list_store_insert_with_values ( - ls, - NULL, - -1, - AG_AMMC_TYPE, type, - AG_AMMC_VISUALIZATION, instructions, - AG_AMMC_INDEX, (guint) index, - -1); + AG_list_add ("authentication_methods_liststore", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + type), + GNUNET_JSON_pack_string ("visualization", + instructions), + GNUNET_JSON_pack_uint64 ("index", + index))); have_auth = true; } } @@ -1078,17 +1044,19 @@ action_policies_reviewing (void) json_t *policies; size_t pindex; json_t *policy; - GtkTreeStore *ts; + GListStore *ts; AG_hide_all_frames (); - ts = GTK_TREE_STORE (GCG_get_main_window_object ("policy_review_treestore")); - gtk_tree_store_clear (ts); + ts = AG_list ("policy_review_treestore"); + if (NULL == ts) + return; + g_list_store_remove_all (ts); policies = json_object_get (AG_redux_state, "policies"); GNUNET_assert (NULL != policies); json_array_foreach (policies, pindex, policy) { - GtkTreeIter piter; + AGRow *prow; json_t *methods; struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_json ("methods", @@ -1107,12 +1075,15 @@ action_policies_reviewing (void) GNUNET_break (0); continue; } - gtk_tree_store_insert_with_values (ts, - &piter, - NULL, /* no parent */ - -1, /* append */ - -1); - + /* the row is only added to the list once its challenges are known, + as that is when the list learns that it can be expanded */ + prow = AG_row_new (GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("policy_index", + pindex), + GNUNET_JSON_pack_bool ("is_challenge", + false), + GNUNET_JSON_pack_string ("expiration", + "N/A"))); summary = NULL; json_array_foreach (methods, mindex, method) { @@ -1144,7 +1115,6 @@ action_policies_reviewing (void) jmethod = json_array_get (jmethods, imethod); { - GtkTreeIter miter; const char *instructions; const char *type; struct GNUNET_JSON_Specification tspec[] = { @@ -1174,30 +1144,27 @@ action_policies_reviewing (void) continue; } provider_name = lookup_provider_name (provider_url); - gtk_tree_store_insert_with_values ( - ts, - &miter, - &piter, /* parent */ - -1, /* append */ - AG_PRMC_POLICY_NAME, - instructions, - AG_PRMC_METHOD_TYPE, - type, - AG_PRMC_COST, - TALER_amount2s (&method_cost), - AG_PRMC_PROVIDER_URL, - provider_url, - AG_PRMC_EXPIRATION_TIME_STR, - "N/A", - AG_PRMC_POLICY_INDEX, - (guint) pindex, - AG_PRMC_IS_CHALLENGE, - TRUE, - AG_PRMC_METHOD_INDEX, - (guint) mindex, - AG_PRMC_PROVIDER_NAME, - provider_name, - -1); + AG_list_append (AG_row_children (prow), + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("policy_name", + instructions), + GNUNET_JSON_pack_string ("method_type", + type), + GNUNET_JSON_pack_string ("cost", + TALER_amount2s ( + &method_cost)), + GNUNET_JSON_pack_string ("provider_url", + provider_url), + GNUNET_JSON_pack_string ("expiration", + "N/A"), + GNUNET_JSON_pack_uint64 ("policy_index", + pindex), + GNUNET_JSON_pack_bool ("is_challenge", + true), + GNUNET_JSON_pack_uint64 ("method_index", + mindex), + GNUNET_JSON_pack_string ("provider_name", + provider_name))); if (NULL == summary) { summary = GNUNET_strdup (type); @@ -1218,27 +1185,15 @@ action_policies_reviewing (void) } if (NULL != summary) { - gtk_tree_store_set (ts, - &piter, - AG_PRMC_POLICY_NAME, summary, - AG_PRMC_EXPIRATION_TIME_STR, - "N/A", - AG_PRMC_POLICY_INDEX, - (guint) pindex, - AG_PRMC_IS_CHALLENGE, - FALSE, - -1); + AG_row_set (prow, + "policy_name", + json_string (summary)); GNUNET_free (summary); } + AG_list_add_row (ts, + prow); GNUNET_JSON_parse_free (pspec); } - { - GtkTreeView *tv; - - tv = GTK_TREE_VIEW (GCG_get_main_window_object ( - "anastasis_gtk_review_policy_treeview")); - gtk_tree_view_expand_all (tv); - } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); @@ -1563,56 +1518,81 @@ action_secret_editing (void) } +/** + * Add a row with the QR code of the payment request @a payto to the list of + * unpaid payment requests. + * + * @param payto the "taler://pay" URI to encode + * @param provider the provider that wants to be paid + */ +static void +add_qrcode (const char *payto, + const char *provider) +{ + GtkWidget *w; + GdkPixbuf *pb; + GListStore *list; + AGRow *row; + + w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); + if (NULL == w) + { + GNUNET_break (0); + return; + } + pb = AG_setup_qrcode (w, + payto, + strlen (payto)); + if (NULL == pb) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _ ("Failed to initialize QR-code pixbuf for `%s'\n"), + payto); + return; + } + list = AG_list ("unpaid_qrcodes_liststore"); + if (NULL == list) + { + g_object_unref (pb); + return; + } + row = AG_row_new (GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("url", + payto), + GNUNET_JSON_pack_string ("provider", + provider))); + /* the row must know its image before it is added to the list, as the + list renders it right away */ + AG_row_set_image (row, + GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pb))); + g_object_unref (pb); + AG_list_add_row (list, + row); +} + + static void action_truths_paying (void) { json_t *pprs; size_t index; json_t *pt; - GtkListStore *ls; AG_hide_all_frames (); - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "unpaid_qrcodes_liststore")); - gtk_list_store_clear (ls); + AG_list_clear ("unpaid_qrcodes_liststore"); pprs = json_object_get (AG_redux_state, "payments"); json_array_foreach (pprs, index, pt) { const char *payto = json_string_value (pt); - GdkPixbuf *pb; - GtkWidget *w; - w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); - if (NULL == w) - { - GNUNET_break (0); - continue; - } if (NULL == payto) { GNUNET_break (0); continue; } - pb = AG_setup_qrcode (w, - payto, - strlen (payto)); - if (NULL == pb) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ ("Failed to initialize QR-code pixbuf for `%s'\n"), - payto); - continue; - } - - gtk_list_store_insert_with_values (ls, - NULL, - -1, /* append */ - AG_UQRMC_QR_IMAGE, pb, - AG_UQRMC_URL, payto, - AG_UQRMC_PROVIDER, "", - -1); - g_object_unref (pb); + add_qrcode (payto, + ""); } { @@ -1648,12 +1628,9 @@ action_policies_paying (void) json_t *pprs; size_t index; json_t *ppr; - GtkListStore *ls; AG_hide_all_frames (); - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "unpaid_qrcodes_liststore")); - gtk_list_store_clear (ls); + AG_list_clear ("unpaid_qrcodes_liststore"); pprs = json_object_get (AG_redux_state, "policy_payment_requests"); json_array_foreach (pprs, index, ppr) @@ -1667,15 +1644,7 @@ action_policies_paying (void) &payto), GNUNET_JSON_spec_end () }; - GdkPixbuf *pb; - GtkWidget *w; - w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); - if (NULL == w) - { - GNUNET_break (0); - continue; - } if (GNUNET_OK != GNUNET_JSON_parse (ppr, spec, @@ -1684,25 +1653,8 @@ action_policies_paying (void) GNUNET_break (0); continue; } - pb = AG_setup_qrcode (w, - payto, - strlen (payto)); - if (NULL == pb) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ ("Failed to initialize QR-code pixbuf for `%s'\n"), - payto); - continue; - } - - gtk_list_store_insert_with_values (ls, - NULL, - -1, /* append */ - AG_UQRMC_QR_IMAGE, pb, - AG_UQRMC_URL, payto, - AG_UQRMC_PROVIDER, provider, - -1); - g_object_unref (pb); + add_qrcode (payto, + provider); } { json_t *args; @@ -1741,15 +1693,12 @@ action_backup_finished (void) json_t *det; json_t *se; const char *url; - GtkListStore *ls; struct GNUNET_TIME_Timestamp mexp; AG_hide_all_frames (); det = json_object_get (AG_redux_state, "success_details"); - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "backup_provider_liststore")); - gtk_list_store_clear (ls); + AG_list_clear ("backup_provider_liststore"); mexp = GNUNET_TIME_UNIT_FOREVER_TS; json_object_foreach (det, url, se) { @@ -1774,21 +1723,18 @@ action_backup_finished (void) } mexp = GNUNET_TIME_timestamp_min (mexp, pexp); - gtk_list_store_insert_with_values ( - ls, - NULL, - -1, /* append */ - AG_BPC_PROVIDER_URL, - url, - AG_BPC_BACKUP_VERSION, - (guint64) version, - AG_BPC_EXPIRATION_TIME_STR, - GNUNET_TIME_timestamp2s (pexp), - AG_BPC_SUCCESS_FLAG, - true, - AG_BPC_PROVIDER_NAME, - lookup_provider_name (url), - -1); + AG_list_add ("backup_provider_liststore", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("provider_url", + url), + GNUNET_JSON_pack_uint64 ("version", + version), + GNUNET_JSON_pack_string ("expiration", + GNUNET_TIME_timestamp2s (pexp)), + GNUNET_JSON_pack_bool ("success", + true), + GNUNET_JSON_pack_string ("provider_name", + lookup_provider_name (url)))); } { struct tm tv; @@ -1968,7 +1914,7 @@ show_challenge_feedback (const char *uuid, * policies again if they are at different providers under versions not * queried up to the cursor. * - * @param cls closure with the `GtkListStore` to update. + * @param cls closure with the list of secrets to update. * @param hcpd hash of the compressed policy document (unique per policy) * @param provider_url which provider claims to have this policy * @param version version of the policy at this provider @@ -1988,34 +1934,30 @@ expand_policy_list (void *cls, const char *secret_name, const json_t *providers) { - GtkListStore *ls = cls; - GtkTreeIter iter; + GListStore *ls = cls; bool have_first; - have_first = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ls), - &iter); - gtk_list_store_insert_with_values ( + have_first = (0 != g_list_model_get_n_items (G_LIST_MODEL (ls))); + AG_list_append ( ls, - &iter, - -1, /* append */ - AG_SSMC_PROVIDER_URL, provider_url, - AG_SSMC_POLICY_VERSION, (gint) version, - AG_SSMC_ATTRIBUTE_MASK, (gint) attribute_mask, - AG_SSMC_SECRET_NAME, secret_name, - AG_SSMC_POLICY_DATE_STRING, GNUNET_TIME_timestamp2s (server_time), - AG_SSMC_POLICY_DATE_NUMERIC, (guint64) server_time.abs_time.abs_value_us, - AG_SSMC_POLICY_PROVIDER_JSON, (gpointer) providers, - -1); + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("provider_url", + provider_url), + GNUNET_JSON_pack_uint64 ("version", + version), + GNUNET_JSON_pack_uint64 ("attribute_mask", + attribute_mask), + GNUNET_JSON_pack_string ("secret_name", + secret_name), + GNUNET_JSON_pack_string ("date_string", + GNUNET_TIME_timestamp2s (server_time)), + GNUNET_JSON_pack_uint64 ("date", + server_time.abs_time.abs_value_us), + GNUNET_JSON_pack_array_incref ("providers", + (json_t *) providers))); if (! have_first) - { - GtkTreeSelection *selection; - - selection = GTK_TREE_SELECTION ( - GCG_get_main_window_object ( - "anastasis_gtk_secret_selection_treeselection")); - gtk_tree_selection_select_iter (selection, - &iter); - } + AG_select_row ("anastasis_gtk_secret_selection_treeselection", + 0); } @@ -2255,14 +2197,13 @@ long_poll_providers_action_cb (void *cls, static void begin_discovery (void) { - GtkListStore *ls; + GListStore *ls; bool have_providers; - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "secret_selection_liststore")); + ls = AG_list ("secret_selection_liststore"); GNUNET_assert (NULL != ls); if (NULL == AG_pd) - gtk_list_store_clear (ls); + g_list_store_remove_all (ls); have_providers = sync_providers (); if (NULL == AG_pd) AG_pd = ANASTASIS_policy_discovery_start (AG_redux_state, @@ -2744,15 +2685,12 @@ action_challenge_paying (void) { json_t *pprs; json_t *ppr; - GtkListStore *ls; const char *uuid; bool found = false; const char *ps; AG_hide_all_frames (); - ls = GTK_LIST_STORE (GCG_get_main_window_object ( - "unpaid_qrcodes_liststore")); - gtk_list_store_clear (ls); + AG_list_clear ("unpaid_qrcodes_liststore"); pprs = json_object_get (AG_redux_state, "challenge_feedback"); json_object_foreach (pprs, uuid, ppr) @@ -2775,8 +2713,6 @@ action_challenge_paying (void) &ps), GNUNET_JSON_spec_end () }; - GdkPixbuf *pb; - GtkWidget *w; if (GNUNET_OK != GNUNET_JSON_parse (ppr, @@ -2795,22 +2731,8 @@ action_challenge_paying (void) "payment")) continue; found = true; - w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); - pb = AG_setup_qrcode (w, - payto, - strlen (payto)); - if (NULL == pb) - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ ("Failed to initialize QR-code pixbuf for `%s'\n"), - payto); - gtk_list_store_insert_with_values (ls, - NULL, - -1, /* append */ - AG_UQRMC_QR_IMAGE, pb, - AG_UQRMC_URL, payto, - AG_UQRMC_PROVIDER, provider, - -1); - g_object_unref (pb); + add_qrcode (payto, + (NULL != provider) ? provider : ""); break; } diff --git a/src/anastasis/anastasis-gtk_cells.c b/src/anastasis/anastasis-gtk_cells.c @@ -0,0 +1,569 @@ +/* + This file is part of anastasis-gtk. + Copyright (C) 2025 Anastasis SARL + + Anastasis 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, or (at your + option) any later version. + + Anastasis 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 Anastasis; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + * @file src/anastasis/anastasis-gtk_cells.c + * @brief Widgets rendering the cells of our GtkColumnViews + * @author Christian Grothoff + */ +#include <gnunet/gnunet_util_lib.h> +#include "anastasis_gtk_util.h" +#include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" + + +/** + * Padding above and below the rows of the two progress lists. + */ +#define PROGRESS_PADDING 15 + + +/** + * Return the row the cell of @a item shows and remember it on the + * widget of that cell, so that a right-click can find it again. + * + * @param item the cell being bound + * @return NULL if @a item shows no row + */ +static AGRow * +bind_row (GtkListItem *item) +{ + AGRow *row = AG_row_of (gtk_list_item_get_item (item)); + GtkWidget *child = gtk_list_item_get_child (item); + + if (NULL != child) + AG_cell_set_row (child, + row); + return row; +} + + +/** + * Set the label of the cell of @a item to the @a field of its row. + * + * @param item the cell being bound + * @param field field of the row to show + */ +static void +bind_text (GtkListItem *item, + const char *field) +{ + AGRow *row = bind_row (item); + const char *val = NULL; + + if (NULL != row) + val = AG_row_string (row, + field); + gtk_label_set_text (GTK_LABEL (gtk_list_item_get_child (item)), + (NULL != val) ? val : ""); +} + + +/** + * Define the "bind" handler @a name of a column showing the string + * @a field of the rows. The columns of our lists differ only in the + * field they show, but the UI files must name a distinct handler for + * each of them. + * + * @param name name of the handler to define + * @param field field of the row the column shows + */ +#define TEXT_CELL(name, field) \ + void \ + name (GtkSignalListItemFactory *f, \ + GtkListItem *item, \ + gpointer user_data) \ + { \ + (void) f; \ + (void) user_data; \ + bind_text (item, \ + field); \ + } + + +/** + * Create the label rendering a cell of a text column. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_setup_text (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *label; + + (void) f; + (void) user_data; + label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (label), + 0.0); + gtk_widget_set_halign (label, + GTK_ALIGN_START); + gtk_list_item_set_child (item, + label); +} + + +/** + * Forget the row a cell showed once it is no longer shown. + * + * @param f the factory of the column + * @param item the cell that was unbound + * @param user_data the builder + */ +void +AG_cell_unbind (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *child = gtk_list_item_get_child (item); + + (void) f; + (void) user_data; + if (NULL != child) + AG_cell_set_row (child, + NULL); +} + + +/* ****************** progress lists ******************* */ + + +/** + * Create the label rendering a step of one of the progress lists. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_setup_progress (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *label; + + AG_cell_setup_text (f, + item, + user_data); + label = gtk_list_item_get_child (item); + gtk_widget_set_margin_top (label, + PROGRESS_PADDING); + gtk_widget_set_margin_bottom (label, + PROGRESS_PADDING); +} + + +/** + * Show the description of a step of one of the progress lists, using the + * long explanation as the tooltip. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_progress (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + AGRow *row; + + (void) f; + (void) user_data; + bind_text (item, + "description"); + row = AG_row_of (gtk_list_item_get_item (item)); + if (NULL == row) + return; + gtk_widget_set_tooltip_text (gtk_list_item_get_child (item), + AG_row_string (row, + "tooltip")); +} + + +/* ************ continents, countries, methods ************* */ + + +TEXT_CELL (AG_cell_bind_continent, "name_i18n") +TEXT_CELL (AG_cell_bind_country, "name") +TEXT_CELL (AG_cell_bind_auth_type, "type") +TEXT_CELL (AG_cell_bind_auth_details, "visualization") + + +/* ******************* policy review ******************** */ + + +/** + * Create the expander and label rendering the first column of the policy + * review, where the challenges are nested under their policy. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_setup_policy_name (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *expander; + GtkWidget *label; + + (void) f; + (void) user_data; + label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (label), + 0.0); + gtk_widget_set_halign (label, + GTK_ALIGN_START); + expander = gtk_tree_expander_new (); + gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), + label); + gtk_list_item_set_child (item, + expander); +} + + +/** + * Show the name of a policy, or the instructions of a challenge. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_policy_name (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkTreeExpander *expander; + GtkWidget *label; + AGRow *row; + const char *name = NULL; + + (void) f; + (void) user_data; + expander = GTK_TREE_EXPANDER (gtk_list_item_get_child (item)); + gtk_tree_expander_set_list_row (expander, + GTK_TREE_LIST_ROW ( + gtk_list_item_get_item (item))); + row = bind_row (item); + if (NULL != row) + name = AG_row_string (row, + "policy_name"); + label = gtk_tree_expander_get_child (expander); + gtk_label_set_text (GTK_LABEL (label), + (NULL != name) ? name : ""); +} + + +TEXT_CELL (AG_cell_bind_policy_type, "method_type") +TEXT_CELL (AG_cell_bind_policy_cost, "cost") +TEXT_CELL (AG_cell_bind_policy_provider, "provider_name") + + +/* ****************** payment QR codes ******************* */ + + +/** + * Create the picture rendering a QR code. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_setup_qr_image (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *picture; + + (void) f; + (void) user_data; + picture = gtk_picture_new (); + /* render the QR code at its natural size */ + gtk_picture_set_can_shrink (GTK_PICTURE (picture), + FALSE); + gtk_picture_set_content_fit (GTK_PICTURE (picture), + GTK_CONTENT_FIT_SCALE_DOWN); + /* a GtkCellRendererPixbuf centred its image in the cell */ + gtk_widget_set_halign (picture, + GTK_ALIGN_CENTER); + gtk_list_item_set_child (item, + picture); +} + + +/** + * Show the QR code of a payment request. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_qr_image (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + AGRow *row = bind_row (item); + + (void) f; + (void) user_data; + gtk_picture_set_paintable (GTK_PICTURE (gtk_list_item_get_child (item)), + (NULL != row) + ? AG_row_image (row) + : NULL); +} + + +TEXT_CELL (AG_cell_bind_qr_uri, "url") + + +/* ***************** completed backup ******************* */ + + +TEXT_CELL (AG_cell_bind_backup_provider, "provider_name") + + +/** + * Show the version of the policy stored at a provider, but only for the + * providers the backup actually reached. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_backup_version (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + AGRow *row = bind_row (item); + GtkWidget *label = gtk_list_item_get_child (item); + char *version; + + (void) f; + (void) user_data; + if (NULL == row) + { + gtk_widget_set_visible (label, + FALSE); + return; + } + GNUNET_asprintf (&version, + "%llu", + (unsigned long long) AG_row_uint (row, + "version")); + gtk_label_set_text (GTK_LABEL (label), + version); + GNUNET_free (version); + gtk_widget_set_visible (label, + AG_row_bool (row, + "success")); +} + + +/* ***************** secret selection ******************* */ + + +TEXT_CELL (AG_cell_bind_secret_name, "secret_name") +TEXT_CELL (AG_cell_bind_secret_time, "date_string") + + +/* ****************** provider list ******************** */ + + +/** + * The user toggled whether a provider is to be used. + * + * @param button the check button of the row + * @param user_data NULL + */ +static void +provider_use_toggled_cb (GtkCheckButton *button, + gpointer user_data) +{ + AGRow *row; + bool active; + + (void) user_data; + row = AG_cell_get_row (GTK_WIDGET (button)); + if (NULL == row) + return; /* not bound to a row (yet) */ + active = gtk_check_button_get_active (button); + if (active == AG_row_bool (row, + "enabled")) + return; /* we set the state ourselves while binding */ + AG_row_set (row, + "enabled", + json_boolean (active)); +} + + +/** + * Create the check button with which a provider is enabled or disabled. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_setup_provider_use (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *check; + + (void) f; + (void) user_data; + check = gtk_check_button_new (); + gtk_widget_set_halign (check, + GTK_ALIGN_CENTER); + g_signal_connect (check, + "toggled", + G_CALLBACK (&provider_use_toggled_cb), + NULL); + gtk_list_item_set_child (item, + check); +} + + +/** + * Show whether a provider is to be used. Providers we could not talk to + * cannot be enabled, which is shown as an indeterminate state. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_provider_use (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + GtkCheckButton *check; + AGRow *row; + + (void) f; + (void) user_data; + /* the row must be known before we change the state, or the "toggled" + handler cannot tell our own change from the user's */ + row = bind_row (item); + check = GTK_CHECK_BUTTON (gtk_list_item_get_child (item)); + if (NULL == row) + return; + gtk_check_button_set_active (check, + AG_row_bool (row, + "enabled")); + gtk_check_button_set_inconsistent (check, + ! AG_row_bool (row, + "sensitive")); + gtk_widget_set_sensitive (GTK_WIDGET (check), + AG_row_bool (row, + "sensitive")); +} + + +TEXT_CELL (AG_cell_bind_provider_name, "name") + + +/** + * Show the liability limit of a provider, which we only know for the + * providers we could talk to. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_provider_liability (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + AGRow *row; + + (void) f; + (void) user_data; + bind_text (item, + "liability"); + row = AG_row_of (gtk_list_item_get_item (item)); + gtk_widget_set_visible (gtk_list_item_get_child (item), + (NULL != row) && + AG_row_bool (row, + "sensitive")); +} + + +/** + * Show the status of a provider in the colour that goes with it. + * + * @param f the factory of the column + * @param item the cell to fill + * @param user_data the builder + */ +void +AG_cell_bind_provider_status (GtkSignalListItemFactory *f, + GtkListItem *item, + gpointer user_data) +{ + AGRow *row = bind_row (item); + GtkLabel *label = GTK_LABEL (gtk_list_item_get_child (item)); + const char *status; + const char *color; + + (void) f; + (void) user_data; + status = (NULL != row) + ? AG_row_string (row, + "status") + : NULL; + if (NULL == status) + { + gtk_label_set_text (label, + ""); + return; + } + color = AG_row_string (row, + "color"); + if (NULL == color) + { + gtk_label_set_text (label, + status); + return; + } + { + char *markup; + + markup = g_markup_printf_escaped ("<span foreground=\"%s\">%s</span>", + color, + status); + gtk_label_set_markup (label, + markup); + g_free (markup); + } +} + + +/* end of anastasis-gtk_cells.c */ diff --git a/src/anastasis/anastasis-gtk_handle-auth-delete-button-clicked.c b/src/anastasis/anastasis-gtk_handle-auth-delete-button-clicked.c @@ -26,6 +26,7 @@ #include <gnunet/gnunet_util_lib.h> #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> @@ -40,28 +41,19 @@ anastasis_gtk_authentication_method_delete_button_clicked_cb (GObject *object, gpointer user_data) { json_t *args; - guint index; - GtkTreeSelection *ts; - GtkTreeModel *model; - GtkTreeIter iter; + AGRow *row; - ts = GTK_TREE_SELECTION (GCG_get_main_window_object ( - "anastasis_gtk_authentication_methods_selection")); - if (! gtk_tree_selection_get_selected (ts, - &model, - &iter)) + row = AG_selected_row ("anastasis_gtk_authentication_methods_selection"); + if (NULL == row) { GNUNET_break (0); return; } - gtk_tree_model_get (model, - &iter, - AG_AMMC_INDEX, &index, - -1); AG_freeze (); args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("authentication_method", - index)); + AG_row_uint (row, + "index"))); AG_ra = ANASTASIS_redux_action (AG_redux_state, "delete_authentication", args, @@ -71,17 +63,22 @@ anastasis_gtk_authentication_method_delete_button_clicked_cb (GObject *object, } +/** + * Callback invoked if the selected authentication method changed. + * + * @param selection the selection of the authentication method list + * @param pspec unused + * @param user_data unused + */ void auth_method_selection_changed_cb ( - GtkTreeSelection *treeselection, + GtkSingleSelection *selection, + GParamSpec *pspec, gpointer user_data) { - GtkTreeModel *model; - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected (treeselection, - &model, - &iter)) + (void) pspec; + (void) user_data; + if (NULL != AG_selected (selection)) AG_sensitive ("anastasis_gtk_authentication_method_delete_button"); else AG_insensitive ("anastasis_gtk_authentication_method_delete_button"); diff --git a/src/anastasis/anastasis-gtk_handle-auth-edit-provider-clicked.c b/src/anastasis/anastasis-gtk_handle-auth-edit-provider-clicked.c @@ -29,6 +29,7 @@ #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_handle-main-window-forward-clicked.h" +#include "anastasis-gtk_rows.h" #include <sys/types.h> #include <sys/wait.h> #include <jansson.h> @@ -111,13 +112,13 @@ xdg_open (const char *url) /** * Return the provider the user right-clicked on. * - * @param tree_view the tree view the context menu belongs to + * @param view the list the context menu belongs to * @return the context, NULL if there is none */ static struct MenuContext * -current_context (gpointer tree_view) +current_context (gpointer view) { - return g_object_get_data (G_OBJECT (tree_view), + return g_object_get_data (G_OBJECT (view), "anastasis-menu-context"); } @@ -182,50 +183,11 @@ view_privacy_policy (GSimpleAction *action, } -void -provider_toggle_callback (GtkCellRendererToggle *cell, - gchar *path_str, - gpointer user_data) -{ - GtkBuilder *builder = GTK_BUILDER (user_data); - GtkTreeIter iter; - GtkTreePath *path; - gboolean enabled; - GtkTreeModel *tm; - - tm = GTK_TREE_MODEL (gtk_builder_get_object (builder, - "provider_liststore")); - if (NULL == tm) - { - GNUNET_break (0); - return; - } - path = gtk_tree_path_new_from_string (path_str); - if (! gtk_tree_model_get_iter (tm, - &iter, - path)) - { - GNUNET_break (0); - return; - } - gtk_tree_model_get (tm, - &iter, - AG_PMC_PROVIDER_ENABLED, &enabled, - -1); - enabled = ! enabled; - gtk_list_store_set (GTK_LIST_STORE (tm), - &iter, - AG_PMC_PROVIDER_ENABLED, enabled, - -1); - gtk_tree_path_free (path); -} - - /** * Return the context menu of @a tv, creating it on first use. A single - * popover is reused for the lifetime of the tree view. + * popover is reused for the lifetime of the list. * - * @param tv the tree view + * @param tv the list * @return the popover to show */ static GtkPopover * @@ -283,7 +245,7 @@ get_popover (GtkWidget *tv) /** - * User right-clicked on the tree view: show the context menu that lets them + * User right-clicked on the list: show the context menu that lets them * view the provider's privacy policy or terms of service. * * @param gesture the click gesture, restricted to button 3 by the UI file @@ -299,57 +261,21 @@ provider_tree_view_pressed_cb (GtkGestureClick *gesture, gdouble y, gpointer user_data) { - GtkBuilder *builder = GTK_BUILDER (user_data); GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); - GtkTreeView *tv = GTK_TREE_VIEW (widget); - GtkTreeModel *tm; - GtkTreePath *path; - GtkTreeIter iter; struct MenuContext *ctx; - int bx; - int by; + AGRow *row; (void) n_press; - tm = GTK_TREE_MODEL (gtk_builder_get_object (builder, - "provider_liststore")); - if (NULL == tm) - { - GNUNET_break (0); - return; - } - gtk_tree_view_convert_widget_to_bin_window_coords (tv, - (int) x, - (int) y, - &bx, - &by); - if (! gtk_tree_view_get_path_at_pos (tv, - bx, - by, - &path, - NULL, - NULL, - NULL)) - { - /* nothing selected */ - return; - } - if (! gtk_tree_model_get_iter (tm, - &iter, - path)) - { - /* not sure how we got a path but no iter... */ - GNUNET_break (0); - gtk_tree_path_free (path); - return; - } - gtk_tree_path_free (path); + (void) user_data; + row = AG_row_at (widget, + y); + if (NULL == row) + return; /* the user clicked past the last provider */ ctx = GNUNET_new (struct MenuContext); - gtk_tree_model_get (tm, - &iter, - AG_PMC_PROVIDER_URL, &ctx->url, - -1); - /* the tree view owns the context of the row currently pointed at; the + ctx->url = GNUNET_strdup (AG_row_string (row, + "url")); + /* the list owns the context of the row currently pointed at; the previous one is released here */ g_object_set_data_full (G_OBJECT (widget), "anastasis-menu-context", @@ -382,27 +308,32 @@ url_add_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkBuilder *builder = GTK_BUILDER (user_data); - GtkListStore *ls; + GListStore *ls; GtkEntry *entry; const char *url; - ls = GTK_LIST_STORE (gtk_builder_get_object (builder, - "provider_liststore")); + ls = AG_list_of (builder, + "provider_liststore"); GNUNET_assert (NULL != ls); entry = GTK_ENTRY (gtk_builder_get_object (builder, "url_entry")); url = gtk_editable_get_text (GTK_EDITABLE (entry)); - gtk_list_store_insert_with_values (ls, - NULL, - -1, - AG_PMC_PROVIDER_URL, url, - AG_PMC_PROVIDER_STATUS, _ ("new"), - AG_PMC_PROVIDER_STATUS_COLOR, "blue", - AG_PMC_PROVIDER_ENABLED, true, - AG_PMC_PROVIDER_SENSITIVE, false, - AG_PMC_PROVIDER_NOT_SENSITIVE, true, - AG_PMC_PROVIDER_NAME, url, - -1); + AG_list_append (ls, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("url", + url), + GNUNET_JSON_pack_string ("status", + _ ("new")), + GNUNET_JSON_pack_string ("color", + "blue"), + GNUNET_JSON_pack_bool ("enabled", + true), + GNUNET_JSON_pack_bool ("sensitive", + false), + GNUNET_JSON_pack_bool ("not_sensitive", + true), + GNUNET_JSON_pack_string ("name", + url))); gtk_editable_set_text (GTK_EDITABLE (entry), ""); } @@ -449,9 +380,9 @@ edit_provider_dialog_response_cb (GtkDialog *dialog, gpointer user_data) { GtkBuilder *builder = GTK_BUILDER (user_data); - GtkTreeModel *tm; - GtkTreeIter iter; + GListModel *lm; json_t *args; + guint n; if (GTK_RESPONSE_APPLY != response_id) { @@ -459,38 +390,33 @@ edit_provider_dialog_response_cb (GtkDialog *dialog, g_object_unref (G_OBJECT (builder)); return; } - tm = GTK_TREE_MODEL (gtk_builder_get_object (builder, - "provider_liststore")); - if (NULL == tm) + lm = G_LIST_MODEL (AG_list_of (builder, + "provider_liststore")); + if (NULL == lm) { GNUNET_break (0); return; } args = json_object (); - if (gtk_tree_model_get_iter_first (tm, - &iter)) - do { - gchar *url; - gboolean enabled; - - gtk_tree_model_get (tm, - &iter, - AG_PMC_PROVIDER_URL, &url, - AG_PMC_PROVIDER_ENABLED, &enabled, - -1); - GNUNET_assert (0 == - json_object_set_new ( - args, - url, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("status", - enabled - ? "not-contacted" - : "disabled")))); - g_free (url); - } - while (gtk_tree_model_iter_next (tm, - &iter)); + n = g_list_model_get_n_items (lm); + for (guint i = 0; i < n; i++) + { + AGRow *row = g_list_model_get_item (lm, + i); + + GNUNET_assert (0 == + json_object_set_new ( + args, + AG_row_string (row, + "url"), + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + AG_row_bool (row, + "enabled") + ? "not-contacted" + : "disabled")))); + g_object_unref (row); + } gtk_window_destroy (GTK_WINDOW (dialog)); g_object_unref (G_OBJECT (builder)); AG_freeze (); @@ -513,9 +439,18 @@ void anastasis_gtk_edit_provider_list_clicked_cb (GtkButton *object, gpointer user_data) { + static const struct AG_SortSpec sorts[] = { + { .column = "enabled_column", + .field = "not_sensitive", + .numeric = true }, + { .column = "status_column", + .field = "not_sensitive", + .numeric = true }, + { .column = NULL } + }; GtkWidget *ad; GtkBuilder *builder; - GtkListStore *ls; + GListStore *ls; json_t *providers; builder = ANASTASIS_GTK_get_new_builder ( @@ -527,8 +462,12 @@ anastasis_gtk_edit_provider_list_clicked_cb (GtkButton *object, GNUNET_break (0); return; } - ls = GTK_LIST_STORE (gtk_builder_get_object (builder, - "provider_liststore")); + AG_list_make_sortable (builder, + "provider_tree_view", + "provider_sorted", + sorts); + ls = AG_list_of (builder, + "provider_liststore"); providers = json_object_get (AG_redux_state, "authentication_providers"); { @@ -624,20 +563,27 @@ anastasis_gtk_edit_provider_list_clicked_cb (GtkButton *object, } if (NULL == name) name = url; - gtk_list_store_insert_with_values ( + AG_list_append ( ls, - NULL, - -1, - AG_PMC_PROVIDER_URL, url, - AG_PMC_PROVIDER_STATUS, status_str, - AG_PMC_PROVIDER_STATUS_COLOR, color, - AG_PMC_PROVIDER_LIABILITY_LIMIT, ll_s, - AG_PMC_PROVIDER_ENABLED, (0 != strcmp (status, - "disabled")), - AG_PMC_PROVIDER_SENSITIVE, sensitive, - AG_PMC_PROVIDER_NOT_SENSITIVE, ! sensitive, - AG_PMC_PROVIDER_NAME, name, - -1); + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("url", + url), + GNUNET_JSON_pack_string ("status", + status_str), + GNUNET_JSON_pack_string ("color", + color), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("liability", + ll_s)), + GNUNET_JSON_pack_bool ("enabled", + 0 != strcmp (status, + "disabled")), + GNUNET_JSON_pack_bool ("sensitive", + sensitive), + GNUNET_JSON_pack_bool ("not_sensitive", + ! sensitive), + GNUNET_JSON_pack_string ("name", + name))); GNUNET_free (status_str); } } diff --git a/src/anastasis/anastasis-gtk_handle-continent-selected.c b/src/anastasis/anastasis-gtk_handle-continent-selected.c @@ -26,29 +26,31 @@ #include <gnunet/gnunet_util_lib.h> #include "anastasis-gtk_helper.h" #include "anastasis-gtk_action.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** * Callback invoked if a continent is selected. * - * @param selection A GtkTreeSelection. + * @param selection the selection of the continent list + * @param pspec unused * @param user_data user data set when the signal handler was connected. */ void -anastasis_gtk_continent_selection_changed_cb (GtkTreeSelection *treeselection, +anastasis_gtk_continent_selection_changed_cb (GtkSingleSelection *selection, + GParamSpec *pspec, gpointer user_data) { - GtkTreeModel *model; - GtkTreeIter iter; - gchar *continent_name; + AGRow *row; json_t *arguments; + (void) pspec; + (void) user_data; if (AG_in_action) return; - if (! gtk_tree_selection_get_selected (treeselection, - &model, - &iter)) + row = AG_selected (selection); + if (NULL == row) { AG_freeze (); AG_ra = ANASTASIS_redux_action (AG_redux_state, @@ -58,14 +60,10 @@ anastasis_gtk_continent_selection_changed_cb (GtkTreeSelection *treeselection, NULL); return; } - gtk_tree_model_get (model, - &iter, - AG_CMC_CONTINENT_NAME, &continent_name, - -1); arguments = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("continent", - continent_name)); - g_free (continent_name); + AG_row_string (row, + "name"))); AG_freeze (); AG_ra = ANASTASIS_redux_action (AG_redux_state, "select_continent", diff --git a/src/anastasis/anastasis-gtk_handle-country-activated.c b/src/anastasis/anastasis-gtk_handle-country-activated.c @@ -27,26 +27,25 @@ #include "anastasis-gtk_helper.h" #include "anastasis-gtk_action.h" #include "anastasis-gtk_handle-main-window-forward-clicked.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** * Callback invoked if a country is selected. * - * @param treeselection selection object + * @param selection the selection of the country list + * @param pspec unused * @param user_data NULL */ void -anastasis_gtk_country_selection_changed_cb (GtkTreeSelection *treeselection, +anastasis_gtk_country_selection_changed_cb (GtkSingleSelection *selection, + GParamSpec *pspec, gpointer user_data) { - GtkTreeModel *model; - GtkTreeIter iter; - + (void) pspec; (void) user_data; - if (! gtk_tree_selection_get_selected (treeselection, - &model, - &iter)) + if (NULL == AG_selected (selection)) { AG_insensitive ("anastasis_gtk_main_window_forward_button"); return; diff --git a/src/anastasis/anastasis-gtk_handle-country-unselected.c b/src/anastasis/anastasis-gtk_handle-country-unselected.c @@ -1,50 +0,0 @@ -/* - This file is part of anastasis-gtk. - Copyright (C) 2020 Anastasis SARL - - Anastasis 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, or (at your - option) any later version. - - Anastasis 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 Anastasis; see the file COPYING. If not, write to the - Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ -/** - * @file src/anastasis/anastasis-gtk_handle-country-unselected.c - * @brief - * @author Christian Grothoff - * @author Dennis Neufeld - */ -#include <gnunet/gnunet_util_lib.h> -#include "anastasis-gtk_action.h" -#include "anastasis-gtk_helper.h" -#include <jansson.h> - - -/** - * Callback invoked if a country is unselected (unselected signal). - * - * @param selection A GtkTreeSelection. - * @param user_data user data set when the signal handler was connected. - */ -void -anastasis_gtk_country_unselected (GtkTreeSelection *selection, - gpointer user_data) -{ - GtkTreeModel *model; - - if (gtk_tree_selection_get_selected (selection, - &model, - NULL)) - AG_enable_next (); - else - AG_insensitive ("anastasis_gtk_main_window_forward_button"); -} diff --git a/src/anastasis/anastasis-gtk_handle-main-window-forward-clicked.c b/src/anastasis/anastasis-gtk_handle-main-window-forward-clicked.c @@ -32,6 +32,7 @@ #include "anastasis-gtk_attributes.h" #include "anastasis-gtk_dispatch.h" #include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> @@ -42,36 +43,22 @@ static void forward_country_selecting (void) { - GtkTreeIter iter; - GtkTreeView *tv; - GtkTreeModel *model; - GtkTreeSelection *sel; - gchar *country_name; - gchar *country_code; + AGRow *row; json_t *arguments; - tv = GTK_TREE_VIEW (GCG_get_main_window_object ( - "anastasis_gtk_country_treeview")); - sel = gtk_tree_view_get_selection (tv); - if (! gtk_tree_selection_get_selected (sel, - &model, - &iter)) + row = AG_selected_row ("anastasis_gtk_country_selection"); + if (NULL == row) { GNUNET_break (0); return; } - gtk_tree_model_get (model, - &iter, - AG_CCMC_COUNTRY_NAME, &country_name, - AG_CCMC_COUNTRY_CODE, &country_code, - -1); arguments = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("country", - country_name), + AG_row_string (row, + "name")), GNUNET_JSON_pack_string ("country_code", - country_code)); - g_free (country_name); - g_free (country_code); + AG_row_string (row, + "code"))); AG_freeze (); AG_ra = ANASTASIS_redux_action (AG_redux_state, "select_country", @@ -206,21 +193,20 @@ AG_forward_user_attributes_collecting (void) * if they really want to proceed with less than three * authentication factors. * - * @param the dialog - * @param response_id must be GTK_RESPONSE_OK to proceed - * @param user_data the builder + * @param source the dialog + * @param res the result of the user's choice, button 1 to proceed + * @param user_data unused */ -void -anastasis_gtk_confirm_multifactor_dialog_response_cb ( - GtkDialog *dialog, - gint response_id, - gpointer user_data) +static void +confirm_multifactor_response_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { - GtkBuilder *builder = user_data; - - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); - if (GTK_RESPONSE_OK != response_id) + (void) user_data; + if (1 != + gtk_alert_dialog_choose_finish (GTK_ALERT_DIALOG (source), + res, + NULL)) { AG_thaw (); return; @@ -237,63 +223,72 @@ anastasis_gtk_confirm_multifactor_dialog_response_cb ( * Function called with the result of telling the * user that they cannot use 1-FA. * - * @param the dialog - * @param response_id must be GTK_RESPONSE_OK to proceed - * @param user_data the builder + * @param source the dialog + * @param res the result of the user's choice, unused + * @param user_data unused */ -void -anastasis_gtk_deny_singlefactor_dialog_response_cb ( - GtkDialog *dialog, - gint response_id, - gpointer user_data) +static void +deny_singlefactor_response_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { - GtkBuilder *builder = user_data; - - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); + (void) user_data; + (void) gtk_alert_dialog_choose_finish (GTK_ALERT_DIALOG (source), + res, + NULL); AG_thaw (); } /** + * Return the main window, to be used as the parent of our dialogs. + * + * @return the main window + */ +static GtkWindow * +main_window (void) +{ + GtkWidget *widget; + + widget = GTK_WIDGET (GCG_get_main_window_object ( + "anastasis_gtk_auth_button_grid")); + return GTK_WINDOW (gtk_widget_get_root (widget)); +} + + +/** * Launch dialog to question sanity of user providing * too few authentication methods. */ static void question_sanity (void) { - GtkBuilder *builder; - GtkDialog *ad; - - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_warn_multifactor.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_DIALOG (gtk_builder_get_object (builder, - "anastasis_gtk_confirm_multifactor_dialog")); - if (NULL == ad) - { - GNUNET_break (0); - g_object_unref (G_OBJECT (builder)); - return; - } - /* show dialog */ - { - GtkRoot *toplevel; - GtkWidget *widget; + GtkAlertDialog *ad; + const char *buttons[] = { + _ ("_Back"), + _ ("_Next"), + NULL + }; - widget = GTK_WIDGET (GCG_get_main_window_object ( - "anastasis_gtk_auth_button_grid")); - toplevel = gtk_widget_get_root (widget); - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); - } + ad = gtk_alert_dialog_new ( + "%s", + _ ( + "To keep your secret secure, we recommend you specify at least three authentication options before proceeding.")); + gtk_alert_dialog_set_detail ( + ad, + _ ("Select \"back\" to add additional authentication options.")); + gtk_alert_dialog_set_buttons (ad, + buttons); + gtk_alert_dialog_set_cancel_button (ad, + 0); + gtk_alert_dialog_set_default_button (ad, + 1); + gtk_alert_dialog_choose (ad, + main_window (), + NULL, + &confirm_multifactor_response_cb, + NULL); + g_object_unref (ad); } @@ -303,38 +298,31 @@ question_sanity (void) static void refuse_insanity (void) { - GtkBuilder *builder; - GtkDialog *ad; - - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_deny_singlefactor.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_DIALOG (gtk_builder_get_object (builder, - "anastasis_gtk_deny_singlefactor_dialog")); - if (NULL == ad) - { - GNUNET_break (0); - g_object_unref (G_OBJECT (builder)); - return; - } - /* show dialog */ - { - GtkRoot *toplevel; - GtkWidget *widget; + GtkAlertDialog *ad; + const char *buttons[] = { + _ ("_Back"), + NULL + }; - widget = GTK_WIDGET (GCG_get_main_window_object ( - "anastasis_gtk_auth_button_grid")); - toplevel = gtk_widget_get_root (widget); - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); - } + ad = gtk_alert_dialog_new ( + "%s", + _ ( + "To keep your secret secure, we require at least two authentication options, and recommend you specify at least three authentication options before proceeding.")); + gtk_alert_dialog_set_detail ( + ad, + _ ("Select \"back\" to add additional authentication options.")); + gtk_alert_dialog_set_buttons (ad, + buttons); + gtk_alert_dialog_set_cancel_button (ad, + 0); + gtk_alert_dialog_set_default_button (ad, + 0); + gtk_alert_dialog_choose (ad, + main_window (), + NULL, + &deny_singlefactor_response_cb, + NULL); + g_object_unref (ad); } @@ -403,51 +391,34 @@ forward_secret_editing (void) static void forward_secret_selecting (void) { - GtkTreeSelection *selection; - GtkTreeModel *model; - GtkTreeIter iter; + AGRow *row; json_t *args; - gchar *provider_url; - gchar *secret_name; - gint version; - gint mask; - const json_t *providers; AG_freeze (); AG_stop_long_action (); - selection = GTK_TREE_SELECTION ( - GCG_get_main_window_object ( - "anastasis_gtk_secret_selection_treeselection")); - - if (! gtk_tree_selection_get_selected (selection, - &model, - &iter)) + row = AG_selected_row ("anastasis_gtk_secret_selection_treeselection"); + if (NULL == row) { GNUNET_break (0); AG_insensitive ("anastasis_gtk_main_window_forward_button"); return; } - gtk_tree_model_get (model, - &iter, - AG_SSMC_PROVIDER_URL, &provider_url, - AG_SSMC_POLICY_VERSION, &version, - AG_SSMC_ATTRIBUTE_MASK, &mask, - AG_SSMC_SECRET_NAME, &secret_name, - AG_SSMC_POLICY_PROVIDER_JSON, &providers, - -1); args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_incref ("providers", - (json_t *) providers), + AG_row_json (row, + "providers")), GNUNET_JSON_pack_uint64 ("attribute_mask", - mask), + AG_row_uint (row, + "attribute_mask")), GNUNET_JSON_pack_string ("secret_name", - secret_name)); + AG_row_string (row, + "secret_name"))); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Proceeding with policy version %u at provider %s\n", - (unsigned int) version, - provider_url); - g_free (provider_url); - g_free (secret_name); + (unsigned int) AG_row_uint (row, + "version"), + AG_row_string (row, + "provider_url")); if (NULL != AG_pd) { ANASTASIS_policy_discovery_stop (AG_pd); diff --git a/src/anastasis/anastasis-gtk_handle-payqr-selection-changed.c b/src/anastasis/anastasis-gtk_handle-payqr-selection-changed.c @@ -25,24 +25,29 @@ #include <gnunet/gnunet_util_lib.h> #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** * Callback invoked if the QR code selection changed. * - * @param selection A GtkTreeSelection. + * @param selection the selection of the list of payment requests + * @param pspec unused * @param user_data user data set when the signal handler was connected. */ void -unpaid_qr_tree_selection_changed_cb (GtkTreeSelection *selection, +unpaid_qr_tree_selection_changed_cb (GtkSingleSelection *selection, + GParamSpec *pspec, gpointer user_data) { - GtkTreeModel *model; - GtkTreeIter iter; + AGRow *row; GdkClipboard *cb; GtkWidget *tv; + const char *uri = ""; + (void) pspec; + (void) user_data; tv = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); if (NULL == tv) { @@ -51,23 +56,15 @@ unpaid_qr_tree_selection_changed_cb (GtkTreeSelection *selection, } cb = gtk_widget_get_primary_clipboard (tv); GNUNET_assert (NULL != cb); - if (gtk_tree_selection_get_selected (selection, - &model, - &iter)) + row = AG_selected (selection); + if (NULL != row) { - char *uri; + const char *url = AG_row_string (row, + "url"); - gtk_tree_model_get (model, - &iter, - AG_UQRMC_URL, &uri, - -1); - gdk_clipboard_set_text (cb, - uri); - g_free (uri); - } - else - { - gdk_clipboard_set_text (cb, - ""); + if (NULL != url) + uri = url; } + gdk_clipboard_set_text (cb, + uri); } diff --git a/src/anastasis/anastasis-gtk_handle-policy-activate.c b/src/anastasis/anastasis-gtk_handle-policy-activate.c @@ -27,38 +27,39 @@ #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_pe.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> +/** + * The user double-clicked a row of the policy review. + * + * @param view the column view showing the policies + * @param position index of the row that was activated + * @param user_data NULL + */ void anastasis_gtk_review_policy_treeview_row_activated_cb ( - GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, + GtkColumnView *view, + guint position, gpointer user_data) { - GtkTreeModel *tm = gtk_tree_view_get_model (tree_view); - GtkTreeIter iter; - guint pindex; - gboolean is_challenge; + GListModel *model = G_LIST_MODEL (gtk_column_view_get_model (view)); + AGRow *row; + gpointer item; - if (NULL == path) - return; - if (! gtk_tree_model_get_iter (tm, - &iter, - path)) + (void) user_data; + item = g_list_model_get_item (model, + position); + if (NULL == item) { GNUNET_break (0); return; } - gtk_tree_model_get (tm, - &iter, - AG_PRMC_POLICY_INDEX, - &pindex, - AG_PRMC_IS_CHALLENGE, - &is_challenge, - -1); - if (! is_challenge) - return; - AG_edit_policy (pindex); + row = AG_row_of (item); + if (AG_row_bool (row, + "is_challenge")) + AG_edit_policy (AG_row_uint (row, + "policy_index")); + g_object_unref (item); } diff --git a/src/anastasis/anastasis-gtk_handle-policy-button.c b/src/anastasis/anastasis-gtk_handle-policy-button.c @@ -27,11 +27,12 @@ #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_pe.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** - * The user pressed a key while the policy treeview had the focus. 'Delete' + * The user pressed a key while the policy list had the focus. 'Delete' * removes the selected policy or challenge. * * @param controller the key controller of the tree view @@ -49,13 +50,8 @@ anastasis_gtk_review_policy_treeview_key_pressed_cb ( GdkModifierType state, gpointer user_data) { - GtkTreeView *tv; - GtkTreeSelection *ts; - GtkTreeModel *tm; - GtkTreeIter iter; + AGRow *row; guint pindex; - gboolean is_challenge; - guint mindex; (void) controller; (void) keycode; @@ -63,29 +59,20 @@ anastasis_gtk_review_policy_treeview_key_pressed_cb ( (void) user_data; if (GDK_KEY_Delete != keyval) return FALSE; - tv = GTK_TREE_VIEW (GCG_get_main_window_object ( - "anastasis_gtk_review_policy_treeview")); - ts = gtk_tree_view_get_selection (tv); - if (! gtk_tree_selection_get_selected (ts, - &tm, - &iter)) + row = AG_selected_row ("anastasis_gtk_review_policy_selection"); + if (NULL == row) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Nothing selected, cannot delete\n"); return FALSE; } - gtk_tree_model_get (tm, - &iter, - AG_PRMC_POLICY_INDEX, - &pindex, - AG_PRMC_IS_CHALLENGE, - &is_challenge, - AG_PRMC_METHOD_INDEX, - &mindex, - -1); - if (is_challenge) + pindex = AG_row_uint (row, + "policy_index"); + if (AG_row_bool (row, + "is_challenge")) AG_delete_challenge (pindex, - mindex); + AG_row_uint (row, + "method_index")); else AG_delete_policy (pindex); return TRUE; diff --git a/src/anastasis/anastasis-gtk_handle-policy-meta.c b/src/anastasis/anastasis-gtk_handle-policy-meta.c @@ -28,6 +28,7 @@ #include "anastasis-gtk_action.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_pe.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> @@ -37,9 +38,10 @@ struct MenuContext { /** - * Reference to the row that the user right-clicked. + * The row that the user right-clicked, NULL if they clicked past the + * last one. */ - GtkTreeRowReference *rr; + AGRow *row; }; @@ -55,60 +57,12 @@ free_menu_context (gpointer cls) { struct MenuContext *ctx = cls; - if (NULL != ctx->rr) - gtk_tree_row_reference_free (ctx->rr); + g_clear_object (&ctx->row); GNUNET_free (ctx); } /** - * Resolve the row the user right-clicked into its policy coordinates. - * - * @param ctx context with the row reference - * @param[out] pindex set to the index of the policy - * @param[out] is_challenge set to true if the row is a challenge - * @param[out] mindex set to the index of the challenge - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -lookup_row (struct MenuContext *ctx, - guint *pindex, - gboolean *is_challenge, - guint *mindex) -{ - GtkTreePath *path; - GtkTreeModel *tm; - GtkTreeIter iter; - - if (NULL == ctx->rr) - return GNUNET_SYSERR; - path = gtk_tree_row_reference_get_path (ctx->rr); - tm = gtk_tree_row_reference_get_model (ctx->rr); - if (NULL == path) - return GNUNET_SYSERR; - if (! gtk_tree_model_get_iter (tm, - &iter, - path)) - { - gtk_tree_path_free (path); - GNUNET_break (0); - return GNUNET_SYSERR; - } - gtk_tree_path_free (path); - gtk_tree_model_get (tm, - &iter, - AG_PRMC_POLICY_INDEX, - pindex, - AG_PRMC_IS_CHALLENGE, - is_challenge, - AG_PRMC_METHOD_INDEX, - mindex, - -1); - return GNUNET_OK; -} - - -/** * The user selected the 'add policy' menu item. * * @param action the activated action @@ -141,20 +95,18 @@ delete_from_ctx_menu (GSimpleAction *action, { struct MenuContext *ctx = user_data; guint pindex; - gboolean is_challenge; - guint mindex; (void) action; (void) parameter; - if (GNUNET_OK != - lookup_row (ctx, - &pindex, - &is_challenge, - &mindex)) + if (NULL == ctx->row) return; - if (is_challenge) + pindex = AG_row_uint (ctx->row, + "policy_index"); + if (AG_row_bool (ctx->row, + "is_challenge")) AG_delete_challenge (pindex, - mindex); + AG_row_uint (ctx->row, + "method_index")); else AG_delete_policy (pindex); } @@ -173,19 +125,13 @@ edit_from_ctx_menu (GSimpleAction *action, gpointer user_data) { struct MenuContext *ctx = user_data; - guint pindex; - gboolean is_challenge; - guint mindex; (void) action; (void) parameter; - if (GNUNET_OK != - lookup_row (ctx, - &pindex, - &is_challenge, - &mindex)) + if (NULL == ctx->row) return; - AG_edit_policy (pindex); + AG_edit_policy (AG_row_uint (ctx->row, + "policy_index")); } @@ -199,7 +145,7 @@ edit_from_ctx_menu (GSimpleAction *action, */ static GMenuModel * build_menu (bool have_row, - gboolean is_challenge) + bool is_challenge) { GMenu *menu = g_menu_new (); @@ -241,9 +187,9 @@ build_menu (bool have_row, /** * Install the actions the context menu of @a tv activates; they operate on the - * row referenced by @a ctx. + * row given by @a ctx. * - * @param tv the tree view the menu belongs to + * @param tv the list the menu belongs to * @param ctx context to hand to the actions, ownership is taken */ static void @@ -280,10 +226,10 @@ install_actions (GtkWidget *tv, /** * Return the context menu of @a tv, creating it on first use. A single - * popover is reused for the lifetime of the tree view; only its model and + * popover is reused for the lifetime of the list; only its model and * position change. * - * @param tv the tree view + * @param tv the list * @return the popover to show */ static GtkPopover * @@ -312,7 +258,7 @@ get_popover (GtkWidget *tv) /** - * We got a right-click on the policy treeview: compute which menu items are + * We got a right-click on the policy list: compute which menu items are * applicable and display an appropriate menu. * * @param gesture the click gesture, restricted to button 3 by the UI file @@ -330,58 +276,26 @@ anastasis_gtk_review_policy_treeview_pressed_cb (GtkGestureClick *gesture, { GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); - GtkTreeView *tv = GTK_TREE_VIEW (widget); struct MenuContext *ctx; - GtkTreePath *path; - gboolean is_challenge = FALSE; - bool have_row = false; - int bx; - int by; + AGRow *row; + bool is_challenge = false; (void) n_press; (void) user_data; ctx = GNUNET_new (struct MenuContext); - gtk_tree_view_convert_widget_to_bin_window_coords (tv, - (int) x, - (int) y, - &bx, - &by); - if (gtk_tree_view_get_path_at_pos (tv, - bx, - by, - &path, - NULL, - NULL, - NULL)) + row = AG_row_at (widget, + y); + if (NULL != row) { - GtkTreeModel *tm = gtk_tree_view_get_model (tv); - GtkTreeIter iter; - - if (gtk_tree_model_get_iter (tm, - &iter, - path)) - { - have_row = true; - ctx->rr = gtk_tree_row_reference_new (tm, - path); - gtk_tree_model_get (tm, - &iter, - AG_PRMC_IS_CHALLENGE, - &is_challenge, - -1); - } - else - { - /* not sure how we got a path but no iter... */ - GNUNET_break (0); - } - gtk_tree_path_free (path); + ctx->row = g_object_ref (row); + is_challenge = AG_row_bool (row, + "is_challenge"); } install_actions (widget, ctx); { GtkPopover *popover = get_popover (widget); - GMenuModel *model = build_menu (have_row, + GMenuModel *model = build_menu (NULL != row, is_challenge); const GdkRectangle at = { .x = (int) x, diff --git a/src/anastasis/anastasis-gtk_handle-policy-selection-changed.c b/src/anastasis/anastasis-gtk_handle-policy-selection-changed.c @@ -27,25 +27,25 @@ #include "anastasis-gtk_action.h" #include "anastasis-gtk_attributes.h" #include "anastasis-gtk_handle-identity-changed.h" +#include "anastasis-gtk_rows.h" #include <jansson.h> /** * Callback invoked if the policy selection changed. * - * @param selection A GtkTreeSelection. + * @param selection the selection of the secret list + * @param pspec unused * @param user_data user data set when the signal handler was connected. */ void anastasis_gtk_secret_selection_treeselection_changed_cb ( - GtkTreeSelection *selection, + GtkSingleSelection *selection, + GParamSpec *pspec, gpointer user_data) { - GtkTreeModel *model; - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected (selection, - &model, - &iter)) + (void) pspec; + (void) user_data; + if (NULL != AG_selected (selection)) { AG_enable_next (); } diff --git a/src/anastasis/anastasis-gtk_handle-policy-version-changed.c b/src/anastasis/anastasis-gtk_handle-policy-version-changed.c @@ -23,6 +23,7 @@ * @author Christian Grothoff */ #include <gnunet/gnunet_util_lib.h> +#include "anastasis_gtk_util.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_action.h" #include "anastasis-gtk_attributes.h" @@ -118,18 +119,20 @@ change_version (void) * The user answered the question whether they accept losing their challenge * solving progress. * - * @param dialog the warning dialog - * @param response_id #GTK_RESPONSE_OK if the user confirmed + * @param source the warning dialog + * @param res the result of the user's choice, button 0 to proceed * @param user_data unused */ static void -reset_warning_response_cb (GtkDialog *dialog, - gint response_id, +reset_warning_response_cb (GObject *source, + GAsyncResult *res, gpointer user_data) { (void) user_data; - gtk_window_destroy (GTK_WINDOW (dialog)); - if (GTK_RESPONSE_OK == response_id) + if (0 == + gtk_alert_dialog_choose_finish (GTK_ALERT_DIALOG (source), + res, + NULL)) { change_version (); return; @@ -165,22 +168,31 @@ update_policy (void) "challenge_feedback")) { GtkRoot *toplevel; - GtkWidget *diag; + GtkAlertDialog *diag; + const char *buttons[] = { + _ ("_OK"), + _ ("_Cancel"), + NULL + }; toplevel = gtk_widget_get_root ( GTK_WIDGET (GCG_get_main_window_object ( "anastasis_gtk_main_window"))); - diag = gtk_message_dialog_new ( - GTK_WINDOW (toplevel), - GTK_DIALOG_MODAL, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_OK_CANCEL, + diag = gtk_alert_dialog_new ( + "%s", _ ("This action will reset all of your challenge solving progress!")); - g_signal_connect (diag, - "response", - G_CALLBACK (&reset_warning_response_cb), - NULL); - gtk_window_present (GTK_WINDOW (diag)); + gtk_alert_dialog_set_buttons (diag, + buttons); + gtk_alert_dialog_set_cancel_button (diag, + 1); + gtk_alert_dialog_set_default_button (diag, + 0); + gtk_alert_dialog_choose (diag, + GTK_WINDOW (toplevel), + NULL, + &reset_warning_response_cb, + NULL); + g_object_unref (diag); return; } change_version (); diff --git a/src/anastasis/anastasis-gtk_handle-print.c b/src/anastasis/anastasis-gtk_handle-print.c @@ -127,19 +127,13 @@ print_to_file (GtkWindow *parent_window, { /* report something went wrong; alas, we don't have a good error message here (yet)...*/ - GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT; - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (parent_window, - flags, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - _ ("Failed to generate PDF file.")); - g_signal_connect (dialog, - "response", - G_CALLBACK (&gtk_window_destroy), - NULL); - gtk_window_present (GTK_WINDOW (dialog)); + GtkAlertDialog *dialog; + + dialog = gtk_alert_dialog_new ("%s", + _ ("Failed to generate PDF file.")); + gtk_alert_dialog_show (dialog, + parent_window); + g_object_unref (dialog); } } @@ -147,24 +141,26 @@ print_to_file (GtkWindow *parent_window, /** * Function called once the user picked the file to print to. * - * @param dialog the file selection dialog - * @param response_id #GTK_RESPONSE_ACCEPT if the user confirmed - * @param user_data unused + * @param source the file dialog + * @param res the result of the user's choice + * @param user_data the window the dialog belongs to */ static void -print_dialog_response_cb (GtkDialog *dialog, - gint response_id, +print_dialog_response_cb (GObject *source, + GAsyncResult *res, gpointer user_data) { - GtkWindow *parent_window; - char *filename = NULL; - - (void) user_data; - parent_window = gtk_window_get_transient_for (GTK_WINDOW (dialog)); - if (GTK_RESPONSE_ACCEPT == response_id) - filename = ANASTASIS_GTK_filechooser_get_filename_utf8 ( - GTK_FILE_CHOOSER (dialog)); - gtk_window_destroy (GTK_WINDOW (dialog)); + GtkWindow *parent_window = user_data; + GFile *file; + char *filename; + + file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (source), + res, + NULL); + if (NULL == file) + return; /* user aborted */ + filename = ANASTASIS_GTK_file_get_path_utf8 (file); + g_object_unref (file); if (NULL == filename) return; print_to_file (parent_window, @@ -184,9 +180,7 @@ void anastasis_gtk_print_details_button_clicked_cb (GtkWidget *button, gpointer user_data) { - GtkWidget *dialog; - GtkFileChooser *chooser; - GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; + GtkFileDialog *dialog; GtkWindow *parent_window; GtkRoot *top; @@ -201,21 +195,15 @@ anastasis_gtk_print_details_button_clicked_cb (GtkWidget *button, GNUNET_break (0); parent_window = NULL; } - dialog = gtk_file_chooser_dialog_new ("Save Personal Details to PDF file", - parent_window, - action, - _ ("_Cancel"), - GTK_RESPONSE_CANCEL, - _ ("_Save"), - GTK_RESPONSE_ACCEPT, - NULL); - chooser = GTK_FILE_CHOOSER (dialog); - /* GTK4 always asks before overwriting, no need to request it */ - gtk_file_chooser_set_current_name (chooser, - _ ("anastasis-personal-details.pdf")); - g_signal_connect (dialog, - "response", - G_CALLBACK (&print_dialog_response_cb), - NULL); - gtk_window_present (GTK_WINDOW (dialog)); + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, + _ ("Save Personal Details to PDF file")); + gtk_file_dialog_set_initial_name (dialog, + _ ("anastasis-personal-details.pdf")); + gtk_file_dialog_save (dialog, + parent_window, + NULL, + &print_dialog_response_cb, + parent_window); + g_object_unref (dialog); } diff --git a/src/anastasis/anastasis-gtk_handle-secret-buttons.c b/src/anastasis/anastasis-gtk_handle-secret-buttons.c @@ -39,18 +39,18 @@ static magic_t magic; /** - * Function called from the open-file dialog upon completion. + * Function called once the user picked the file holding the secret. * - * @param dialog the secret selection dialog - * @param response_id response code from the dialog - * @param user_data the builder of the dialog + * @param source the file dialog + * @param res the result of the user's choice + * @param user_data unused */ -void -open_secret_dialog_response_cb (GtkDialog *dialog, - gint response_id, - gpointer user_data) +static void +open_secret_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { - GtkBuilder *builder = GTK_BUILDER (user_data); + GFile *file; char *filename; const char *fn; size_t data_size; @@ -59,16 +59,16 @@ open_secret_dialog_response_cb (GtkDialog *dialog, GtkEntry *entry; const char *name; - if (GTK_RESPONSE_OK != response_id) - { - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); + (void) user_data; + file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source), + res, + NULL); + if (NULL == file) + return; /* user aborted */ + filename = ANASTASIS_GTK_file_get_path_utf8 (file); + g_object_unref (file); + if (NULL == filename) return; - } - filename = - ANASTASIS_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); fn = strrchr (filename, '/'); if (NULL == fn) @@ -182,46 +182,34 @@ void anastasis_gtk_enter_secret_open_button_clicked_cb (GtkButton *button, gpointer user_data) { - GtkWidget *ad; - GtkBuilder *builder; + GtkFileDialog *dialog; - (void) button; (void) user_data; - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_open_secret_dialog.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_WIDGET (gtk_builder_get_object (builder, - "open_file_dialog")); - { - GtkRoot *toplevel; - - toplevel = gtk_widget_get_root (GTK_WIDGET (button)); - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); - } + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, + _ ("Open file with secret to back up")); + gtk_file_dialog_open (dialog, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + NULL, + &open_secret_ready_cb, + NULL); + g_object_unref (dialog); } /** - * Function called from the open-directory dialog upon completion. + * Function called once the user picked the file to save the secret in. * - * @param dialog the pseudonym selection dialog - * @param response_id response code from the dialog - * @param user_data the builder of the dialog + * @param source the file dialog + * @param res the result of the user's choice + * @param user_data unused */ -void -save_secret_dialog_response_cb (GtkDialog *dialog, - gint response_id, - gpointer user_data) +static void +save_secret_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { - GtkBuilder *builder = GTK_BUILDER (user_data); + GFile *file; char *filename; size_t data_len = 0; const char *text = NULL; @@ -240,16 +228,16 @@ save_secret_dialog_response_cb (GtkDialog *dialog, GNUNET_JSON_spec_end () }; - if (GTK_RESPONSE_ACCEPT != response_id) - { - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); + (void) user_data; + file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (source), + res, + NULL); + if (NULL == file) + return; /* user aborted */ + filename = ANASTASIS_GTK_file_get_path_utf8 (file); + g_object_unref (file); + if (NULL == filename) return; - } - filename = - ANASTASIS_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); cs = json_object_get (AG_redux_state, "core_secret"); GNUNET_assert (NULL != cs); @@ -331,8 +319,7 @@ anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button, .fn = NULL } }; - GtkWidget *ad; - GtkBuilder *builder; + GtkFileDialog *dialog; const char *mime = NULL; const char *fn = NULL; json_t *cs; @@ -348,7 +335,6 @@ anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button, GNUNET_JSON_spec_end () }; - (void) button; (void) user_data; cs = json_object_get (AG_redux_state, "core_secret"); @@ -357,17 +343,6 @@ anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button, GNUNET_JSON_parse (cs, spec, NULL, NULL)); - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_save_secret_dialog.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_WIDGET (gtk_builder_get_object (builder, - "save_file_dialog")); if ( (NULL == fn) && (NULL != mime) ) { @@ -381,16 +356,35 @@ anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button, break; } } - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (ad), - fn); + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, + _ ("Save recovered secret")); { - GtkRoot *toplevel; - - toplevel = gtk_widget_get_root (GTK_WIDGET (button)); - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); + GtkFileFilter *filter; + GListStore *filters; + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, + _ ("Secrets")); + gtk_file_filter_add_pattern (filter, + "*.secret"); + filters = g_list_store_new (GTK_TYPE_FILE_FILTER); + g_list_store_append (filters, + filter); + g_object_unref (filter); + gtk_file_dialog_set_filters (dialog, + G_LIST_MODEL (filters)); + g_object_unref (filters); } + if (NULL != fn) + gtk_file_dialog_set_initial_name (dialog, + fn); + gtk_file_dialog_save (dialog, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + NULL, + &save_secret_ready_cb, + NULL); + g_object_unref (dialog); } diff --git a/src/anastasis/anastasis-gtk_helper.h b/src/anastasis/anastasis-gtk_helper.h @@ -40,387 +40,6 @@ extern bool AG_have_error; /** - * Columns of the continent_liststore. - */ -enum AG_ContinentsModelColumns -{ - /** - * A gchararray. - */ - AG_CMC_CONTINENT_NAME = 0, - - /** - * A gchararray. - */ - AG_CMC_CONTINENT_NAME_I18N = 1 -}; - - -/** - * Columns of the secret_selection_liststore. - */ -enum AG_SecretSelectionModelColumns -{ - /** - * A gchararray. - */ - AG_SSMC_PROVIDER_URL = 0, - - /** - * A gint. - */ - AG_SSMC_POLICY_VERSION = 1, - - /** - * A gint. - */ - AG_SSMC_ATTRIBUTE_MASK = 2, - - /** - * A gchararray. - */ - AG_SSMC_SECRET_NAME = 3, - - /** - * A gchararray. - */ - AG_SSMC_POLICY_DATE_STRING = 4, - - /** - * A guint64. - */ - AG_SSMC_POLICY_DATE_NUMERIC = 5, - - /** - * A gpointer. - */ - AG_SSMC_POLICY_PROVIDER_JSON = 6 -}; - - -/** - * Columns of the currency_liststore. - */ -enum AG_CurrencyModelColumns -{ - /** - * A gchararray. - */ - AG_CMC_CURRENCY_NAME = 0 -}; - - -/** - * Columns of the challenge_status_liststore. - */ -enum AG_ChallengeStatusModelColumns -{ - /** - * A guint. - */ - AG_CSM_CHALLENGE_OFFSET = 0, - - /** - * A gchararray. - */ - AG_CSM_CHALLENGE_UUID = 1, - - /** - * A gboolean - */ - AG_CSM_SOLVED = 2, - - /** - * A gchararray. - */ - AG_CSM_STATUS = 3, - - /** - * A GdkPixBuf. - */ - AG_CSM_PAYMENT_QR_CODE = 4, - - /** - * A gchararray. - */ - AG_CSM_ERROR_MESSAGE = 5, - - /** - * A gchararray. - */ - AG_CSM_PAYTO_URI = 6, - - /** - * A gboolean. - */ - AG_CSM_PAYING = 7, - - /** - * A gboolean. - */ - AG_CSM_HAS_ERROR = 8, - - /** - * A gchararray. - */ - AG_CSM_COST = 9, - - /** - * A gchararray. - */ - AG_CSM_REDIRECT_URL = 10, - - /** - * A gboolean. - */ - AG_CSM_HAVE_REDIRECT = 11, - - /** - * A gboolean - */ - AG_CSM_NOT_SOLVED = 12, - - /** - * A gchararray - */ - AG_CSM_TYPE = 13, - - /** - * A gchararray - */ - AG_CSM_INSTRUCTIONS = 14, - - /** - * A gchararray - */ - AG_CSM_PROVIDER_URL = 15 - -}; - - -/** - * Columns of the provider_liststore. - */ -enum AG_ProviderModelColumns -{ - /** - * A gchararray. Contains the provider's base URL. - */ - AG_PMC_PROVIDER_URL = 0, - - /** - * A gchararray. Status of the provider in text. - */ - AG_PMC_PROVIDER_STATUS = 1, - - /** - * A gchararray. Color to use when rendering the status. - */ - AG_PMC_PROVIDER_STATUS_COLOR = 2, - - /** - * A gchararray. Amount as human-readable string with the provider's liability limit, - */ - AG_PMC_PROVIDER_LIABILITY_LIMIT = 3, - - /** - * A gboolean. true if this provider is enabled. - */ - AG_PMC_PROVIDER_ENABLED = 4, - - /** - * A gboolean. true if this provider can be enabled. - */ - AG_PMC_PROVIDER_SENSITIVE = 5, - - /** - * A gboolean. false if this provider can be enabled. - */ - AG_PMC_PROVIDER_NOT_SENSITIVE = 6, - - /** - * A gchararray. Contains the provider's business name, if known, - * otherwise the provider's base URL. - */ - AG_PMC_PROVIDER_NAME = 7, - -}; - - -/** - * Columns of the backup_provider_liststore. - */ -enum AG_BackupProviderColumns -{ - /** - * A gchararray. - */ - AG_BPC_PROVIDER_URL = 0, - - /** - * A guint64 - */ - AG_BPC_BACKUP_VERSION = 1, - - /** - * A gchararray. // FIXME: #6823 - */ - AG_BPC_EXPIRATION_TIME_STR = 2, - - /** - * A gboolean. - */ - AG_BPC_SUCCESS_FLAG = 3, - - /** - * A gchararray. - */ - AG_BPC_PROVIDER_NAME = 4 - -}; - -/** - * Columns of the country_liststore. - */ -enum AG_CountryCodeModelColumns -{ - /** - * A gchararray. - */ - AG_CCMC_COUNTRY_NAME = 0, - - /** - * A gchararray. - */ - AG_CCMC_COUNTRY_CODE = 1 - -}; - -/** - * Columns of the authentication_methods_liststore. - */ -enum AG_AuthenticationMethodsModelColumns -{ - /** - * A gchararray. - */ - AG_AMMC_TYPE = 0, - - /** - * A gchararray. - */ - AG_AMMC_VISUALIZATION = 1, - - /** - * A guint. - */ - AG_AMMC_INDEX = 2 -}; - - -/** - * Columns of the unpaid_qrcodes_liststore. - */ -enum AG_UnpaidQrcodesModelColumns -{ - /** - * A GdkPixbuf. - */ - AG_UQRMC_QR_IMAGE = 0, - - /** - * A gchararray. - */ - AG_UQRMC_URL = 1, - - /** - * A gchararray. - */ - AG_UQRMC_PROVIDER = 2 -}; - - -/** - * Columns of the policy_review_treestore. - */ -enum AG_PolicyReviewModelColumns -{ - /** - * A gchararray. - */ - AG_PRMC_POLICY_NAME = 0, - - /** - * A gchararray. - */ - AG_PRMC_METHOD_TYPE = 1, - - /** - * A gchararray. - */ - AG_PRMC_COST = 2, - - /** - * A gchararray. - */ - AG_PRMC_PROVIDER_URL = 3, - - /** - * A gchararray. - */ - AG_PRMC_EXPIRATION_TIME_STR = 4, - - /** - * A guint. - */ - AG_PRMC_POLICY_INDEX = 5, - - /** - * A gboolean. True on lines representing challenges. - */ - AG_PRMC_IS_CHALLENGE = 6, - - /** - * A guint. - */ - AG_PRMC_METHOD_INDEX = 7, - - /** - * A gboolean. True on lines representing solved challenges. - */ - AG_PRMC_WAS_SOLVED = 8, - - /** - * Name of the provider (if known, otherwise the provider's URL). - */ - AG_PRMC_PROVIDER_NAME = 9 - -}; - - -/** - * Columns in the progress model liststores. - */ -enum AG_ProgressModelColumns -{ - /** - * A gchararray. - */ - AG_PRGMC_DESCRIPTION = 0, - - /** - * A gchararray. - */ - AG_PRGMC_REGEX = 1, - - /** - * A gchararray. - */ - AG_PRGMC_TOOLTIP = 2 -}; - - -/** * Hide widget of the given @a name of the main window * * @param name widget to hide diff --git a/src/anastasis/anastasis-gtk_io.c b/src/anastasis/anastasis-gtk_io.c @@ -31,30 +31,54 @@ /** - * Function called from the open-directory dialog upon completion. + * Return a file filter matching the files we store states in. * - * @param dialog the pseudonym selection dialog - * @param response_id response code from the dialog - * @param user_data the builder of the dialog + * @return the filter, ownership is passed to the caller */ -void -open_directory_dialog_response_cb (GtkDialog *dialog, - gint response_id, - gpointer user_data) +static GListModel * +state_filters (void) +{ + GtkFileFilter *filter; + GListStore *filters; + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, + _ ("Anastasis state files")); + gtk_file_filter_add_pattern (filter, + "*.ana"); + filters = g_list_store_new (GTK_TYPE_FILE_FILTER); + g_list_store_append (filters, + filter); + g_object_unref (filter); + return G_LIST_MODEL (filters); +} + + +/** + * Function called once the user picked the state file to open. + * + * @param source the file dialog + * @param res the result of the user's choice + * @param user_data unused + */ +static void +open_state_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { - GtkBuilder *builder = GTK_BUILDER (user_data); + GFile *file; char *filename; - if (GTK_RESPONSE_OK != response_id) - { - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); + (void) user_data; + file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source), + res, + NULL); + if (NULL == file) + return; /* user aborted */ + filename = ANASTASIS_GTK_file_get_path_utf8 (file); + g_object_unref (file); + if (NULL == filename) return; - } - filename = - ANASTASIS_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); AG_load (filename); GNUNET_free (filename); } @@ -70,28 +94,23 @@ void anastasis_gtk_open_state_clicked_cb (GtkButton *button, gpointer user_data) { - GtkWidget *ad; - GtkBuilder *builder; - - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_open_file_dialog.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_WIDGET (gtk_builder_get_object (builder, - "open_file_dialog")); - { - GtkRoot *toplevel; + GtkFileDialog *dialog; + GListModel *filters; - toplevel = gtk_widget_get_root (GTK_WIDGET (button)); - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); - } + (void) user_data; + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, + _ ("Open saved Anastasis state")); + filters = state_filters (); + gtk_file_dialog_set_filters (dialog, + filters); + g_object_unref (filters); + gtk_file_dialog_open (dialog, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + NULL, + &open_state_ready_cb, + NULL); + g_object_unref (dialog); } @@ -160,19 +179,21 @@ write_state (char *filename) * The user answered the question whether they really want their secret written * to disk in cleartext. * - * @param dialog the warning dialog - * @param response_id #GTK_RESPONSE_OK if the user confirmed + * @param source the warning dialog + * @param res the result of the user's choice * @param user_data the file name to write to */ static void -cleartext_warning_response_cb (GtkDialog *dialog, - gint response_id, - gpointer user_data) +cleartext_warning_response_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { char *filename = user_data; - gtk_window_destroy (GTK_WINDOW (dialog)); - if (GTK_RESPONSE_OK != response_id) + if (0 != + gtk_alert_dialog_choose_finish (GTK_ALERT_DIALOG (source), + res, + NULL)) { /* user aborted */ GNUNET_free (filename); @@ -183,16 +204,16 @@ cleartext_warning_response_cb (GtkDialog *dialog, /** - * Function called from the save-file dialog upon completion. + * Function called once the user picked the file to save the state in. * - * @param dialog the file selection dialog - * @param response_id response code from the dialog - * @param user_data the builder of the dialog + * @param source the file dialog + * @param res the result of the user's choice + * @param user_data unused */ -void -save_directory_dialog_response_cb (GtkDialog *dialog, - gint response_id, - gpointer user_data) +static void +save_state_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) { static const struct DispatchItem save_state[] = { { .state = "USER_ATTRIBUTES_COLLECTING", @@ -200,20 +221,20 @@ save_directory_dialog_response_cb (GtkDialog *dialog, { .state = NULL, .action = NULL } }; - GtkBuilder *builder = GTK_BUILDER (user_data); + GFile *file; char *filename; - if (GTK_RESPONSE_ACCEPT != response_id) - { - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); + (void) user_data; + file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (source), + res, + NULL); + if (NULL == file) + return; /* user aborted */ + filename = ANASTASIS_GTK_file_get_path_utf8 (file); + g_object_unref (file); + if (NULL == filename) return; - } (void) AG_dispatch (save_state); - filename = - ANASTASIS_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); - gtk_window_destroy (GTK_WINDOW (dialog)); - g_object_unref (G_OBJECT (builder)); /* check if we should warn the user about writing 'core_secret' to disk */ { @@ -224,23 +245,32 @@ save_directory_dialog_response_cb (GtkDialog *dialog, if ( (NULL != cs) && (! json_is_null (cs)) ) { - GtkWidget *diag; + GtkAlertDialog *diag; GtkRoot *toplevel; + const char *buttons[] = { + _ ("_OK"), + _ ("_Cancel"), + NULL + }; toplevel = gtk_widget_get_root ( GTK_WIDGET (GCG_get_main_window_object ( "anastasis_gtk_main_window"))); - diag = gtk_message_dialog_new ( - GTK_WINDOW (toplevel), - GTK_DIALOG_MODAL, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_OK_CANCEL, + diag = gtk_alert_dialog_new ( + "%s", _ ("This will write your secret to disk in cleartext!")); - g_signal_connect (diag, - "response", - G_CALLBACK (&cleartext_warning_response_cb), - filename); - gtk_window_present (GTK_WINDOW (diag)); + gtk_alert_dialog_set_buttons (diag, + buttons); + gtk_alert_dialog_set_cancel_button (diag, + 1); + gtk_alert_dialog_set_default_button (diag, + 0); + gtk_alert_dialog_choose (diag, + GTK_WINDOW (toplevel), + NULL, + &cleartext_warning_response_cb, + filename); + g_object_unref (diag); return; } } @@ -260,27 +290,23 @@ void anastasis_gtk_main_window_save_as_button_clicked_cb (GtkButton *button, gpointer user_data) { - GtkWidget *ad; - GtkBuilder *builder; - GtkRoot *toplevel; - - toplevel = gtk_widget_get_root (GTK_WIDGET (button)); - builder = ANASTASIS_GTK_get_new_builder ( - ANASTASIS_GTK_project_data (), - "anastasis_gtk_save_file_dialog.ui", - NULL); - if (NULL == builder) - { - GNUNET_break (0); - return; - } - ad = GTK_WIDGET (gtk_builder_get_object (builder, - "save_file_dialog")); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (ad), - "untitled.ana"); - { - gtk_window_set_transient_for (GTK_WINDOW (ad), - GTK_WINDOW (toplevel)); - gtk_window_present (GTK_WINDOW (ad)); - } + GtkFileDialog *dialog; + GListModel *filters; + + (void) user_data; + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, + _ ("Save Anastasis state")); + filters = state_filters (); + gtk_file_dialog_set_filters (dialog, + filters); + g_object_unref (filters); + gtk_file_dialog_set_initial_name (dialog, + "untitled.ana"); + gtk_file_dialog_save (dialog, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), + NULL, + &save_state_ready_cb, + NULL); + g_object_unref (dialog); } diff --git a/src/anastasis/anastasis-gtk_pe-edit-policy.c b/src/anastasis/anastasis-gtk_pe-edit-policy.c @@ -58,14 +58,14 @@ struct LineContext struct EditDialogContext *edc; /** - * Our combo box. + * Our drop down. */ - GtkComboBox *cb; + GtkDropDown *cb; /** - * Model for our combo box. + * The provider URLs our drop down offers. */ - GtkTreeModel *model; + GtkStringList *model; /** * Challenge index for this line. @@ -141,21 +141,16 @@ anastasis_gtk_policy_edit_dialog_response_cb ( NULL != lctx; lctx = lctx->next) { - GtkTreeIter iter; - gchar *url; + guint selected; if (! lctx->on) continue; - if (! gtk_combo_box_get_active_iter (lctx->cb, - &iter)) + selected = gtk_drop_down_get_selected (lctx->cb); + if (GTK_INVALID_LIST_POSITION == selected) { GNUNET_break (0); continue; } - gtk_tree_model_get (lctx->model, - &iter, - 0, &url, - -1); GNUNET_assert (0 == json_array_append_new ( policy, @@ -163,8 +158,9 @@ anastasis_gtk_policy_edit_dialog_response_cb ( GNUNET_JSON_pack_uint64 ("authentication_method", lctx->cindex), GNUNET_JSON_pack_string ("provider", - url)))); - g_free (url); + gtk_string_list_get_string ( + lctx->model, + selected))))); } if (UINT_MAX == edc->pindex) { @@ -216,43 +212,40 @@ anastasis_gtk_policy_edit_dialog_response_cb ( /** - * The user changed an entry in the combo boxy of an edit + * The user changed an entry in the drop down of an edit * dialog. Update the ability to confirm the selection: * if at least one authentication method is selected, the * OK button should be sensitive. * - * @param widget the combo box that was changed + * @param widget the drop down that was changed + * @param pspec unused * @param user_data the `struct EditDialogContext` */ static void -combo_box_changed_cb ( - GtkComboBox *widget, +drop_down_changed_cb ( + GtkDropDown *widget, + GParamSpec *pspec, gpointer user_data) { struct LineContext *lc = user_data; struct EditDialogContext *edc = lc->edc; + (void) widget; + (void) pspec; /* Update our line context's on/off flag */ { - GtkTreeIter iter; + guint selected = gtk_drop_down_get_selected (lc->cb); - if (! gtk_combo_box_get_active_iter (lc->cb, - &iter)) + if (GTK_INVALID_LIST_POSITION == selected) { GNUNET_break (0); } else { - gchar *url; - - gtk_tree_model_get (lc->model, - &iter, - 0, &url, - -1); lc->on = (0 != strcmp (_ ("<off>"), - url)); - g_free (url); + gtk_string_list_get_string (lc->model, + selected))); } } /* finally, update "OK" button sensitivity */ @@ -320,27 +313,22 @@ ap_matches (const char *type, /** - * Create a GtkListStore containing all of the URLs - * of Anastasis providers offering @a type as an - * authentication method. + * Create a list with all of the URLs of Anastasis providers offering + * @a type as an authentication method. * * @return the model */ -static GtkTreeModel * +static GtkStringList * make_model (const char *type) { - GtkListStore *ls; + GtkStringList *sl; json_t *aps; const char *url; json_t *ap; - ls = gtk_list_store_new (1, - G_TYPE_STRING); - gtk_list_store_insert_with_values (ls, - NULL, - -1, - 0, _ ("<off>"), - -1); + sl = gtk_string_list_new (NULL); + gtk_string_list_append (sl, + _ ("<off>")); aps = json_object_get (AG_redux_state, "authentication_providers"); GNUNET_break (NULL != aps); @@ -348,15 +336,12 @@ make_model (const char *type) if (ap_matches (type, ap)) { - gtk_list_store_insert_with_values (ls, - NULL, - -1, - 0, url, - -1); + gtk_string_list_append (sl, + url); } } - return GTK_TREE_MODEL (ls); + return sl; } @@ -370,33 +355,20 @@ static void select_by_url (const char *url, struct LineContext *lctx) { - GtkTreeIter iter; + guint n = g_list_model_get_n_items (G_LIST_MODEL (lctx->model)); - if (! gtk_tree_model_get_iter_first (lctx->model, - &iter)) + for (guint i = 0; i < n; i++) { - GNUNET_break (0); - return; - } - do { - gchar *have; - - gtk_tree_model_get (lctx->model, - &iter, - 0, &have, - -1); - if (0 == strcmp (have, + if (0 == strcmp (gtk_string_list_get_string (lctx->model, + i), url)) { - gtk_combo_box_set_active_iter (lctx->cb, - &iter); + gtk_drop_down_set_selected (lctx->cb, + i); lctx->on = true; - g_free (have); return; } - g_free (have); - } while (gtk_tree_model_iter_next (lctx->model, - &iter)); + } GNUNET_break (0); /* not found */ } @@ -414,16 +386,10 @@ select_by_policy (const json_t *methods, { size_t index; json_t *method; - GtkTreeIter iter; - if (! gtk_tree_model_get_iter_first (lctx->model, - &iter)) - { - GNUNET_break (0); - return; - } - gtk_combo_box_set_active_iter (lctx->cb, - &iter); + /* default to the "<off>" entry we put first */ + gtk_drop_down_set_selected (lctx->cb, + 0); json_array_foreach (methods, index, method) { json_int_t am = json_integer_value (json_object_get (method, \ "authentication_method")); @@ -518,27 +484,17 @@ AG_edit_policy (guint pindex) labels); GNUNET_free (labels); lctx->model = make_model (type); - cb = gtk_combo_box_new_with_model (lctx->model); - lctx->cb = GTK_COMBO_BOX (cb); - { - GtkCellRenderer *renderer; - - renderer = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cb), - renderer, - true); - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (cb), - renderer, - "text", - 0); - } + /* the drop down takes over our reference to the model */ + cb = gtk_drop_down_new (G_LIST_MODEL (lctx->model), + NULL); + lctx->cb = GTK_DROP_DOWN (cb); lctx->edc = edc; GNUNET_CONTAINER_DLL_insert (edc->lc_head, edc->lc_tail, lctx); g_object_connect (cb, - "signal::changed", - &combo_box_changed_cb, lctx, + "signal::notify::selected", + &drop_down_changed_cb, lctx, NULL); if (NULL != methods) select_by_policy (methods, diff --git a/src/anastasis/anastasis-gtk_progress.c b/src/anastasis/anastasis-gtk_progress.c @@ -19,21 +19,143 @@ */ /** * @file src/anastasis/anastasis-gtk_progress.c - * @brief Functions dealing with the tree views used to show the user where we are in the process + * @brief Functions dealing with the lists that show the user where we are in the process * @author Christian Grothoff */ #include <gnunet/gnunet_util_lib.h> +#include "anastasis_gtk_util.h" #include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" #include <gdk-pixbuf/gdk-pixbuf.h> /** - * The progress tree views only report where we are in the process; the user - * must not be able to change the selection by clicking. The gesture runs in - * the capture phase, so claiming the sequence here keeps the click from ever - * reaching the tree view. + * One step of one of the two processes we guide the user through. + */ +struct Step +{ + /** + * Name of the step, as shown to the user. + */ + const char *description; + + /** + * Regular expression matching the reducer states this step covers. + */ + const char *regex; + + /** + * Long explanation of the step, shown as a tooltip. + */ + const char *tooltip; +}; + + +/** + * The steps of making a backup. + */ +static const struct Step backup_steps[] = { + { .description = N_ ("1. Location & currency"), + .regex = "^(CONTINENT|COUNTRY)_SELECTING$", + .tooltip = N_ ( + "Where you live will determine which personal information can be used to identify your backup.") }, + { .description = N_ ("2. Personal information"), + .regex = "^USER_ATTRIBUTES_COLLECTING$", + .tooltip = N_ ( + "Your personal information will not be shared with anyone. But you must provide the same information during recovery.\\") }, + { .description = N_ ("3. Authorization methods"), + .regex = "^AUTHENTICATIONS_EDITING$", + .tooltip = N_ ( + "Specify which methods could be used to authenticate you during recovery.") }, + { .description = N_ ("4. Recovery policies"), + .regex = "^POLICIES_REVIEWING$", + .tooltip = N_ ( + "These are possible ways how the secret could be recovered. Please review and possibly edit the policies.") }, + { .description = N_ ("5. Enter secret"), + .regex = "^SECRET_EDITING$", + .tooltip = N_ ("Please specify the secret to be backed up.") }, + { .description = N_ ("6. Payment (optional)"), + .regex = "^.*_PAYING$", + .tooltip = N_ ( + "Some Anastasis providers require payment for their service. Please pay.") }, + { .description = N_ ("7. Backup completed"), + .regex = "BACKUP_FINISHED", + .tooltip = N_ ( + "Your backup is complete. Please do test the recovery on occasion.") }, + { .description = NULL } +}; + + +/** + * The steps of a recovery. + */ +static const struct Step recovery_steps[] = { + { .description = N_ ("1. Location & currency"), + .regex = "^(CONTINENT|COUNTRY)_SELECTING$", + .tooltip = N_ ( + "Where you live will determine which personal information can be used to identify your backup.") }, + { .description = N_ ("2. Personal information"), + .regex = "^USER_ATTRIBUTES_COLLECTING$", + .tooltip = N_ ( + "Your personal information will not be shared with anyone. But you must provide the same information during recovery.") }, + { .description = N_ ("3. Select secret"), + .regex = "^SECRET_SELECTING$", + .tooltip = N_ ( + "Please select which secret to recover. You may switch to a different version or provider.") }, + { .description = N_ ("4. Solve challenges"), + .regex = "^CHALLENGE_SELECTING$", + .tooltip = N_ ( + "Please select an authentication challenge to pass to recover the secret.") }, + { .description = N_ ("5. Secret recovered"), + .regex = "^RECOVERY_FINISHED$", + .tooltip = N_ ("Your secret was successfully recovered.") }, + { .description = NULL } +}; + + +/** + * Fill the list called @a name with the given @a steps. * - * @param gesture the click gesture of the tree view + * @param name name of the model in the UI file + * @param steps the steps to show + */ +static void +fill_progress (const char *name, + const struct Step *steps) +{ + GListStore *list = AG_list (name); + + if (NULL == list) + return; + for (unsigned int i = 0; NULL != steps[i].description; i++) + AG_list_append (list, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + _ (steps[i].description)), + GNUNET_JSON_pack_string ("regex", + steps[i].regex), + GNUNET_JSON_pack_string ("tooltip", + _ (steps[i].tooltip)))); +} + + +void +AG_progress_setup (void) +{ + fill_progress ("anastasis_gtk_backup_progress_liststore", + backup_steps); + fill_progress ("anastasis_gtk_recovery_progress_liststore", + recovery_steps); +} + + +/** + * The progress lists only report where we are in the process; the user must + * not be able to change the selection by clicking. The gesture runs in the + * capture phase, so claiming the sequence here keeps the click from ever + * reaching the list. + * + * @param gesture the click gesture of the list * @param n_press number of presses * @param x horizontal position of the click * @param y vertical position of the click @@ -96,10 +218,11 @@ validate_regex (const char *input, void AG_progress_update (void) { - GtkTreeSelection *ts; - GtkTreeModel *tm; - GtkTreeIter iter; + const char *sel_name; + GtkSingleSelection *sel; + GListModel *steps; const char *state; + guint n; state = json_string_value (json_object_get (AG_redux_state, "backup_state")); @@ -112,45 +235,47 @@ AG_progress_update (void) GNUNET_break (0); return; } - ts = GTK_TREE_SELECTION (GCG_get_main_window_object ( - "anastasis_gtk_recovery_progress_tree_selection") - ); + sel_name = "anastasis_gtk_recovery_progress_tree_selection"; + steps = G_LIST_MODEL (AG_list ( + "anastasis_gtk_recovery_progress_liststore")); } else { - ts = GTK_TREE_SELECTION (GCG_get_main_window_object ( - "anastasis_gtk_backup_progress_tree_selection")); + sel_name = "anastasis_gtk_backup_progress_tree_selection"; + steps = G_LIST_MODEL (AG_list ( + "anastasis_gtk_backup_progress_liststore")); } - if (! gtk_tree_selection_get_selected (ts, - &tm, - &iter)) - { + if (NULL == steps) return; - } - if (! gtk_tree_model_get_iter_first (tm, - &iter)) + sel = GTK_SINGLE_SELECTION (GCG_get_main_window_object (sel_name)); + if (NULL == sel) { GNUNET_break (0); return; } - do { - char *regex; - - gtk_tree_model_get (tm, - &iter, - AG_PRGMC_REGEX, &regex, - -1); - if (validate_regex (state, - regex)) + /* We only ever move an existing selection; as nothing selects a step to + begin with, the lists show no step as the current one. This is how the + GTK3 version behaved as well. */ + if (GTK_INVALID_LIST_POSITION == + gtk_single_selection_get_selected (sel)) + return; + n = g_list_model_get_n_items (steps); + for (guint i = 0; i < n; i++) + { + AGRow *row = g_list_model_get_item (steps, + i); + bool match; + + match = validate_regex (state, + AG_row_string (row, + "regex")); + g_object_unref (row); + if (match) { - g_free (regex); - gtk_tree_selection_select_iter (ts, - &iter); + AG_select_row (sel_name, + i); return; } - g_free (regex); - } while (gtk_tree_model_iter_next (tm, - &iter)); + } GNUNET_break (0); - return; } diff --git a/src/anastasis/anastasis-gtk_progress.h b/src/anastasis/anastasis-gtk_progress.h @@ -27,6 +27,14 @@ /** + * Fill the two lists of steps. To be called once, after the main window + * was built. + */ +void +AG_progress_setup (void); + + +/** * Update progress indicators. */ void diff --git a/src/anastasis/anastasis-gtk_rows.c b/src/anastasis/anastasis-gtk_rows.c @@ -0,0 +1,610 @@ +/* + This file is part of anastasis-gtk. + Copyright (C) 2025 Anastasis SARL + + Anastasis 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, or (at your + option) any later version. + + Anastasis 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 Anastasis; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + * @file src/anastasis/anastasis-gtk_rows.c + * @brief Rows of the GtkColumnViews and the models holding them + * @author Christian Grothoff + */ +#include <gnunet/gnunet_util_lib.h> +#include "anastasis-gtk_helper.h" +#include "anastasis-gtk_rows.h" + + +/** + * Key under which the row a cell shows is attached to the widget of that + * cell. This is how #AG_row_at() finds the row that was clicked. + */ +#define ROW_KEY "anastasis-row" + + +/** + * One row of one of our lists. + */ +struct _AGRow +{ + /** + * We are a GObject. + */ + GObject parent_instance; + + /** + * The values of this row. + */ + json_t *fields; + + /** + * Image to render this row with, or NULL. + */ + GdkPaintable *image; + + /** + * Rows nested under this one, or NULL if there are none. + */ + GListStore *children; +}; + +G_DEFINE_TYPE (AGRow, + AG_row, + G_TYPE_OBJECT); + + +static void +AG_row_finalize (GObject *object) +{ + AGRow *row = AG_ROW (object); + + json_decref (row->fields); + row->fields = NULL; + g_clear_object (&row->image); + g_clear_object (&row->children); + G_OBJECT_CLASS (AG_row_parent_class)->finalize (object); +} + + +static void +AG_row_class_init (AGRowClass *klass) +{ + G_OBJECT_CLASS (klass)->finalize = &AG_row_finalize; +} + + +static void +AG_row_init (AGRow *row) +{ + row->fields = json_object (); + GNUNET_assert (NULL != row->fields); +} + + +AGRow * +AG_row_new (json_t *fields) +{ + AGRow *row; + + GNUNET_assert (json_is_object (fields)); + row = g_object_new (AG_TYPE_ROW, + NULL); + json_decref (row->fields); + row->fields = fields; + return row; +} + + +void +AG_row_set (AGRow *row, + const char *field, + json_t *value) +{ + GNUNET_assert (0 == + json_object_set_new (row->fields, + field, + value)); +} + + +const char * +AG_row_string (AGRow *row, + const char *field) +{ + return json_string_value (json_object_get (row->fields, + field)); +} + + +bool +AG_row_bool (AGRow *row, + const char *field) +{ + return json_boolean_value (json_object_get (row->fields, + field)); +} + + +uint64_t +AG_row_uint (AGRow *row, + const char *field) +{ + return (uint64_t) json_integer_value (json_object_get (row->fields, + field)); +} + + +json_t * +AG_row_json (AGRow *row, + const char *field) +{ + return json_object_get (row->fields, + field); +} + + +void +AG_row_set_image (AGRow *row, + GdkPaintable *image) +{ + g_clear_object (&row->image); + row->image = image; +} + + +GdkPaintable * +AG_row_image (AGRow *row) +{ + return row->image; +} + + +GListStore * +AG_row_children (AGRow *row) +{ + if (NULL == row->children) + row->children = g_list_store_new (AG_TYPE_ROW); + return row->children; +} + + +AGRow * +AG_row_of (gpointer item) +{ + if (NULL == item) + return NULL; + if (GTK_IS_TREE_LIST_ROW (item)) + { + AGRow *row; + + row = gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (item)); + if (NULL == row) + return NULL; + /* we hand out a borrowed reference; the tree list row holds one of + its own for as long as it exists */ + g_object_unref (row); + return AG_ROW (row); + } + return AG_ROW (item); +} + + +GListStore * +AG_list_of (GtkBuilder *builder, + const char *name) +{ + GObject *model; + + model = gtk_builder_get_object (builder, + name); + if (NULL == model) + { + GNUNET_break (0); + return NULL; + } + return G_LIST_STORE (model); +} + + +GListStore * +AG_list (const char *name) +{ + GObject *model; + + model = GCG_get_main_window_object (name); + if (NULL == model) + { + GNUNET_break (0); + return NULL; + } + return G_LIST_STORE (model); +} + + +void +AG_list_clear (const char *name) +{ + GListStore *list = AG_list (name); + + if (NULL == list) + return; + g_list_store_remove_all (list); +} + + +void +AG_list_add_row (GListStore *list, + AGRow *row) +{ + g_list_store_append (list, + row); + g_object_unref (row); +} + + +AGRow * +AG_list_append (GListStore *list, + json_t *fields) +{ + AGRow *row = AG_row_new (fields); + + g_list_store_append (list, + row); + g_object_unref (row); + return row; +} + + +AGRow * +AG_list_add (const char *name, + json_t *fields) +{ + GListStore *list = AG_list (name); + + if (NULL == list) + { + json_decref (fields); + return NULL; + } + return AG_list_append (list, + fields); +} + + +AGRow * +AG_selected (GtkSingleSelection *selection) +{ + return AG_row_of (gtk_single_selection_get_selected_item (selection)); +} + + +/** + * Return the selection called @a name of the main window. + * + * @param name name of the selection in the UI file + * @return NULL on error + */ +static GtkSingleSelection * +main_window_selection (const char *name) +{ + GObject *sel; + + sel = GCG_get_main_window_object (name); + if (NULL == sel) + { + GNUNET_break (0); + return NULL; + } + return GTK_SINGLE_SELECTION (sel); +} + + +AGRow * +AG_selected_row (const char *name) +{ + GtkSingleSelection *sel = main_window_selection (name); + + if (NULL == sel) + return NULL; + return AG_selected (sel); +} + + +void +AG_select_row (const char *name, + guint position) +{ + GtkSingleSelection *sel = main_window_selection (name); + + if (NULL == sel) + return; + gtk_selection_model_select_item (GTK_SELECTION_MODEL (sel), + position, + TRUE); +} + + +/** + * Search the descendants of @a widget for the cell of a row that covers + * @a y in the coordinates of @a view. + * + * @param widget widget to search below + * @param view the column view @a y refers to + * @param y vertical position to look for + * @return NULL if no cell of any row covers @a y + */ +static AGRow * +find_row_at (GtkWidget *widget, + GtkWidget *view, + double y) +{ + for (GtkWidget *child = gtk_widget_get_first_child (widget); + NULL != child; + child = gtk_widget_get_next_sibling (child)) + { + AGRow *row; + GtkWidget *cell; + graphene_rect_t bounds; + + row = g_object_get_data (G_OBJECT (child), + ROW_KEY); + if (NULL == row) + { + row = find_row_at (child, + view, + y); + if (NULL != row) + return row; + continue; + } + /* our widget does not fill the cell it was put into, so we measure + the cell, which does tile the list without gaps */ + cell = gtk_widget_get_parent (child); + if (! gtk_widget_compute_bounds ((NULL != cell) ? cell : child, + view, + &bounds)) + continue; + if ( (y >= bounds.origin.y) && + (y < bounds.origin.y + bounds.size.height) ) + return row; + } + return NULL; +} + + +AGRow * +AG_row_at (GtkWidget *view, + double y) +{ + return find_row_at (view, + view, + y); +} + + +/** + * Return the @a field of @a row as a number, mapping true to 1. + * + * @param row the row to inspect + * @param field name of the field + * @return 0 if there is no such field + */ +static json_int_t +row_number (AGRow *row, + const char *field) +{ + json_t *val = AG_row_json (row, + field); + + if (json_is_boolean (val)) + return json_is_true (val) ? 1 : 0; + return json_integer_value (val); +} + + +/** + * Compare two rows by the field named in @a cls. + * + * @param a first row + * @param b second row + * @param cls the `struct AG_SortSpec` of the column being sorted + * @return -1, 0 or 1 + */ +static gint +compare_rows (gconstpointer a, + gconstpointer b, + gpointer cls) +{ + const struct AG_SortSpec *spec = cls; + AGRow *ra = AG_row_of ((gpointer) a); + AGRow *rb = AG_row_of ((gpointer) b); + + if (spec->numeric) + { + json_int_t na = row_number (ra, + spec->field); + json_int_t nb = row_number (rb, + spec->field); + + if (na < nb) + return -1; + if (na > nb) + return 1; + return 0; + } + { + const char *sa = AG_row_string (ra, + spec->field); + const char *sb = AG_row_string (rb, + spec->field); + + if (NULL == sa) + sa = ""; + if (NULL == sb) + sb = ""; + return g_utf8_collate (sa, + sb); + } +} + + +void +AG_list_make_sortable (GtkBuilder *builder, + const char *view, + const char *sort_model, + const struct AG_SortSpec *specs) +{ + GtkColumnView *cv; + GtkSortListModel *slm; + + cv = GTK_COLUMN_VIEW (gtk_builder_get_object (builder, + view)); + slm = GTK_SORT_LIST_MODEL (gtk_builder_get_object (builder, + sort_model)); + if ( (NULL == cv) || + (NULL == slm) ) + { + GNUNET_break (0); + return; + } + for (unsigned int i = 0; NULL != specs[i].column; i++) + { + GtkColumnViewColumn *col; + GtkSorter *sorter; + + col = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, + specs[i].column)); + if (NULL == col) + { + GNUNET_break (0); + continue; + } + sorter = GTK_SORTER (gtk_custom_sorter_new (&compare_rows, + (gpointer) &specs[i], + NULL)); + gtk_column_view_column_set_sorter (col, + sorter); + g_object_unref (sorter); + } + /* clicking a column header now sorts the rows the view shows */ + gtk_sort_list_model_set_sorter (slm, + gtk_column_view_get_sorter (cv)); +} + + +/** + * Return the rows nested under @a item, if any. + * + * @param item the parent row + * @param cls NULL + * @return NULL if @a item has no children + */ +static GListModel * +create_child_model (gpointer item, + gpointer cls) +{ + AGRow *row = AG_ROW (item); + + (void) cls; + if (NULL == row->children) + return NULL; + return G_LIST_MODEL (g_object_ref (row->children)); +} + + +/** + * Columns of the list of authentication methods that can be sorted by + * clicking their header. + */ +static const struct AG_SortSpec auth_method_sorts[] = { + { .column = "auth_method_type", + .field = "type" }, + { .column = NULL } +}; + + +/** + * Columns of the list of secrets that can be sorted by clicking their + * header. + */ +static const struct AG_SortSpec secret_sorts[] = { + { .column = "secret_name_column", + .field = "secret_name" }, + { .column = "backup_time_column", + .field = "date", + .numeric = true }, + { .column = NULL } +}; + + +void +AG_lists_setup (void) +{ + GtkBuilder *builder = ANASTASIS_GTK_main_loop_get_builder (AG_ml); + + /* the policy review shows a tree, which the UI file cannot express */ + { + GListStore *policies; + GtkTreeListModel *tlm; + GtkSingleSelection *sel; + + policies = AG_list ("policy_review_treestore"); + sel = main_window_selection ("anastasis_gtk_review_policy_selection"); + if ( (NULL == policies) || + (NULL == sel) ) + { + GNUNET_break (0); + return; + } + tlm = gtk_tree_list_model_new (G_LIST_MODEL (g_object_ref (policies)), + FALSE, /* we want the GtkTreeListRows */ + TRUE, /* expand all */ + &create_child_model, + NULL, + NULL); + gtk_single_selection_set_model (sel, + G_LIST_MODEL (tlm)); + g_object_unref (tlm); + } + AG_list_make_sortable (builder, + "anastasis_gtk_authentication_methods_list", + "authentication_methods_sorted", + auth_method_sorts); + AG_list_make_sortable (builder, + "anastasis_secret_selection_treeview", + "secret_selection_sorted", + secret_sorts); +} + + +void +AG_cell_set_row (GtkWidget *widget, + AGRow *row) +{ + g_object_set_data (G_OBJECT (widget), + ROW_KEY, + row); +} + + +AGRow * +AG_cell_get_row (GtkWidget *widget) +{ + return g_object_get_data (G_OBJECT (widget), + ROW_KEY); +} + + +/* end of anastasis-gtk_rows.c */ diff --git a/src/anastasis/anastasis-gtk_rows.h b/src/anastasis/anastasis-gtk_rows.h @@ -0,0 +1,354 @@ +/* + This file is part of anastasis-gtk. + Copyright (C) 2025 Anastasis SARL + + Anastasis 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, or (at your + option) any later version. + + Anastasis 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 Anastasis; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + * @file src/anastasis/anastasis-gtk_rows.h + * @brief Rows of the GtkColumnViews and the models holding them + * @author Christian Grothoff + */ +#ifndef ANASTASIS_GTK_ROWS_H +#define ANASTASIS_GTK_ROWS_H + +#include <gtk/gtk.h> +#include <jansson.h> + + +/** + * One row of one of our lists. The values shown in the cells and the + * bookkeeping data that goes with them are kept as JSON, which is also + * what the reducer state we build the rows from is made of. + */ +#define AG_TYPE_ROW (AG_row_get_type ()) + +G_DECLARE_FINAL_TYPE (AGRow, + AG_row, + AG, + ROW, + GObject); + + +/** + * Create a row with the given @a fields. + * + * @param fields the values of the row, ownership is taken + * @return the new row + */ +AGRow * +AG_row_new (json_t *fields); + + +/** + * Change the @a field of @a row to @a value. + * + * @param row the row to update + * @param field name of the field + * @param value new value, ownership is taken + */ +void +AG_row_set (AGRow *row, + const char *field, + json_t *value); + + +/** + * Obtain the string @a field of @a row. + * + * @param row the row to inspect + * @param field name of the field + * @return NULL if there is no such string field + */ +const char * +AG_row_string (AGRow *row, + const char *field); + + +/** + * Obtain the boolean @a field of @a row. + * + * @param row the row to inspect + * @param field name of the field + * @return false if there is no such field + */ +bool +AG_row_bool (AGRow *row, + const char *field); + + +/** + * Obtain the numeric @a field of @a row. + * + * @param row the row to inspect + * @param field name of the field + * @return 0 if there is no such field + */ +uint64_t +AG_row_uint (AGRow *row, + const char *field); + + +/** + * Obtain the @a field of @a row as JSON. + * + * @param row the row to inspect + * @param field name of the field + * @return NULL if there is no such field, otherwise + * a value borrowed from @a row + */ +json_t * +AG_row_json (AGRow *row, + const char *field); + + +/** + * Set the image @a row is to be rendered with. + * + * @param row the row to update + * @param image the image, ownership is taken; may be NULL + */ +void +AG_row_set_image (AGRow *row, + GdkPaintable *image); + + +/** + * Return the image @a row is rendered with. + * + * @param row the row to inspect + * @return NULL if @a row has no image + */ +GdkPaintable * +AG_row_image (AGRow *row); + + +/** + * Return the rows nested under @a row, creating the list on first use. + * Only rows of a tree ever have children. + * + * @param row the parent row + * @return the children of @a row, owned by @a row + */ +GListStore * +AG_row_children (AGRow *row); + + +/* ***************** the lists of rows ****************** */ + + +/** + * Return the list of rows called @a name in the main window. + * + * @param name name of the model in the UI file + * @return the model, NULL on error + */ +GListStore * +AG_list (const char *name); + + +/** + * Return the list of rows called @a name in the window @a builder built. + * + * @param builder builder of the window holding the list + * @param name name of the model in the UI file + * @return the model, NULL on error + */ +GListStore * +AG_list_of (GtkBuilder *builder, + const char *name); + + +/** + * Remove all rows from the list called @a name in the main window. + * + * @param name name of the model in the UI file + */ +void +AG_list_clear (const char *name); + + +/** + * Append a row with the given @a fields to @a list. + * + * @param list list to append to + * @param fields the values of the row, ownership is taken + * @return the new row, owned by @a list + */ +AGRow * +AG_list_append (GListStore *list, + json_t *fields); + + +/** + * Append @a row to @a list. Rows that have children must be filled + * before they are added, as only then does the list learn that they can + * be expanded. + * + * @param list list to append to + * @param row the row to append, ownership is taken + */ +void +AG_list_add_row (GListStore *list, + AGRow *row); + + +/** + * Append a row with the given @a fields to the list called @a name in + * the main window. + * + * @param name name of the model in the UI file + * @param fields the values of the row, ownership is taken + * @return the new row, owned by the list + */ +AGRow * +AG_list_add (const char *name, + json_t *fields); + + +/** + * Return the row @a item stands for. Rows of a tree are wrapped into a + * `GtkTreeListRow` by the model, this unwraps them. + * + * @param item item of a list model, may be NULL + * @return NULL if @a item is NULL + */ +AGRow * +AG_row_of (gpointer item); + + +/** + * Attach the row a cell shows to the @a widget rendering it, so that + * #AG_row_at() can find the row a click landed on. + * + * @param widget widget rendering the cell + * @param row the row being rendered, may be NULL + */ +void +AG_cell_set_row (GtkWidget *widget, + AGRow *row); + + +/** + * Return the row the cell rendered by @a widget shows. + * + * @param widget widget rendering a cell + * @return NULL if @a widget is not bound to a row + */ +AGRow * +AG_cell_get_row (GtkWidget *widget); + + +/* ******************* selections ********************* */ + + +/** + * Return the row that is selected in @a selection. + * + * @param selection the selection to inspect + * @return NULL if nothing is selected + */ +AGRow * +AG_selected (GtkSingleSelection *selection); + + +/** + * Return the row selected in the selection called @a name of the main + * window. + * + * @param name name of the selection in the UI file + * @return NULL if nothing is selected + */ +AGRow * +AG_selected_row (const char *name); + + +/** + * Select the row at @a position in the selection called @a name of the + * main window. + * + * @param name name of the selection in the UI file + * @param position index of the row to select + */ +void +AG_select_row (const char *name, + guint position); + + +/** + * Return the row shown at @a y in @a view. Used to find the row a + * context menu was opened on. + * + * @param view the column view that was clicked + * @param y vertical position of the click, in widget coordinates + * @return NULL if there is no row at @a y + */ +AGRow * +AG_row_at (GtkWidget *view, + double y); + + +/* ****************** sorting ******************* */ + + +/** + * How a column of a list is to be sorted. + */ +struct AG_SortSpec +{ + /** + * Name of the `GtkColumnViewColumn` in the UI file. + */ + const char *column; + + /** + * Field of the rows to compare. + */ + const char *field; + + /** + * True to compare the field numerically instead of as text. + */ + bool numeric; +}; + + +/** + * Make the columns given in @a specs of the column view called @a view + * sort the rows of @a sort_model. Both objects are looked up in @a + * builder. + * + * @param builder builder of the window holding the list + * @param view name of the `GtkColumnView` in the UI file + * @param sort_model name of the `GtkSortListModel` feeding @a view + * @param specs the columns to make sortable, terminated by a NULL column + */ +void +AG_list_make_sortable (GtkBuilder *builder, + const char *view, + const char *sort_model, + const struct AG_SortSpec *specs); + + +/** + * Set up those models of the main window that cannot be described in + * the UI file. To be called once, after the main window was built. + */ +void +AG_lists_setup (void); + + +#endif diff --git a/src/anastasis/meson.build b/src/anastasis/meson.build @@ -4,6 +4,7 @@ anastasis_gtk_SOURCES = [ 'anastasis-gtk_action.c', 'anastasis-gtk_attributes.c', 'anastasis-gtk_autocomplete.c', + 'anastasis-gtk_cells.c', 'anastasis-gtk_dispatch.c', 'anastasis-gtk_io.c', 'anastasis-gtk_handle-add-provider.c', @@ -17,7 +18,6 @@ anastasis_gtk_SOURCES = [ 'anastasis-gtk_handle-clear-secret-clicked.c', 'anastasis-gtk_handle-continent-selected.c', 'anastasis-gtk_handle-country-activated.c', - 'anastasis-gtk_handle-country-unselected.c', 'anastasis-gtk_handle-core-secret-changed.c', 'anastasis-gtk_handle-core-secret-name-changed.c', 'anastasis-gtk_handle-expiration-change.c', @@ -43,6 +43,7 @@ anastasis_gtk_SOURCES = [ 'anastasis-gtk_pe-delete-policy.c', 'anastasis-gtk_pe-edit-policy.c', 'anastasis-gtk_progress.c', + 'anastasis-gtk_rows.c', ] if haru_dep.found() diff --git a/src/include/anastasis_gtk_util.h b/src/include/anastasis_gtk_util.h @@ -29,6 +29,12 @@ #define _(String) dgettext (PACKAGE, String) +/** + * Mark a string for translation without translating it right here, for + * strings in static tables. Translate them with #_() where they are used. + */ +#define N_(String) String + #define DIR_SEPARATOR_STR "/" #define DIR_SEPARATOR '/' @@ -117,13 +123,13 @@ ANASTASIS_GTK_from_loc_to_utf8 (const char *str_loc); /** - * Returns filename from a file chooser, encoded in UTF-8. + * Returns the path of @a file, encoded in UTF-8. * - * @param fc file chooser to inspect - * @return selected filename as UTF-8, NULL on errors + * @param fc the file the user picked, may be NULL + * @return path of @a file as UTF-8, NULL on errors */ char * -ANASTASIS_GTK_filechooser_get_filename_utf8 (GtkFileChooser *fc); +ANASTASIS_GTK_file_get_path_utf8 (GFile *file); /** diff --git a/src/util/builder.c b/src/util/builder.c @@ -115,20 +115,4 @@ ANASTASIS_GTK_get_new_builder (const struct GNUNET_OS_ProjectData *pd, } -void -GNUNET_FS_GTK_remove_treestore_subtree (GtkTreeStore *ts, - GtkTreeIter *root) -{ - GtkTreeIter child; - - while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ts), - &child, - root)) - GNUNET_FS_GTK_remove_treestore_subtree (ts, - &child); - gtk_tree_store_remove (ts, - root); -} - - /* end of builder.c */ diff --git a/src/util/nls.c b/src/util/nls.c @@ -134,17 +134,14 @@ from_filename_to_utf8 (gchar *filename) char * -ANASTASIS_GTK_filechooser_get_filename_utf8 (GtkFileChooser *fc) +ANASTASIS_GTK_file_get_path_utf8 (GFile *file) { char *filename_utf8; - GFile *file; gchar *filename; - file = gtk_file_chooser_get_file (fc); if (NULL == file) return NULL; filename = g_file_get_path (file); - g_object_unref (file); if (NULL == filename) return NULL; filename_utf8 = from_filename_to_utf8 (filename);