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