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