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