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