anastasis-gtk_handle-policy-meta.c (8750B)
1 /* 2 This file is part of anastasis-gtk. 3 Copyright (C) 2021 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 Anastasis is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with Anastasis; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @file src/anastasis/anastasis-gtk_handle-policy-meta.c 23 * @brief Handle right-click context menu in policy review 24 * @author Christian Grothoff 25 */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include "anastasis_gtk_util.h" 28 #include "anastasis-gtk_action.h" 29 #include "anastasis-gtk_helper.h" 30 #include "anastasis-gtk_pe.h" 31 #include "anastasis-gtk_rows.h" 32 #include <jansson.h> 33 34 35 /** 36 * Context for menu callbacks. 37 */ 38 struct MenuContext 39 { 40 /** 41 * The row that the user right-clicked, NULL if they clicked past the 42 * last one. 43 */ 44 AGRow *row; 45 46 }; 47 48 49 /** 50 * Free a `struct MenuContext`. Called when the action group holding it goes 51 * away, which happens on the next right-click at the latest. 52 * 53 * @param cls the `struct MenuContext` to free 54 */ 55 static void 56 free_menu_context (gpointer cls) 57 { 58 struct MenuContext *ctx = cls; 59 60 g_clear_object (&ctx->row); 61 GNUNET_free (ctx); 62 } 63 64 65 /** 66 * The user selected the 'add policy' menu item. 67 * 68 * @param action the activated action 69 * @param parameter unused 70 * @param user_data a `struct MenuContext` 71 */ 72 static void 73 add_from_ctx_menu (GSimpleAction *action, 74 GVariant *parameter, 75 gpointer user_data) 76 { 77 (void) action; 78 (void) parameter; 79 (void) user_data; 80 AG_add_policy (); 81 } 82 83 84 /** 85 * The user selected the 'delete challenge' or 'delete policy' menu item. 86 * 87 * @param action the activated action 88 * @param parameter unused 89 * @param user_data a `struct MenuContext` 90 */ 91 static void 92 delete_from_ctx_menu (GSimpleAction *action, 93 GVariant *parameter, 94 gpointer user_data) 95 { 96 struct MenuContext *ctx = user_data; 97 guint pindex; 98 99 (void) action; 100 (void) parameter; 101 if (NULL == ctx->row) 102 return; 103 pindex = AG_row_uint (ctx->row, 104 "policy_index"); 105 if (AG_row_bool (ctx->row, 106 "is_challenge")) 107 AG_delete_challenge (pindex, 108 AG_row_uint (ctx->row, 109 "method_index")); 110 else 111 AG_delete_policy (pindex); 112 } 113 114 115 /** 116 * The user selected the 'edit policy' menu item. 117 * 118 * @param action the activated action 119 * @param parameter unused 120 * @param user_data a `struct MenuContext` 121 */ 122 static void 123 edit_from_ctx_menu (GSimpleAction *action, 124 GVariant *parameter, 125 gpointer user_data) 126 { 127 struct MenuContext *ctx = user_data; 128 129 (void) action; 130 (void) parameter; 131 if (NULL == ctx->row) 132 return; 133 AG_edit_policy (AG_row_uint (ctx->row, 134 "policy_index")); 135 } 136 137 138 /** 139 * Build the menu model applicable to the row that was clicked. The two 140 * sections are rendered with a separator in between. 141 * 142 * @param have_row true if the user clicked on a row 143 * @param is_challenge true if that row represents a challenge 144 * @return the menu model 145 */ 146 static GMenuModel * 147 build_menu (bool have_row, 148 bool is_challenge) 149 { 150 GMenu *menu = g_menu_new (); 151 152 if (have_row) 153 { 154 GMenu *section = g_menu_new (); 155 156 if (! is_challenge) 157 { 158 /* only show 'edit' entry for lines that are for an entire policy */ 159 g_menu_append (section, 160 _ ("_Edit policy..."), 161 "policy.edit"); 162 } 163 g_menu_append (section, 164 is_challenge 165 ? _ ("Delete challenge") 166 : _ ("Delete policy"), 167 "policy.delete"); 168 g_menu_append_section (menu, 169 NULL, 170 G_MENU_MODEL (section)); 171 g_object_unref (section); 172 } 173 { 174 GMenu *section = g_menu_new (); 175 176 g_menu_append (section, 177 _ ("_Add policy..."), 178 "policy.add"); 179 g_menu_append_section (menu, 180 NULL, 181 G_MENU_MODEL (section)); 182 g_object_unref (section); 183 } 184 return G_MENU_MODEL (menu); 185 } 186 187 188 /** 189 * Install the actions the context menu of @a tv activates; they operate on the 190 * row given by @a ctx. 191 * 192 * @param tv the list the menu belongs to 193 * @param ctx context to hand to the actions, ownership is taken 194 */ 195 static void 196 install_actions (GtkWidget *tv, 197 struct MenuContext *ctx) 198 { 199 static const GActionEntry entries[] = { 200 { .name = "add", 201 .activate = &add_from_ctx_menu }, 202 { .name = "edit", 203 .activate = &edit_from_ctx_menu }, 204 { .name = "delete", 205 .activate = &delete_from_ctx_menu } 206 }; 207 GSimpleActionGroup *actions; 208 209 actions = g_simple_action_group_new (); 210 g_action_map_add_action_entries (G_ACTION_MAP (actions), 211 entries, 212 G_N_ELEMENTS (entries), 213 ctx); 214 /* the action group owns the context; installing a new group on the next 215 right-click releases the previous one */ 216 g_object_set_data_full (G_OBJECT (actions), 217 "anastasis-menu-context", 218 ctx, 219 &free_menu_context); 220 gtk_widget_insert_action_group (tv, 221 "policy", 222 G_ACTION_GROUP (actions)); 223 g_object_unref (actions); 224 } 225 226 227 /** 228 * Return the context menu of @a tv, creating it on first use. A single 229 * popover is reused for the lifetime of the list; only its model and 230 * position change. 231 * 232 * @param tv the list 233 * @return the popover to show 234 */ 235 static GtkPopover * 236 get_popover (GtkWidget *tv) 237 { 238 GtkWidget *popover; 239 240 popover = g_object_get_data (G_OBJECT (tv), 241 "anastasis-context-menu"); 242 if (NULL == popover) 243 { 244 popover = gtk_popover_menu_new_from_model (NULL); 245 gtk_popover_set_has_arrow (GTK_POPOVER (popover), 246 FALSE); 247 gtk_widget_set_halign (popover, 248 GTK_ALIGN_START); 249 gtk_widget_set_parent (popover, 250 tv); 251 g_object_set_data_full (G_OBJECT (tv), 252 "anastasis-context-menu", 253 popover, 254 (GDestroyNotify) & gtk_widget_unparent); 255 } 256 return GTK_POPOVER (popover); 257 } 258 259 260 /** 261 * We got a right-click on the policy list: compute which menu items are 262 * applicable and display an appropriate menu. 263 * 264 * @param gesture the click gesture, restricted to button 3 by the UI file 265 * @param n_press number of presses 266 * @param x horizontal position of the click, in widget coordinates 267 * @param y vertical position of the click, in widget coordinates 268 * @param user_data NULL 269 */ 270 void 271 anastasis_gtk_review_policy_treeview_pressed_cb (GtkGestureClick *gesture, 272 gint n_press, 273 gdouble x, 274 gdouble y, 275 gpointer user_data) 276 { 277 GtkWidget *widget 278 = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); 279 struct MenuContext *ctx; 280 AGRow *row; 281 bool is_challenge = false; 282 283 (void) n_press; 284 (void) user_data; 285 ctx = GNUNET_new (struct MenuContext); 286 row = AG_row_at (widget, 287 y); 288 if (NULL != row) 289 { 290 ctx->row = g_object_ref (row); 291 is_challenge = AG_row_bool (row, 292 "is_challenge"); 293 } 294 install_actions (widget, 295 ctx); 296 { 297 GtkPopover *popover = get_popover (widget); 298 GMenuModel *model = build_menu (NULL != row, 299 is_challenge); 300 const GdkRectangle at = { 301 .x = (int) x, 302 .y = (int) y, 303 .width = 1, 304 .height = 1 305 }; 306 307 gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (popover), 308 model); 309 g_object_unref (model); 310 gtk_popover_set_pointing_to (popover, 311 &at); 312 gtk_popover_popup (popover); 313 } 314 }