fce1d0b46c24a0673b93a5770a22e1cdb2bd7ce8
[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         if (!key_press_focus_accelerator_handler (*win, ev)) {
347                 return PublicEditor::instance().on_key_press_event(ev);
348         } else {
349                 return true;
350         }
351 }
352
353 bool
354 forward_key_press (GdkEventKey* ev)
355 {
356         return PublicEditor::instance().on_key_press_event(ev);
357 }
358
359 bool
360 emulate_key_event (Gtk::Widget* w, unsigned int keyval)
361 {
362         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(w->gobj()));
363         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
364         GdkKeymapKey *keymapkey = NULL;
365         gint n_keys;
366
367         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
368         if (n_keys !=1) { g_free(keymapkey); return false;}
369
370         GdkEventKey ev;
371         ev.type = GDK_KEY_PRESS;
372         ev.window = gtk_widget_get_window(GTK_WIDGET(w->gobj()));
373         ev.send_event = FALSE;
374         ev.time = 0;
375         ev.state = 0;
376         ev.keyval = keyval;
377         ev.length = 0;
378         ev.string = const_cast<gchar*> ("");
379         ev.hardware_keycode = keymapkey[0].keycode;
380         ev.group = keymapkey[0].group;
381         g_free(keymapkey);
382
383         forward_key_press(&ev);
384         ev.type = GDK_KEY_RELEASE;
385         return forward_key_press(&ev);
386 }
387
388 bool
389 key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev)
390 {
391         GtkWindow* win = window.gobj();
392         GtkWidget* focus = gtk_window_get_focus (win);
393         bool special_handling_of_unmodified_accelerators = false;
394         bool allow_activating = true;
395         /* consider all relevant modifiers but not LOCK or SHIFT */
396         const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
397
398         if (focus) {
399                 if (GTK_IS_ENTRY(focus) || Keyboard::some_magic_widget_has_focus()) {
400                         special_handling_of_unmodified_accelerators = true;
401                 }
402         }
403
404 #ifdef GTKOSX
405         /* at one time this appeared to be necessary. As of July 2012, it does not
406            appear to be. if it ever is necessar, figure out if it should apply
407            to all platforms.
408         */
409 #if 0 
410         if (Keyboard::some_magic_widget_has_focus ()) {
411                 allow_activating = false;
412         }
413 #endif
414 #endif
415
416
417         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",
418                                                           win,
419                                                           ev->keyval,
420                                                           ev->state,
421                                                           special_handling_of_unmodified_accelerators,
422                                                           Keyboard::some_magic_widget_has_focus(),
423                                                           allow_activating,
424                                                           focus));
425
426         /* This exists to allow us to override the way GTK handles
427            key events. The normal sequence is:
428
429            a) event is delivered to a GtkWindow
430            b) accelerators/mnemonics are activated
431            c) if (b) didn't handle the event, propagate to
432                the focus widget and/or focus chain
433
434            The problem with this is that if the accelerators include
435            keys without modifiers, such as the space bar or the
436            letter "e", then pressing the key while typing into
437            a text entry widget results in the accelerator being
438            activated, instead of the desired letter appearing
439            in the text entry.
440
441            There is no good way of fixing this, but this
442            represents a compromise. The idea is that
443            key events involving modifiers (not Shift)
444            get routed into the activation pathway first, then
445            get propagated to the focus widget if necessary.
446
447            If the key event doesn't involve modifiers,
448            we deliver to the focus widget first, thus allowing
449            it to get "normal text" without interference
450            from acceleration.
451
452            Of course, this can also be problematic: if there
453            is a widget with focus, then it will swallow
454            all "normal text" accelerators.
455         */
456
457         if (!special_handling_of_unmodified_accelerators) {
458
459                 /* XXX note that for a brief moment, the conditional above
460                  * included "|| (ev->state & mask)" so as to enforce the
461                  * implication of special_handling_of_UNMODIFIED_accelerators.
462                  * however, this forces any key that GTK doesn't allow and that
463                  * we have an alternative (see next comment) for to be
464                  * automatically sent through the accel groups activation
465                  * pathway, which prevents individual widgets & canvas items
466                  * from ever seeing it if is used by a key binding.
467                  * 
468                  * specifically, this hid Ctrl-down-arrow from MIDI region
469                  * views because it is also bound to an action.
470                  *
471                  * until we have a robust, clean binding system, this
472                  * quirk will have to remain in place.
473                  */
474
475                 /* pretend that certain key events that GTK does not allow
476                    to be used as accelerators are actually something that
477                    it does allow. but only where there are no modifiers.
478                 */
479
480                 uint32_t fakekey = ev->keyval;
481
482                 if (Gtkmm2ext::possibly_translate_keyval_to_make_legal_accelerator (fakekey)) {
483                         DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tactivate (was %1 now %2) without special hanlding of unmodified accels\n",
484                                                                           ev->keyval, fakekey));
485
486                         GdkModifierType mod = GdkModifierType (ev->state);
487
488                         mod = GdkModifierType (mod & gtk_accelerator_get_default_mod_mask());
489 #ifdef GTKOSX
490                         /* GTK on OS X is currently (February 2012) setting both
491                            the Meta and Mod2 bits in the event modifier state if 
492                            the Command key is down.
493
494                            gtk_accel_groups_activate() does not invoke any of the logic
495                            that gtk_window_activate_key() will that sorts out that stupid
496                            state of affairs, and as a result it fails to find a match
497                            for the key event and the current set of accelerators.
498
499                            to fix this, if the meta bit is set, remove the mod2 bit
500                            from the modifier. this assumes that our bindings use Primary
501                            which will have set the meta bit in the accelerator entry.
502                         */
503                         if (mod & GDK_META_MASK) {
504                                 mod = GdkModifierType (mod & ~GDK_MOD2_MASK);
505                         }
506 #endif
507
508                         if (allow_activating && gtk_accel_groups_activate(G_OBJECT(win), fakekey, mod)) {
509                                 DEBUG_TRACE (DEBUG::Accelerators, "\taccel group activated by fakekey\n");
510                                 return true;
511                         }
512                 }
513         }
514
515         if (!special_handling_of_unmodified_accelerators || (ev->state & mask)) {
516
517                 /* no special handling or there are modifiers in effect: accelerate first */
518
519                 DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
520                 DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 string:%4 hardware_keycode:%5 group:%6\n",
521                                         ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group));
522
523                 if (allow_activating) {
524                         DEBUG_TRACE (DEBUG::Accelerators, "\tsending to window\n");
525                         if (gtk_window_activate_key (win, ev)) {
526                                 DEBUG_TRACE (DEBUG::Accelerators, "\t\thandled\n");
527                                 return true;
528                         }
529                 } else {
530                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
531                 }
532
533                 DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
534
535                 return gtk_window_propagate_key_event (win, ev);
536         }
537
538         /* no modifiers, propagate first */
539
540         DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate, then activate\n");
541
542         if (!gtk_window_propagate_key_event (win, ev)) {
543                 DEBUG_TRACE (DEBUG::Accelerators, "\tpropagation didn't handle, so activate\n");
544                 if (allow_activating) {
545                         return gtk_window_activate_key (win, ev);
546                 } else {
547                         DEBUG_TRACE (DEBUG::Accelerators, "\tactivation skipped\n");
548                 }
549
550         } else {
551                 DEBUG_TRACE (DEBUG::Accelerators, "\thandled by propagate\n");
552                 return true;
553         }
554
555         DEBUG_TRACE (DEBUG::Accelerators, "\tnot handled\n");
556         return true;
557 }
558
559 Glib::RefPtr<Gdk::Pixbuf>
560 get_xpm (std::string name)
561 {
562         if (!xpm_map[name]) {
563
564                 SearchPath spath(ARDOUR::ardour_data_search_path());
565
566                 spath.add_subdirectory_to_paths("pixmaps");
567
568                 std::string data_file_path;
569
570                 if(!find_file_in_search_path (spath, name, data_file_path)) {
571                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
572                 }
573
574                 try {
575                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
576                 } catch(const Glib::Error& e)   {
577                         warning << "Caught Glib::Error: " << e.what() << endmsg;
578                 }
579         }
580
581         return xpm_map[name];
582 }
583
584 std::string
585 get_icon_path (const char* cname)
586 {
587         string name = cname;
588         name += X_(".png");
589
590         SearchPath spath(ARDOUR::ardour_data_search_path());
591
592         spath.add_subdirectory_to_paths("icons");
593
594         std::string data_file_path;
595
596         if (!find_file_in_search_path (spath, name, data_file_path)) {
597                 fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
598         }
599
600         return data_file_path;
601 }
602
603 Glib::RefPtr<Gdk::Pixbuf>
604 get_icon (const char* cname)
605 {
606         Glib::RefPtr<Gdk::Pixbuf> img;
607         try {
608                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
609         } catch (const Gdk::PixbufError &e) {
610                 cerr << "Caught PixbufError: " << e.what() << endl;
611         } catch (...) {
612                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
613         }
614
615         return img;
616 }
617
618 string
619 longest (vector<string>& strings)
620 {
621         if (strings.empty()) {
622                 return string ("");
623         }
624
625         vector<string>::iterator longest = strings.begin();
626         string::size_type longest_length = (*longest).length();
627
628         vector<string>::iterator i = longest;
629         ++i;
630
631         while (i != strings.end()) {
632
633                 string::size_type len = (*i).length();
634
635                 if (len > longest_length) {
636                         longest = i;
637                         longest_length = len;
638                 }
639
640                 ++i;
641         }
642
643         return *longest;
644 }
645
646 bool
647 key_is_legal_for_numeric_entry (guint keyval)
648 {
649         /* we assume that this does not change over the life of the process 
650          */
651
652         static int comma_decimal = -1;
653
654         switch (keyval) {
655         case GDK_period:
656         case GDK_comma:
657                 if (comma_decimal < 0) {
658                         std::lconv* lc = std::localeconv();
659                         if (strchr (lc->decimal_point, ',') != 0) {
660                                 comma_decimal = 1;
661                         } else {
662                                 comma_decimal = 0;
663                         }
664                 }
665                 break;
666         default:
667                 break;
668         }
669
670         switch (keyval) {
671         case GDK_decimalpoint:
672         case GDK_KP_Separator:
673                 return true;
674
675         case GDK_period:
676                 if (comma_decimal) {
677                         return false;
678                 } else {
679                         return true;
680                 }
681                 break;
682         case GDK_comma:
683                 if (comma_decimal) {
684                         return true;
685                 } else {
686                         return false;
687                 }
688                 break;
689         case GDK_minus:
690         case GDK_plus:
691         case GDK_0:
692         case GDK_1:
693         case GDK_2:
694         case GDK_3:
695         case GDK_4:
696         case GDK_5:
697         case GDK_6:
698         case GDK_7:
699         case GDK_8:
700         case GDK_9:
701         case GDK_KP_Add:
702         case GDK_KP_Subtract:
703         case GDK_KP_Decimal:
704         case GDK_KP_0:
705         case GDK_KP_1:
706         case GDK_KP_2:
707         case GDK_KP_3:
708         case GDK_KP_4:
709         case GDK_KP_5:
710         case GDK_KP_6:
711         case GDK_KP_7:
712         case GDK_KP_8:
713         case GDK_KP_9:
714         case GDK_Return:
715         case GDK_BackSpace:
716         case GDK_Delete:
717         case GDK_KP_Enter:
718         case GDK_Home:
719         case GDK_End:
720         case GDK_Left:
721         case GDK_Right:
722                 return true;
723
724         default:
725                 break;
726         }
727
728         return false;
729 }
730 void
731 set_pango_fontsize ()
732 {
733         long val = ARDOUR::Config->get_font_scale();
734
735         /* FT2 rendering - used by GnomeCanvas, sigh */
736
737         pango_ft2_font_map_set_resolution ((PangoFT2FontMap*) pango_ft2_font_map_new(), val/1024, val/1024);
738
739         /* Cairo rendering, in case there is any */
740
741         pango_cairo_font_map_set_resolution ((PangoCairoFontMap*) pango_cairo_font_map_get_default(), val/1024);
742 }
743
744 void
745 reset_dpi ()
746 {
747         long val = ARDOUR::Config->get_font_scale();
748         set_pango_fontsize ();
749         /* Xft rendering */
750
751         gtk_settings_set_long_property (gtk_settings_get_default(),
752                                         "gtk-xft-dpi", val, "ardour");
753         DPIReset();//Emit Signal
754 }
755
756 void
757 resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
758 {
759         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
760         Gdk::Rectangle monitor_rect;
761         screen->get_monitor_geometry (0, monitor_rect);
762
763         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
764         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
765
766         window->resize (w, h);
767 }
768
769
770 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
771 string
772 escape_underscores (string const & s)
773 {
774         string o;
775         string::size_type const N = s.length ();
776
777         for (string::size_type i = 0; i < N; ++i) {
778                 if (s[i] == '_') {
779                         o += "__";
780                 } else {
781                         o += s[i];
782                 }
783         }
784
785         return o;
786 }
787
788 /** Replace < and > with &lt; and &gt; respectively to make < > display correctly in markup strings */
789 string
790 escape_angled_brackets (string const & s)
791 {
792         string o = s;
793         boost::replace_all (o, "<", "&lt;");
794         boost::replace_all (o, ">", "&gt;");
795         return o;
796 }
797
798 Gdk::Color
799 unique_random_color (list<Gdk::Color>& used_colors)
800 {
801         Gdk::Color newcolor;
802
803         while (1) {
804
805                 double h, s, v;
806
807                 h = fmod (random(), 360.0);
808                 s = (random() % 65535) / 65535.0;
809                 v = (random() % 65535) / 65535.0;
810
811                 s = min (0.5, s); /* not too saturated */
812                 v = max (0.9, v);  /* not too bright */
813                 newcolor.set_hsv (h, s, v);
814
815                 if (used_colors.size() == 0) {
816                         used_colors.push_back (newcolor);
817                         return newcolor;
818                 }
819
820                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
821                   Gdk::Color c = *i;
822                         float rdelta, bdelta, gdelta;
823
824                         rdelta = newcolor.get_red() - c.get_red();
825                         bdelta = newcolor.get_blue() - c.get_blue();
826                         gdelta = newcolor.get_green() - c.get_green();
827
828                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
829                                 /* different enough */
830                                 used_colors.push_back (newcolor);
831                                 return newcolor;
832                         }
833                 }
834
835                 /* XXX need throttle here to make sure we don't spin for ever */
836         }
837 }