Optimize automation-event process splitting
[ardour.git] / gtk2_ardour / ui_config.cc
1 /*
2     Copyright (C) 1999-2014 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
21 #define OPTIONAL_CAIRO_IMAGE_SURFACE
22 #endif
23
24 #include <iostream>
25 #include <sstream>
26 #include <unistd.h>
27 #include <cstdlib>
28 #include <cstdio> /* for snprintf, grrr */
29
30 #include <cairo/cairo.h>
31
32 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
33 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
34
35 #include <glibmm/miscutils.h>
36
37 #include <gtkmm/settings.h>
38
39 #include "pbd/convert.h"
40 #include "pbd/error.h"
41 #include "pbd/failed_constructor.h"
42 #include "pbd/file_utils.h"
43 #include "pbd/gstdio_compat.h"
44 #include "pbd/unwind.h"
45 #include "pbd/xml++.h"
46
47 #include "ardour/filesystem_paths.h"
48 #include "ardour/search_paths.h"
49 #include "ardour/revision.h"
50 #include "ardour/utils.h"
51 #include "ardour/types_convert.h"
52
53 #include "gtkmm2ext/rgb_macros.h"
54 #include "gtkmm2ext/gtk_ui.h"
55
56 #include "ui_config.h"
57
58 #include "pbd/i18n.h"
59
60 using namespace std;
61 using namespace PBD;
62 using namespace ARDOUR;
63 using namespace Gtkmm2ext;
64
65 static const char* ui_config_file_name = "ui_config";
66 static const char* default_ui_config_file_name = "default_ui_config";
67
68 static const double hue_width = 18.0;
69 std::string UIConfiguration::color_file_suffix = X_(".colors");
70
71 UIConfiguration&
72 UIConfiguration::instance ()
73 {
74         static UIConfiguration s_instance;
75         _instance = &s_instance;
76         return s_instance;
77 }
78
79 UIConfiguration::UIConfiguration ()
80         :
81 #undef  UI_CONFIG_VARIABLE
82 #define UI_CONFIG_VARIABLE(Type,var,name,val) var (name,val),
83 #define CANVAS_FONT_VARIABLE(var,name) var (name),
84 #include "ui_config_vars.h"
85 #include "canvas_vars.h"
86 #undef  UI_CONFIG_VARIABLE
87 #undef  CANVAS_FONT_VARIABLE
88
89         _dirty (false),
90         aliases_modified (false),
91         colors_modified (false),
92         modifiers_modified (false),
93         block_save (0)
94 {
95         load_state();
96
97         ColorsChanged.connect (boost::bind (&UIConfiguration::colors_changed, this));
98
99         ParameterChanged.connect (sigc::mem_fun (*this, &UIConfiguration::parameter_changed));
100 }
101
102 UIConfiguration::~UIConfiguration ()
103 {
104 }
105
106 void
107 UIConfiguration::colors_changed ()
108 {
109         reset_gtk_theme ();
110
111         /* In theory, one of these ought to work:
112
113            gtk_rc_reparse_all_for_settings (gtk_settings_get_default(), true);
114            gtk_rc_reset_styles (gtk_settings_get_default());
115
116            but in practice, neither of them do. So just reload the current
117            GTK RC file, which causes a reset of all styles and a redraw
118         */
119
120         parameter_changed ("ui-rc-file");
121 }
122
123 void
124 UIConfiguration::parameter_changed (string param)
125 {
126         _dirty = true;
127
128         if (param == "ui-rc-file") {
129                 load_rc_file (true);
130         } else if (param == "color-file") {
131                 load_color_theme (true);
132         }
133
134         save_state ();
135 }
136
137 void
138 UIConfiguration::reset_gtk_theme ()
139 {
140         std::string color_scheme_string("gtk_color_scheme = \"");
141
142         for (ColorAliases::iterator g = color_aliases.begin(); g != color_aliases.end(); ++g) {
143
144                 if (g->first.find ("gtk_") == 0) {
145                         const string gtk_name = g->first.substr (4);
146                         Gtkmm2ext::Color a_color = color (g->second);
147
148                         color_scheme_string += gtk_name + ":#" + color_to_hex_string_no_alpha (a_color) + ';';
149                 }
150         }
151
152         color_scheme_string += '"';
153
154         /* reset GTK color scheme */
155
156         Gtk::Settings::get_default()->property_gtk_color_scheme() = color_scheme_string;
157 }
158
159 void
160 UIConfiguration::reset_dpi ()
161 {
162         long val = get_font_scale();
163
164         /* FT2 rendering - used by GnomeCanvas, sigh */
165
166 #ifndef PLATFORM_WINDOWS
167         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024); // XXX pango_ft2_font_map_new leaks
168 #endif
169
170         /* Cairo rendering, in case there is any */
171
172         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
173
174         /* Xft rendering */
175
176         gtk_settings_set_long_property (gtk_settings_get_default(),
177                                         "gtk-xft-dpi", val, "ardour");
178         DPIReset(); //Emit Signal
179 }
180
181 float
182 UIConfiguration::get_ui_scale ()
183 {
184         return get_font_scale () / 102400.;
185 }
186
187 void
188 UIConfiguration::map_parameters (boost::function<void (std::string)>& functor)
189 {
190 #undef  UI_CONFIG_VARIABLE
191 #define UI_CONFIG_VARIABLE(Type,var,Name,value) functor (Name);
192 #include "ui_config_vars.h"
193 #undef  UI_CONFIG_VARIABLE
194 }
195
196 int
197 UIConfiguration::pre_gui_init ()
198 {
199 #ifdef CAIRO_SUPPORTS_FORCE_BUGGY_GRADIENTS_ENVIRONMENT_VARIABLE
200         if (get_buggy_gradients()) {
201                 g_setenv ("FORCE_BUGGY_GRADIENTS", "1", 1);
202         }
203 #endif
204 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
205         if (get_cairo_image_surface()) {
206                 g_setenv ("ARDOUR_IMAGE_SURFACE", "1", 1);
207         }
208 #endif
209         return 0;
210 }
211
212 UIConfiguration*
213 UIConfiguration::post_gui_init ()
214 {
215         load_color_theme (true);
216         return this;
217 }
218
219 int
220 UIConfiguration::load_defaults ()
221 {
222         std::string rcfile;
223         int ret = -1;
224
225         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile) ) {
226                 XMLTree tree;
227
228                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
229
230                 if (!tree.read (rcfile.c_str())) {
231                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
232                 } else {
233                         if (set_state (*tree.root(), Stateful::loading_state_version)) {
234                                 error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
235                         } else {
236                                 _dirty = false;
237                                 ret = 0;
238                         }
239                 }
240
241         } else {
242                 warning << string_compose (_("Could not find default UI configuration file %1"), default_ui_config_file_name) << endmsg;
243         }
244
245         if (ret == 0) {
246                 /* reload color theme */
247                 load_color_theme (false);
248         }
249
250         return ret;
251 }
252
253 std::string
254 UIConfiguration::color_file_name (bool use_my, bool with_version) const
255 {
256         string basename;
257
258         if (use_my) {
259                 basename += "my-";
260         }
261
262         std::string color_name = color_file.get();
263         size_t sep = color_name.find_first_of("-");
264         if (sep != string::npos) {
265                 color_name = color_name.substr (0, sep);
266         }
267
268         basename += color_name;
269         basename += "-";
270         basename += downcase(std::string(PROGRAM_NAME));
271
272         std::string rev (revision);
273         std::size_t pos = rev.find_first_of("-");
274
275         if (with_version && pos != string::npos && pos > 0) {
276                 basename += "-";
277                 basename += rev.substr (0, pos); // COLORFILE_VERSION - program major.minor
278         }
279
280         basename += color_file_suffix;
281         return basename;
282 }
283
284 int
285 UIConfiguration::load_color_file (string const & path)
286 {
287         XMLTree tree;
288
289         info << string_compose (_("Loading color file %1"), path) << endmsg;
290
291         if (!tree.read (path.c_str())) {
292                 error << string_compose(_("cannot read color file \"%1\""), path) << endmsg;
293                 return -1;
294         }
295
296         if (set_state (*tree.root(), Stateful::loading_state_version)) {
297                 error << string_compose(_("color file \"%1\" not loaded successfully."), path) << endmsg;
298                 return -1;
299         }
300
301         return 0;
302 }
303
304 int
305 UIConfiguration::load_color_theme (bool allow_own)
306 {
307         std::string cfile;
308         bool found = false;
309         /* ColorsChanged() will trigger a  parameter_changed () which
310          * in turn calls save_state()
311          */
312         PBD::Unwinder<uint32_t> uw (block_save, block_save + 1);
313
314         if (find_file (theme_search_path(), color_file_name (false, true), cfile)) {
315                 found = true;
316         }
317
318         if (!found) {
319                 if (find_file (theme_search_path(), color_file_name (false, false), cfile)) {
320                         found = true;
321                 }
322         }
323
324         if (!found) {
325                 warning << string_compose (_("Color file for %1 not found along %2"), color_file.get(), theme_search_path().to_string()) << endmsg;
326                 return -1;
327         }
328
329         (void) load_color_file (cfile);
330
331         if (allow_own) {
332
333                 found = false;
334
335                 PBD::Searchpath sp (user_config_directory());
336
337                 /* user's own color files never have the program name in them */
338
339                 if (find_file (sp, color_file_name (true, true), cfile)) {
340                         found = true;
341                 }
342
343                 if (!found) {
344                         if (find_file (sp, color_file_name (true, false), cfile)) {
345                                 found = true;
346                         }
347                 }
348
349                 if (found) {
350                         (void) load_color_file (cfile);
351                 }
352
353         }
354
355         ColorsChanged ();
356
357         return 0;
358 }
359
360 int
361 UIConfiguration::store_color_theme ()
362 {
363         XMLNode* root;
364
365         root = new XMLNode("Ardour");
366
367         XMLNode* parent = new XMLNode (X_("Colors"));
368         for (Colors::const_iterator i = colors.begin(); i != colors.end(); ++i) {
369                 XMLNode* node = new XMLNode (X_("Color"));
370                 node->set_property (X_("name"), i->first);
371                 node->set_property (X_("value"), color_to_hex_string (i->second));
372                 parent->add_child_nocopy (*node);
373         }
374         root->add_child_nocopy (*parent);
375
376         parent = new XMLNode (X_("ColorAliases"));
377         for (ColorAliases::const_iterator i = color_aliases.begin(); i != color_aliases.end(); ++i) {
378                 XMLNode* node = new XMLNode (X_("ColorAlias"));
379                 node->set_property (X_("name"), i->first);
380                 node->set_property (X_("alias"), i->second);
381                 parent->add_child_nocopy (*node);
382         }
383         root->add_child_nocopy (*parent);
384
385         parent = new XMLNode (X_("Modifiers"));
386         for (Modifiers::const_iterator i = modifiers.begin(); i != modifiers.end(); ++i) {
387                 XMLNode* node = new XMLNode (X_("Modifier"));
388                 node->set_property (X_("name"), i->first);
389                 node->set_property (X_("modifier"), i->second.to_string());
390                 parent->add_child_nocopy (*node);
391         }
392         root->add_child_nocopy (*parent);
393
394         XMLTree tree;
395         std::string colorfile = Glib::build_filename (user_config_directory(), color_file_name (true, true));;
396
397         tree.set_root (root);
398
399         if (!tree.write (colorfile.c_str())){
400                 error << string_compose (_("Color file %1 not saved"), colorfile) << endmsg;
401                 return -1;
402         }
403
404         return 0;
405 }
406
407 int
408 UIConfiguration::load_state ()
409 {
410         bool found = false;
411
412         std::string rcfile;
413
414         if (find_file (ardour_config_search_path(), default_ui_config_file_name, rcfile)) {
415                 XMLTree tree;
416                 found = true;
417
418                 info << string_compose (_("Loading default ui configuration file %1"), rcfile) << endmsg;
419
420                 if (!tree.read (rcfile.c_str())) {
421                         error << string_compose(_("cannot read default ui configuration file \"%1\""), rcfile) << endmsg;
422                         return -1;
423                 }
424
425                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
426                         error << string_compose(_("default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
427                         return -1;
428                 }
429         }
430
431         if (find_file (ardour_config_search_path(), ui_config_file_name, rcfile)) {
432                 XMLTree tree;
433                 found = true;
434
435                 info << string_compose (_("Loading user ui configuration file %1"), rcfile) << endmsg;
436
437                 if (!tree.read (rcfile)) {
438                         error << string_compose(_("cannot read ui configuration file \"%1\""), rcfile) << endmsg;
439                         return -1;
440                 }
441
442                 if (set_state (*tree.root(), Stateful::loading_state_version)) {
443                         error << string_compose(_("user ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
444                         return -1;
445                 }
446
447                 _dirty = false;
448         }
449
450         if (!found) {
451                 error << _("could not find any ui configuration file, canvas will look broken.") << endmsg;
452         }
453
454         return 0;
455 }
456
457 int
458 UIConfiguration::save_state()
459 {
460         if (block_save != 0) {
461                 return -1;
462         }
463
464         if (_dirty) {
465                 std::string rcfile = Glib::build_filename (user_config_directory(), ui_config_file_name);
466
467                 XMLTree tree;
468
469                 tree.set_root (&get_state());
470
471                 if (!tree.write (rcfile.c_str())){
472                         error << string_compose (_("Config file %1 not saved"), rcfile) << endmsg;
473                         return -1;
474                 }
475
476                 _dirty = false;
477         }
478
479         if (aliases_modified || colors_modified || modifiers_modified) {
480
481                 if (store_color_theme ()) {
482                         error << string_compose (_("Color file %1 not saved"), color_file.get()) << endmsg;
483                         return -1;
484                 }
485
486                 aliases_modified = false;
487                 colors_modified = false;
488                 modifiers_modified = false;
489         }
490
491
492         return 0;
493 }
494
495 XMLNode&
496 UIConfiguration::get_state ()
497 {
498         XMLNode* root;
499
500         root = new XMLNode("Ardour");
501
502         root->add_child_nocopy (get_variables ("UI"));
503         root->add_child_nocopy (get_variables ("Canvas"));
504
505         if (_extra_xml) {
506                 root->add_child_copy (*_extra_xml);
507         }
508
509         return *root;
510 }
511
512 XMLNode&
513 UIConfiguration::get_variables (std::string which_node)
514 {
515         XMLNode* node;
516
517         node = new XMLNode (which_node);
518
519 #undef  UI_CONFIG_VARIABLE
520 #undef  CANVAS_FONT_VARIABLE
521 #define UI_CONFIG_VARIABLE(Type,var,Name,value) if (node->name() == "UI") { var.add_to_node (*node); }
522 #define CANVAS_FONT_VARIABLE(var,Name) if (node->name() == "Canvas") { var.add_to_node (*node); }
523 #include "ui_config_vars.h"
524 #include "canvas_vars.h"
525 #undef  UI_CONFIG_VARIABLE
526 #undef  CANVAS_FONT_VARIABLE
527
528         return *node;
529 }
530
531 int
532 UIConfiguration::set_state (const XMLNode& root, int /*version*/)
533 {
534         /* this can load a generic UI configuration file or a colors file */
535
536         if (root.name() != "Ardour") {
537                 return -1;
538         }
539
540         Stateful::save_extra_xml (root);
541
542         XMLNodeList nlist = root.children();
543         XMLNodeConstIterator niter;
544         XMLNode *node;
545
546         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
547
548                 node = *niter;
549
550                 if (node->name() == "Canvas" ||  node->name() == "UI") {
551                         set_variables (*node);
552
553                 }
554         }
555
556         XMLNode* colors = find_named_node (root, X_("Colors"));
557
558         if (colors) {
559                 load_colors (*colors);
560         }
561
562         XMLNode* aliases = find_named_node (root, X_("ColorAliases"));
563
564         if (aliases) {
565                 load_color_aliases (*aliases);
566         }
567
568         XMLNode* modifiers = find_named_node (root, X_("Modifiers"));
569
570         if (modifiers) {
571                 load_modifiers (*modifiers);
572         }
573
574         return 0;
575 }
576
577 void
578 UIConfiguration::load_color_aliases (XMLNode const & node)
579 {
580         XMLNodeList const nlist = node.children();
581         XMLNodeConstIterator niter;
582         XMLProperty const *name;
583         XMLProperty const *alias;
584
585         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
586                 XMLNode const * child = *niter;
587                 if (child->name() != X_("ColorAlias")) {
588                         continue;
589                 }
590                 name = child->property (X_("name"));
591                 alias = child->property (X_("alias"));
592
593                 if (name && alias) {
594                         color_aliases[name->value()] = alias->value();
595                 }
596         }
597 }
598
599 void
600 UIConfiguration::load_colors (XMLNode const & node)
601 {
602         XMLNodeList const nlist = node.children();
603         XMLNodeConstIterator niter;
604         XMLProperty const *name;
605         XMLProperty const *color;
606
607         /* don't clear colors, so that we can load > 1 color file and have
608            the subsequent ones overwrite the later ones.
609         */
610
611         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
612                 XMLNode const * child = *niter;
613                 if (child->name() != X_("Color")) {
614                         continue;
615                 }
616                 name = child->property (X_("name"));
617                 color = child->property (X_("value"));
618
619                 if (name && color) {
620                         Gtkmm2ext::Color c;
621                         c = strtoul (color->value().c_str(), 0, 16);
622                         /* insert or replace color name definition */
623                         colors[name->value()] =  c;
624                 }
625         }
626 }
627
628 void
629 UIConfiguration::load_modifiers (XMLNode const & node)
630 {
631         XMLNodeList const nlist = node.children();
632         XMLNodeConstIterator niter;
633         XMLProperty const *name;
634         XMLProperty const *mod;
635
636         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
637                 XMLNode const * child = *niter;
638                 if (child->name() != X_("Modifier")) {
639                         continue;
640                 }
641
642                 name = child->property (X_("name"));
643                 mod = child->property (X_("modifier"));
644
645                 if (name && mod) {
646                         SVAModifier svam (mod->value());
647                         modifiers[name->value()] = svam;
648                 }
649         }
650 }
651
652 void
653 UIConfiguration::set_variables (const XMLNode& node)
654 {
655 #undef  UI_CONFIG_VARIABLE
656 #define UI_CONFIG_VARIABLE(Type,var,name,val) if (var.set_from_node (node)) { ParameterChanged (name); }
657 #define CANVAS_FONT_VARIABLE(var,name)        if (var.set_from_node (node)) { ParameterChanged (name); }
658 #include "ui_config_vars.h"
659 #include "canvas_vars.h"
660 #undef  UI_CONFIG_VARIABLE
661 #undef  CANVAS_FONT_VARIABLE
662 }
663
664 Gtkmm2ext::SVAModifier
665 UIConfiguration::modifier (string const & name) const
666 {
667         Modifiers::const_iterator m = modifiers.find (name);
668         if (m != modifiers.end()) {
669                 return m->second;
670         }
671         return SVAModifier ();
672 }
673
674 Gtkmm2ext::Color
675 UIConfiguration::color_mod (std::string const & colorname, std::string const & modifiername) const
676 {
677         return HSV (color (colorname)).mod (modifier (modifiername)).color ();
678 }
679
680 Gtkmm2ext::Color
681 UIConfiguration::color_mod (const Gtkmm2ext::Color& color, std::string const & modifiername) const
682 {
683         return HSV (color).mod (modifier (modifiername)).color ();
684 }
685
686 Gtkmm2ext::Color
687 UIConfiguration::color (const std::string& name, bool* failed) const
688 {
689         ColorAliases::const_iterator e = color_aliases.find (name);
690
691         if (failed) {
692                 *failed = false;
693         }
694
695         if (e != color_aliases.end ()) {
696                 Colors::const_iterator rc = colors.find (e->second);
697                 if (rc != colors.end()) {
698                         return rc->second;
699                 }
700         } else {
701                 /* not an alias, try directly */
702                 Colors::const_iterator rc = colors.find (name);
703                 if (rc != colors.end()) {
704                         return rc->second;
705                 }
706         }
707
708         if (!failed) {
709                 /* only show this message if the caller wasn't interested in
710                    the fail status.
711                 */
712                 cerr << string_compose (_("Color %1 not found"), name) << endl;
713         }
714
715         if (failed) {
716                 *failed = true;
717         }
718
719         return rgba_to_color ((g_random_int()%256)/255.0,
720                               (g_random_int()%256)/255.0,
721                               (g_random_int()%256)/255.0,
722                               0xff);
723 }
724
725 Color
726 UIConfiguration::quantized (Color c) const
727 {
728         HSV hsv (c);
729         hsv.h = hue_width * (round (hsv.h/hue_width));
730         return hsv.color ();
731 }
732
733 void
734 UIConfiguration::set_color (string const& name, Gtkmm2ext::Color color)
735 {
736         Colors::iterator i = colors.find (name);
737         if (i == colors.end()) {
738                 return;
739         }
740         i->second = color;
741         colors_modified = true;
742
743         ColorsChanged (); /* EMIT SIGNAL */
744 }
745
746 void
747 UIConfiguration::set_alias (string const & name, string const & alias)
748 {
749         ColorAliases::iterator i = color_aliases.find (name);
750         if (i == color_aliases.end()) {
751                 return;
752         }
753
754         i->second = alias;
755         aliases_modified = true;
756
757         ColorsChanged (); /* EMIT SIGNAL */
758 }
759
760 void
761 UIConfiguration::set_modifier (string const & name, SVAModifier svam)
762 {
763         Modifiers::iterator m = modifiers.find (name);
764
765         if (m == modifiers.end()) {
766                 return;
767         }
768
769         m->second = svam;
770         modifiers_modified = true;
771
772         ColorsChanged (); /* EMIT SIGNAL */
773 }
774
775 void
776 UIConfiguration::load_rc_file (bool themechange, bool allow_own)
777 {
778         string basename = ui_rc_file.get();
779         std::string rc_file_path;
780
781         if (!find_file (ardour_config_search_path(), basename, rc_file_path)) {
782                 warning << string_compose (_("Unable to find UI style file %1 in search path %2. %3 will look strange"),
783                                            basename, ardour_config_search_path().to_string(), PROGRAM_NAME)
784                                 << endmsg;
785                 return;
786         }
787
788         info << string_compose (_("Loading ui configuration file %1"), rc_file_path) << endmsg;
789
790         Gtkmm2ext::UI::instance()->load_rcfile (rc_file_path, themechange);
791 }
792
793 std::string
794 UIConfiguration::color_to_hex_string (Gtkmm2ext::Color c)
795 {
796         char buf[16];
797         int retval = g_snprintf (buf, sizeof(buf), "%08x", c);
798
799         if (retval < 0 || retval >= (int)sizeof(buf)) {
800                 assert(false);
801         }
802         return buf;
803 }
804
805 std::string
806 UIConfiguration::color_to_hex_string_no_alpha (Gtkmm2ext::Color c)
807 {
808         c >>= 8; // shift/remove alpha
809         char buf[16];
810         int retval = g_snprintf (buf, sizeof(buf), "%06x", c);
811
812         if (retval < 0 || retval >= (int)sizeof(buf)) {
813                 assert(false);
814         }
815         return buf;
816 }