Merge branch 'master' into cairocanvas
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2     Copyright (C) 2003 Paul Davis
3
4     This program is free software; you an 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 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <pango/pangoft2.h> // for fontmap resolution control for GnomeCanvas
25 #include <pango/pangocairo.h> // for fontmap resolution control for GnomeCanvas
26
27 #include <cstdlib>
28 #include <clocale>
29 #include <cstring>
30 #include <cctype>
31 #include <fstream>
32 #include <list>
33 #include <sys/stat.h>
34 #include <gtkmm/rc.h>
35 #include <gtkmm/window.h>
36 #include <gtkmm/combo.h>
37 #include <gtkmm/label.h>
38 #include <gtkmm/paned.h>
39 #include <gtk/gtkpaned.h>
40 #include <boost/algorithm/string.hpp>
41
42 #include "pbd/file_utils.h"
43
44 #include <gtkmm2ext/utils.h>
45 #include "ardour/rc_configuration.h"
46 #include "ardour/filesystem_paths.h"
47 #include "canvas/item.h"
48
49 #include "ardour_ui.h"
50 #include "debug.h"
51 #include "public_editor.h"
52 #include "keyboard.h"
53 #include "utils.h"
54 #include "i18n.h"
55 #include "rgb_macros.h"
56 #include "gui_thread.h"
57
58 using namespace std;
59 using namespace Gtk;
60 using namespace Glib;
61 using namespace PBD;
62 using Gtkmm2ext::Keyboard;
63
64 sigc::signal<void>  DPIReset;
65
66
67 /** Add an element to a menu, settings its sensitivity.
68  * @param m Menu to add to.
69  * @param e Element to add.
70  * @param s true to make sensitive, false to make insensitive
71  */
72 void
73 add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
74 {
75         m.push_back (e);
76         if (!s) {
77                 m.back().set_sensitive (false);
78         }
79 }
80
81
82 gint
83 just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
84 {
85         win->hide ();
86         return 0;
87 }
88
89 /* xpm2rgb copied from nixieclock, which bore the legend:
90
91     nixieclock - a nixie desktop timepiece
92     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
93
94     and was released under the GPL.
95 */
96
97 unsigned char*
98 xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
99 {
100         static long vals[256], val;
101         uint32_t t, x, y, colors, cpp;
102         unsigned char c;
103         unsigned char *savergb, *rgb;
104
105         // PARSE HEADER
106
107         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
108                 error << string_compose (_("bad XPM header %1"), xpm[0])
109                       << endmsg;
110                 return 0;
111         }
112
113         savergb = rgb = (unsigned char*) malloc (h * w * 3);
114
115         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
116         for (t = 0; t < colors; ++t) {
117                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
118                 vals[c] = val;
119         }
120
121         // COLORMAP -> RGB CONVERSION
122         //    Get low 3 bytes from vals[]
123         //
124
125         const char *p;
126         for (y = h-1; y > 0; --y) {
127
128                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
129                         val = vals[(int)*p++];
130                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
131                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
132                         *(rgb+0) = val & 0xff;             // 0:R
133                 }
134         }
135
136         return (savergb);
137 }
138
139 unsigned char*
140 xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
141 {
142         static long vals[256], val;
143         uint32_t t, x, y, colors, cpp;
144         unsigned char c;
145         unsigned char *savergb, *rgb;
146         char transparent;
147
148         // PARSE HEADER
149
150         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
151                 error << string_compose (_("bad XPM header %1"), xpm[0])
152                       << endmsg;
153                 return 0;
154         }
155
156         savergb = rgb = (unsigned char*) malloc (h * w * 4);
157
158         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
159
160         if (strstr (xpm[1], "None")) {
161                 sscanf (xpm[1], "%c", &transparent);
162                 t = 1;
163         } else {
164                 transparent = 0;
165                 t = 0;
166         }
167
168         for (; t < colors; ++t) {
169                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
170                 vals[c] = val;
171         }
172
173         // COLORMAP -> RGB CONVERSION
174         //    Get low 3 bytes from vals[]
175         //
176
177         const char *p;
178         for (y = h-1; y > 0; --y) {
179
180                 char alpha;
181
182                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
183
184                         if (transparent && (*p++ == transparent)) {
185                                 alpha = 0;
186                                 val = 0;
187                         } else {
188                                 alpha = 255;
189                                 val = vals[(int)*p];
190                         }
191
192                         *(rgb+3) = alpha;                  // 3: alpha
193                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
194                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
195                         *(rgb+0) = val & 0xff;             // 0:R
196                 }
197         }
198
199         return (savergb);
200 }
201
202 Pango::FontDescription
203 get_font_for_style (string widgetname)
204 {
205         Gtk::Window window (WINDOW_TOPLEVEL);
206         Gtk::Label foobar;
207         Glib::RefPtr<Gtk::Style> style;
208
209         window.add (foobar);
210         foobar.set_name (widgetname);
211         foobar.ensure_style();
212
213         style = foobar.get_style ();
214
215         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
216
217         PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
218
219         if (!pfd) {
220
221                 /* layout inherited its font description from a PangoContext */
222
223                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
224                 pfd =  pango_context_get_font_description (ctxt);
225                 return Pango::FontDescription (pfd); /* make a copy */
226         }
227
228         return Pango::FontDescription (pfd); /* make a copy */
229 }
230
231 uint32_t
232 rgba_from_style (string style, uint32_t r, uint32_t g, uint32_t b, uint32_t a, string attr, int state, bool rgba)
233 {
234         /* In GTK+2, styles aren't set up correctly if the widget is not
235            attached to a toplevel window that has a screen pointer.
236         */
237
238         static Gtk::Window* window = 0;
239
240         if (window == 0) {
241                 window = new Window (WINDOW_TOPLEVEL);
242         }
243
244         Gtk::Label foo;
245
246         window->add (foo);
247
248         foo.set_name (style);
249         foo.ensure_style ();
250
251         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
252
253         if (rc) {
254                 if (attr == "fg") {
255                         r = rc->fg[state].red / 257;
256                         g = rc->fg[state].green / 257;
257                         b = rc->fg[state].blue / 257;
258
259                         /* what a hack ... "a" is for "active" */
260                         if (state == Gtk::STATE_NORMAL && rgba) {
261                                 a = rc->fg[GTK_STATE_ACTIVE].red / 257;
262                         }
263                 } else if (attr == "bg") {
264                         r = g = b = 0;
265                         r = rc->bg[state].red / 257;
266                         g = rc->bg[state].green / 257;
267                         b = rc->bg[state].blue / 257;
268                 } else if (attr == "base") {
269                         r = rc->base[state].red / 257;
270                         g = rc->base[state].green / 257;
271                         b = rc->base[state].blue / 257;
272                 } else if (attr == "text") {
273                         r = rc->text[state].red / 257;
274                         g = rc->text[state].green / 257;
275                         b = rc->text[state].blue / 257;
276                 }
277         } else {
278                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
279         }
280
281         window->remove ();
282
283         if (state == Gtk::STATE_NORMAL && rgba) {
284                 return (uint32_t) RGBA_TO_UINT(r,g,b,a);
285         } else {
286                 return (uint32_t) RGB_TO_UINT(r,g,b);
287         }
288 }
289
290 bool
291 rgba_p_from_style (string style, float *r, float *g, float *b, string attr, int state)
292 {
293         static Gtk::Window* window = 0;
294         assert (r && g && b);
295
296         if (window == 0) {
297                 window = new Window (WINDOW_TOPLEVEL);
298         }
299
300         Gtk::EventBox foo;
301
302         window->add (foo);
303
304         foo.set_name (style);
305         foo.ensure_style ();
306
307         GtkRcStyle* rc = foo.get_style()->gobj()->rc_style;
308
309         if (!rc) {
310                 warning << string_compose (_("missing RGBA style for \"%1\""), style) << endl;
311                 return false;
312         }
313         if (attr == "fg") {
314                 *r = rc->fg[state].red / 65535.0;
315                 *g = rc->fg[state].green / 65535.0;
316                 *b = rc->fg[state].blue / 65535.0;
317         } else if (attr == "bg") {
318                 *r = rc->bg[state].red / 65535.0;
319                 *g = rc->bg[state].green / 65535.0;
320                 *b = rc->bg[state].blue / 65535.0;
321         } else if (attr == "base") {
322                 *r = rc->base[state].red / 65535.0;
323                 *g = rc->base[state].green / 65535.0;
324                 *b = rc->base[state].blue / 65535.0;
325         } else if (attr == "text") {
326                 *r = rc->text[state].red / 65535.0;
327                 *g = rc->text[state].green / 65535.0;
328                 *b = rc->text[state].blue / 65535.0;
329         } else {
330                 return false;
331         }
332
333         window->remove ();
334         return true;
335 }
336
337 void
338 set_color (Gdk::Color& c, int rgb)
339 {
340         c.set_rgb((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
341 }
342
343 bool
344 relay_key_press (GdkEventKey* ev, Gtk::Window* win)
345 {
346         PublicEditor& ed (PublicEditor::instance());
347
348         if (!key_press_focus_accelerator_handler (*win, ev)) {
349                 if (&ed == 0) {
350                         /* early key press in pre-main-window-dialogs, no editor yet */
351                         return false;
352                 }
353                 return ed.on_key_press_event(ev);
354         } else {
355                 return true;
356         }
357 }
358
359 bool
360 forward_key_press (GdkEventKey* ev)
361 {
362         return PublicEditor::instance().on_key_press_event(ev);
363 }
364
365 bool
366 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
367 {
368         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
369         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
370         GdkKeymapKey *keymapkey = NULL;
371         gint n_keys;
372
373         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
374         if (n_keys !=1) { g_free(keymapkey); return false;}
375
376         GdkEventKey ev;
377         ev.type = GDK_KEY_PRESS;
378         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
379         ev.send_event = FALSE;
380         ev.time = 0;
381         ev.state = 0;
382         ev.keyval = keyval;
383         ev.length = 0;
384         ev.string = const_cast<gchar*> ("");
385         ev.hardware_keycode = keymapkey[0].keycode;
386         ev.group = keymapkey[0].group;
387         g_free(keymapkey);
388
389         forward_key_press(&ev);
390         ev.type = GDK_KEY_RELEASE;
391         return forward_key_press(&ev);
392 }
393
394 bool
395 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
396 {
397         GtkWindow* win = window.gobj();
398         GtkWidget* focus = gtk_window_get_focus (win);
399         bool special_handling_of_unmodified_accelerators = false;
400         bool allow_activating = true;
401         /* consider all relevant modifiers but not LOCK or SHIFT */
402         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
403
404         if (focus) {
405                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
406                         special_handling_of_unmodified_accelerators = true;
407                 }
408         }
409
410 #ifdef GTKOSX
411         /* at one time this appeared to be necessary. As of July 2012, it does not
412            appear to be. if it ever is necessar, figure out if it should apply
413            to all platforms.
414         */
415 #if 0 
416         if (Keyboard::some_magic_widget_has_focus ()) {
417                 allow_activating = false;
418         }
419 #endif
420 #endif
421
422
423         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 focus = %7 Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 allow_activation ? %6\n",
424                                                           win,
425                                                           ev->keyval,
426                                                           ev->state,
427                                                           special_handling_of_unmodified_accelerators,
428                                                           Keyboard::some_magic_widget_has_focus(),
429                                                           allow_activating,
430                                                           focus));
431
432         /* This exists to allow us to override the way GTK handles
433            key events. The normal sequence is:
434
435            a) event is delivered to a GtkWindow
436            b) accelerators/mnemonics are activated
437            c) if (b) didn't handle the event, propagate to
438                the focus widget and/or focus chain
439
440            The problem with this is that if the accelerators include
441            keys without modifiers, such as the space bar or the
442            letter "e", then pressing the key while typing into
443            a text entry widget results in the accelerator being
444            activated, instead of the desired letter appearing
445            in the text entry.
446
447            There is no good way of fixing this, but this
448            represents a compromise. The idea is that
449            key events involving modifiers (not Shift)
450            get routed into the activation pathway first, then
451            get propagated to the focus widget if necessary.
452
453            If the key event doesn't involve modifiers,
454            we deliver to the focus widget first, thus allowing
455            it to get "normal text" without interference
456            from acceleration.
457
458            Of course, this can also be problematic: if there
459            is a widget with focus, then it will swallow
460            all "normal text" accelerators.
461         */
462
463         if (!special_handling_of_unmodified_accelerators) {
464
465                 /* XXX note that for a brief moment, the conditional above
466                  * included "|| (ev->state & mask)" so as to enforce the
467                  * implication of special_handling_of_UNMODIFIED_accelerators.
468                  * however, this forces any key that GTK doesn't allow and that
469                  * we have an alternative (see next comment) for to be
470                  * automatically sent through the accel groups activation
471                  * pathway, which prevents individual widgets & canvas items
472                  * from ever seeing it if is used by a key binding.
473                  * 
474                  * specifically, this hid Ctrl-down-arrow from MIDI region
475                  * views because it is also bound to an action.
476                  *
477                  * until we have a robust, clean binding system, this
478                  * quirk will have to remain in place.
479                  */
480
481                 /* pretend that certain key events that GTK does not allow
482                    to be used as accelerators are actually something that
483                    it does allow. but only where there are no modifiers.
484                 */
485
486                 uint32_t fakekey = ev->keyval;
487
488                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
489                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
490                                                                           ev->keyval, fakekey));
491
492                         GdkModifierType mod = GdkModifierType (ev->state);
493
494                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
495 #ifdef GTKOSX
496                         /* GTK on OS X is currently (February 2012) setting both
497                            the Meta and Mod2 bits in the event modifier state if 
498                            the Command key is down.
499
500                            gtk_accel_groups_activate() does not invoke any of the logic
501                            that gtk_window_activate_key() will that sorts out that stupid
502                            state of affairs, and as a result it fails to find a match
503                            for the key event and the current set of accelerators.
504
505                            to fix this, if the meta bit is set, remove the mod2 bit
506                            from the modifier. this assumes that our bindings use Primary
507                            which will have set the meta bit in the accelerator entry.
508                         */
509                         if (mod & GDK_META_MASK) {
510                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
511                         }
512 #endif
513
514                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
515                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
516                                 return true;
517                         }
518                 }
519         }
520
521         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
522
523                 /* no special handling or there are modifiers in effect: accelerate first */
524
525                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
526                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
527                                         ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
528
529                 if (allow_activating) {
530                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
531                         if (gtk_window_activate_key (win, ev)) {
532                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
533                                 return true;
534                         }
535                 } else {
536                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
537                 }
538
539                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
540
541                 return gtk_window_propagate_key_event (win, ev);
542         }
543
544         /* no modifiers, propagate first */
545
546         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
547
548         if (!gtk_window_propagate_key_event (win, ev)) {
549                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
550                 if (allow_activating) {
551                         return gtk_window_activate_key (win, ev);
552                 } else {
553                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
554                 }
555
556         } else {
557                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
558                 return true;
559         }
560
561         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
562         return true;
563 }
564
565 Glib::RefPtr<Gdk::Pixbuf>
566 get_xpm (std::string name)
567 {
568         if (!xpm_map[name]) {
569
570                 SearchPath spath(ARDOUR::ardour_data_search_path());
571
572                 spath.add_subdirectory_to_paths("pixmaps");
573
574                 std::string data_file_path;
575
576                 if(!find_file_in_search_path (spath, name, data_file_path)) {
577                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
578                 }
579
580                 try {
581                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
582                 } catch(const Glib::Error& e)   {
583                         warning << "Caught Glib::Error: " << e.what() << endmsg;
584                 }
585         }
586
587         return xpm_map[name];
588 }
589
590 std::string
591 get_icon_path (const char* cname)
592 {
593         string name = cname;
594         name += X_(".png");
595
596         SearchPath spath(ARDOUR::ardour_data_search_path());
597
598         spath.add_subdirectory_to_paths("icons");
599
600         std::string data_file_path;
601
602         if (!find_file_in_search_path (spath, name, data_file_path)) {
603                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
604         }
605
606         return data_file_path;
607 }
608
609 Glib::RefPtr<Gdk::Pixbuf>
610 get_icon (const char* cname)
611 {
612         Glib::RefPtr<Gdk::Pixbuf> img;
613         try {
614                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
615         } catch (const Gdk::PixbufError &e) {
616                 cerr << "Caught PixbufError: " << e.what() << endl;
617         } catch (...) {
618                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
619         }
620
621         return img;
622 }
623
624 string
625 longest (vector<string>& strings)
626 {
627         if (strings.empty()) {
628                 return string ("");
629         }
630
631         vector<string>::iterator longest = strings.begin();
632         string::size_type longest_length = (*longest).length();
633
634         vector<string>::iterator i = longest;
635         ++i;
636
637         while (i != strings.end()) {
638
639                 string::size_type len = (*i).length();
640
641                 if (len > longest_length) {
642                         longest = i;
643                         longest_length = len;
644                 }
645
646                 ++i;
647         }
648
649         return *longest;
650 }
651
652 bool
653 key_is_legal_for_numeric_entry (guint keyval)
654 {
655         /* we assume that this does not change over the life of the process 
656          */
657
658         static int comma_decimal = -1;
659
660         switch (keyval) {
661         case GDK_period:
662         case GDK_comma:
663                 if (comma_decimal < 0) {
664                         std::lconv* lc = std::localeconv();
665                         if (strchr (lc->decimal_point, ',') != 0) {
666                                 comma_decimal = 1;
667                         } else {
668                                 comma_decimal = 0;
669                         }
670                 }
671                 break;
672         default:
673                 break;
674         }
675
676         switch (keyval) {
677         case GDK_decimalpoint:
678         case GDK_KP_Separator:
679                 return true;
680
681         case GDK_period:
682                 if (comma_decimal) {
683                         return false;
684                 } else {
685                         return true;
686                 }
687                 break;
688         case GDK_comma:
689                 if (comma_decimal) {
690                         return true;
691                 } else {
692                         return false;
693                 }
694                 break;
695         case GDK_minus:
696         case GDK_plus:
697         case GDK_0:
698         case GDK_1:
699         case GDK_2:
700         case GDK_3:
701         case GDK_4:
702         case GDK_5:
703         case GDK_6:
704         case GDK_7:
705         case GDK_8:
706         case GDK_9:
707         case GDK_KP_Add:
708         case GDK_KP_Subtract:
709         case GDK_KP_Decimal:
710         case GDK_KP_0:
711         case GDK_KP_1:
712         case GDK_KP_2:
713         case GDK_KP_3:
714         case GDK_KP_4:
715         case GDK_KP_5:
716         case GDK_KP_6:
717         case GDK_KP_7:
718         case GDK_KP_8:
719         case GDK_KP_9:
720         case GDK_Return:
721         case GDK_BackSpace:
722         case GDK_Delete:
723         case GDK_KP_Enter:
724         case GDK_Home:
725         case GDK_End:
726         case GDK_Left:
727         case GDK_Right:
728                 return true;
729
730         default:
731                 break;
732         }
733
734         return false;
735 }
736 void
737 set_pango_fontsize ()
738 {
739         long val = ARDOUR::Config->get_font_scale();
740
741         /* FT2 rendering - used by GnomeCanvas, sigh */
742
743         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
744
745         /* Cairo rendering, in case there is any */
746
747         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
748 }
749
750 void
751 reset_dpi ()
752 {
753         long val = ARDOUR::Config->get_font_scale();
754         set_pango_fontsize ();
755         /* Xft rendering */
756
757         gtk_settings_set_long_property (gtk_settings_get_default(),
758                                         "gtk-xft-dpi", val, "ardour");
759         DPIReset();//Emit Signal
760 }
761
762 void
763 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
764 {
765         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
766         Gdk::Rectangle monitor_rect;
767         screen->get_monitor_geometry (0, monitor_rect);
768
769         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
770         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
771
772         window->resize (w, h);
773 }
774
775
776 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
777 string
778 escape_underscores (string const & s)
779 {
780         string o;
781         string::size_type const N = s.length ();
782
783         for (string::size_type i = 0; i < N; ++i) {
784                 if (s[i] == '_') {
785                         o += "__";
786                 } else {
787                         o += s[i];
788                 }
789         }
790
791         return o;
792 }
793
794 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
795 string
796 escape_angled_brackets (string const & s)
797 {
798         string o = s;
799         boost::replace_all (o, "<", "&lt;");
800         boost::replace_all (o, ">", "&gt;");
801         return o;
802 }
803
804 Gdk::Color
805 unique_random_color (list<Gdk::Color>& used_colors)
806 {
807         Gdk::Color newcolor;
808
809         while (1) {
810
811                 double h, s, v;
812
813                 h = fmod (random(), 360.0);
814                 s = (random() % 65535) / 65535.0;
815                 v = (random() % 65535) / 65535.0;
816
817                 s = min (0.5, s); /* not too saturated */
818                 v = max (0.9, v);  /* not too bright */
819                 newcolor.set_hsv (h, s, v);
820
821                 if (used_colors.size() == 0) {
822                         used_colors.push_back (newcolor);
823                         return newcolor;
824                 }
825
826                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
827                   Gdk::Color c = *i;
828                         float rdelta, bdelta, gdelta;
829
830                         rdelta = newcolor.get_red() - c.get_red();
831                         bdelta = newcolor.get_blue() - c.get_blue();
832                         gdelta = newcolor.get_green() - c.get_green();
833
834                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
835                                 /* different enough */
836                                 used_colors.push_back (newcolor);
837                                 return newcolor;
838                         }
839                 }
840
841                 /* XXX need throttle here to make sure we don't spin for ever */
842         }
843 }