anastasis-gtk_helper.c (11408B)
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_hide (w); 128 } 129 130 131 void 132 AG_show (const char *name) 133 { 134 GtkWidget *w; 135 136 w = GTK_WIDGET (GCG_get_main_window_object (name)); 137 if (NULL == w) 138 { 139 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 140 "Widget `%s' not found, cannot show it!\n", 141 name); 142 return; 143 } 144 gtk_widget_show (w); 145 } 146 147 148 void 149 AG_insensitive_children (const char *name) 150 { 151 GList *children; 152 153 children = gtk_container_get_children (GTK_CONTAINER ( 154 GCG_get_main_window_object (name))); 155 for (GList *iter = children; iter != NULL; iter = g_list_next (iter)) 156 gtk_widget_set_sensitive (GTK_WIDGET (iter->data), 157 false); 158 g_list_free (children); 159 } 160 161 162 void 163 AG_hide_children (const char *name) 164 { 165 GList *children; 166 167 children = gtk_container_get_children (GTK_CONTAINER ( 168 GCG_get_main_window_object (name))); 169 for (GList *iter = children; iter != NULL; iter = g_list_next (iter)) 170 gtk_widget_hide (GTK_WIDGET (iter->data)); 171 g_list_free (children); 172 } 173 174 175 void 176 AG_show_children (const char *name) 177 { 178 GList *children; 179 180 children = gtk_container_get_children (GTK_CONTAINER ( 181 GCG_get_main_window_object (name))); 182 for (GList *iter = children; iter != NULL; iter = g_list_next (iter)) 183 gtk_widget_show (GTK_WIDGET (iter->data)); 184 g_list_free (children); 185 186 } 187 188 189 void 190 AG_hide_all_frames (void) 191 { 192 AG_hide ("anastasis_gtk_start_frame"); 193 AG_hide_children ("anastasis_gtk_super_vbox"); 194 if (AG_have_error) 195 AG_show ("anastasis_gtk_error_label"); 196 } 197 198 199 bool 200 AG_check_state (json_t *state, 201 const char *expected_state) 202 { 203 const char *state_name = json_string_value (json_object_get (state, 204 "backup_state")); 205 if (NULL == state_name) 206 state_name = json_string_value (json_object_get (state, 207 "recovery_state")); 208 if (NULL == state_name) 209 return false; 210 return (0 == strcasecmp (state_name, 211 expected_state)); 212 } 213 214 215 /** 216 * Get an object from the main window. 217 * 218 * @param name name of the object 219 * @return NULL on error 220 */ 221 GObject * 222 GCG_get_main_window_object (const char *name) 223 { 224 if (NULL == AG_ml) 225 return NULL; 226 return ANASTASIS_GTK_main_loop_get_object (AG_ml, 227 name); 228 } 229 230 231 /** 232 * Make the 'next' button sensitive (and trigger it via 'Return'). 233 */ 234 void 235 AG_enable_next (void) 236 { 237 GtkWidget *fwd; 238 239 AG_show ("anastasis_gtk_main_window_forward_button"); 240 AG_sensitive ("anastasis_gtk_main_window_forward_button"); 241 fwd = GTK_WIDGET (GCG_get_main_window_object ( 242 "anastasis_gtk_main_window_forward_button")); 243 gtk_widget_set_can_default (fwd, 244 true); 245 gtk_widget_grab_default (fwd); 246 247 } 248 249 250 void 251 AG_error_clear () 252 { 253 AG_have_error = false; 254 AG_hide ("anastasis_gtk_error_label"); 255 } 256 257 258 void 259 AG_error (const char *format, 260 ...) 261 { 262 va_list ap; 263 char *msg; 264 int ret; 265 GtkLabel *l; 266 267 va_start (ap, format); 268 ret = vasprintf (&msg, 269 format, 270 ap); 271 va_end (ap); 272 if (-1 == ret) 273 { 274 GNUNET_break (0); 275 return; 276 } 277 l = GTK_LABEL (GCG_get_main_window_object ("anastasis_gtk_error_label")); 278 if (NULL == l) 279 { 280 GNUNET_break (0); 281 return; 282 } 283 gtk_label_set_text (l, 284 msg); 285 free (msg); 286 AG_have_error = true; 287 gtk_widget_show (GTK_WIDGET (l)); 288 } 289 290 291 /** 292 * Create a the QR code image from a given @a text. 293 * 294 * @param scale factor for scaling up the size of the image to create 295 * @param text text to encode 296 * @return NULL on error 297 */ 298 static GdkPixbuf * 299 create_qrcode (unsigned int scale, 300 const char *text, 301 size_t text_size) 302 { 303 QRinput *qri; 304 QRcode *qrc; 305 GdkPixbuf *pb; 306 guchar *pixels; 307 int n_channels; 308 const char *dir; 309 char *fn; 310 unsigned int size; 311 312 qri = QRinput_new2 (0, QR_ECLEVEL_M); 313 if (NULL == qri) 314 { 315 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2"); 316 return NULL; 317 } 318 /* first try encoding as uppercase-only alpha-numerical 319 QR code (much smaller encoding); if that fails, also 320 try using binary encoding (in case nick contains 321 special characters). */ 322 if ((0 != QRinput_append (qri, 323 QR_MODE_AN, 324 text_size, 325 (unsigned char *) text)) && 326 (0 != QRinput_append (qri, 327 QR_MODE_8, 328 text_size, 329 (unsigned char *) text))) 330 { 331 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 332 "QRinput_append"); 333 return NULL; 334 } 335 qrc = QRcode_encodeInput (qri); 336 if (NULL == qrc) 337 { 338 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 339 "QRcode_encodeInput"); 340 QRinput_free (qri); 341 return NULL; 342 } 343 /* We use a trick to create a pixbuf in a way that works for both Gtk2 and 344 Gtk3 by loading a dummy file from disk; all other methods are not portable 345 to both Gtk2 and Gtk3. */ 346 dir = ANASTASIS_GTK_get_data_dir (ANASTASIS_GTK_project_data ()); 347 GNUNET_asprintf (&fn, 348 "%s%s", 349 dir, 350 "qr_dummy.png"); 351 size = (qrc->width + 8) * scale; 352 size += 8 - (size % 8); 353 pb = gdk_pixbuf_new_from_file_at_size (fn, 354 size, 355 size, 356 NULL); 357 GNUNET_free (fn); 358 if (NULL == pb) 359 { 360 QRcode_free (qrc); 361 QRinput_free (qri); 362 return NULL; 363 } 364 pixels = gdk_pixbuf_get_pixels (pb); 365 n_channels = gdk_pixbuf_get_n_channels (pb); 366 for (unsigned int x = 4 * scale; x < size - 4 * scale; x++) 367 for (unsigned int y = 4 * scale; y < size - 4 * scale; y++) 368 { 369 unsigned int xx = x - 4 * scale; 370 unsigned int yy = y - 4 * scale; 371 unsigned int ss = size - 8 * scale; 372 unsigned int off = 373 (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width; 374 for (int c = 0; c < n_channels; c++) 375 pixels[(y * size + x) * n_channels + c] = 376 (0 == (qrc->data[off] & 1)) ? 0xFF : 0; 377 } 378 QRcode_free (qrc); 379 QRinput_free (qri); 380 return pb; 381 } 382 383 384 GdkPixbuf * 385 AG_setup_qrcode (GtkWidget *w, 386 const char *text, 387 size_t text_size) 388 { 389 GdkScreen *screen; 390 GtkSettings *settings; 391 gint dpi; 392 int scale; 393 394 if (NULL == w) 395 { 396 GNUNET_break (0); 397 return NULL; 398 } 399 /* adjust scale to screen resolution */ 400 screen = gtk_widget_get_screen (w); 401 settings = gtk_settings_get_for_screen (screen); 402 g_object_get (G_OBJECT (settings), 403 "gtk-xft-dpi", 404 &dpi, 405 NULL); 406 if (-1 == dpi) 407 scale = 2; 408 else if (dpi >= 122800) 409 scale = 4; 410 else if (dpi >= 98304) 411 scale = 3; 412 else 413 scale = 2; 414 return create_qrcode (scale, 415 text, 416 text_size); 417 } 418 419 420 char * 421 AG_expand_name (const char *name, 422 const char *type) 423 { 424 static struct 425 { 426 const char *type; 427 const char *suffix; 428 } type_map [] = { 429 { .type = "string", 430 .suffix = "entry" }, 431 { .type = "date", 432 .suffix = "cal" }, 433 { .type = NULL, 434 .suffix = NULL } 435 }; 436 char *data_widget; 437 438 for (unsigned int i = 0; NULL != type_map[i].type; i++) 439 { 440 if (0 != strcmp (type_map[i].type, 441 type)) 442 continue; 443 GNUNET_asprintf (&data_widget, 444 "%s_%s", 445 name, 446 type_map[i].suffix); 447 return data_widget; 448 } 449 return NULL; 450 } 451 452 453 /** 454 * Show widget on focus-in event. 455 * 456 * @param w widget to show (called with 'swapped' true!) 457 * @param event the event 458 * @param widget the widget being focused on 459 * @return FALSE 460 */ 461 bool 462 anastasis_gtk_show_on_focus_in_event_cb (GtkWidget *w, 463 GdkEvent *event, 464 GtkWidget *widget) 465 { 466 gtk_widget_show (w); 467 return FALSE; 468 } 469 470 471 /** 472 * Hide widget on focus-out event. 473 * 474 * @param w widget to show (called with 'swapped' true!) 475 * @param event the event 476 * @param widget the widget being focused on 477 * @return FALSE 478 */ 479 bool 480 anastasis_gtk_hide_on_focus_out_event_cb (GtkWidget *w, 481 GdkEvent *event, 482 GtkWidget *widget) 483 { 484 gtk_widget_hide (w); 485 return FALSE; 486 }