Fix thinkos in cubasish theme
[ardour.git] / gtk2_ardour / utils.cc
1 /*
2  * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
3  * Copyright (C) 2005-2009 Nick Mainsbridge <mainsbridge@gmail.com>
4  * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2005 Karsten Wiese <fzuuzf@googlemail.com>
6  * Copyright (C) 2006-2007 Doug McLain <doug@nostar.net>
7  * Copyright (C) 2006-2009 Sampo Savolainen <v2@iki.fi>
8  * Copyright (C) 2007-2015 David Robillard <d@drobilla.net>
9  * Copyright (C) 2007-2015 Tim Mayberry <mojofunk@gmail.com>
10  * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
11  * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
12  * Copyright (C) 2013 John Emmas <john@creativepost.co.uk>
13  * Copyright (C) 2015 AndrĂ© Nusser <andre.nusser@googlemail.com>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, write to the Free Software Foundation, Inc.,
27  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28  */
29
30 #ifdef WAF_BUILD
31 #include "gtk2ardour-config.h"
32 #endif
33
34 #include <cstdlib>
35 #include <clocale>
36 #include <cstring>
37 #include <cctype>
38 #include <cmath>
39 #include <list>
40 #include <sys/stat.h>
41
42 #include <boost/algorithm/string.hpp>
43
44 #include <gtk/gtkpaned.h>
45 #include <gtkmm/combo.h>
46 #include <gtkmm/label.h>
47 #include <gtkmm/paned.h>
48 #include <gtkmm/rc.h>
49 #include <gtkmm/stock.h>
50 #include <gtkmm/window.h>
51
52 #include "pbd/basename.h"
53 #include "pbd/file_utils.h"
54 #include "pbd/stacktrace.h"
55
56 #include "ardour/audioengine.h"
57 #include "ardour/filesystem_paths.h"
58 #include "ardour/search_paths.h"
59
60 #include "gtkmm2ext/colors.h"
61 #include "gtkmm2ext/utils.h"
62
63 #include "canvas/item.h"
64
65 #include "actions.h"
66 #include "context_menu_helper.h"
67 #include "debug.h"
68 #include "public_editor.h"
69 #include "keyboard.h"
70 #include "utils.h"
71 #include "pbd/i18n.h"
72 #include "rgb_macros.h"
73 #include "gui_thread.h"
74 #include "ui_config.h"
75 #include "ardour_dialog.h"
76 #include "ardour_ui.h"
77
78 using namespace std;
79 using namespace Gtk;
80 using namespace Glib;
81 using namespace PBD;
82 using Gtkmm2ext::Keyboard;
83
84 namespace ARDOUR_UI_UTILS {
85         sigc::signal<void>  DPIReset;
86 }
87
88 #ifdef PLATFORM_WINDOWS
89 #define random() rand()
90 #endif
91
92
93 /** Add an element to a menu, settings its sensitivity.
94  * @param m Menu to add to.
95  * @param e Element to add.
96  * @param s true to make sensitive, false to make insensitive
97  */
98 void
99 ARDOUR_UI_UTILS::add_item_with_sensitivity (Menu_Helpers::MenuList& m, Menu_Helpers::MenuElem e, bool s)
100 {
101         m.push_back (e);
102         if (!s) {
103                 m.back().set_sensitive (false);
104         }
105 }
106
107
108 gint
109 ARDOUR_UI_UTILS::just_hide_it (GdkEventAny */*ev*/, Gtk::Window *win)
110 {
111         win->hide ();
112         return 0;
113 }
114
115 static bool
116 idle_notify_engine_stopped ()
117 {
118         Glib::RefPtr<ToggleAction> tact = ActionManager::get_toggle_action ("Window", "toggle-audio-midi-setup");
119
120         MessageDialog msg (
121                         _("The current operation is not possible because of an error communicating with the audio hardware."),
122                         false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true);
123
124         msg.add_button (_("Cancel"), Gtk::RESPONSE_CANCEL);
125
126         if (tact && !tact->get_active()) {
127                 msg.add_button (_("Configure Hardware"), Gtk::RESPONSE_OK);
128         }
129
130         if (msg.run () == Gtk::RESPONSE_OK) {
131                 tact->set_active ();
132         }
133         return false; /* do not call again */
134 }
135
136 bool
137 ARDOUR_UI_UTILS::engine_is_running ()
138 {
139         if (ARDOUR::AudioEngine::instance()->running ()) {
140                 return true;
141         }
142         Glib::signal_idle().connect (sigc::ptr_fun (&idle_notify_engine_stopped));
143         return false;
144 }
145
146
147 /* xpm2rgb copied from nixieclock, which bore the legend:
148
149     nixieclock - a nixie desktop timepiece
150     Copyright (C) 2000 Greg Ercolano, erco@3dsite.com
151
152     and was released under the GPL.
153 */
154
155 unsigned char*
156 ARDOUR_UI_UTILS::xpm2rgb (const char** xpm, uint32_t& w, uint32_t& h)
157 {
158         static long vals[256], val;
159         uint32_t t, x, y, colors, cpp;
160         unsigned char c;
161         unsigned char *savergb, *rgb;
162
163         // PARSE HEADER
164
165         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
166                 error << string_compose (_("bad XPM header %1"), xpm[0])
167                       << endmsg;
168                 return 0;
169         }
170
171         savergb = rgb = (unsigned char*) malloc (h * w * 3);
172
173         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
174         for (t = 0; t < colors; ++t) {
175                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
176                 vals[c] = val;
177         }
178
179         // COLORMAP -> RGB CONVERSION
180         //    Get low 3 bytes from vals[]
181         //
182
183         const char *p;
184         for (y = h-1; y > 0; --y) {
185
186                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 3) {
187                         val = vals[(int)*p++];
188                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
189                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
190                         *(rgb+0) = val & 0xff;             // 0:R
191                 }
192         }
193
194         return (savergb);
195 }
196
197 unsigned char*
198 ARDOUR_UI_UTILS::xpm2rgba (const char** xpm, uint32_t& w, uint32_t& h)
199 {
200         static long vals[256], val;
201         uint32_t t, x, y, colors, cpp;
202         unsigned char c;
203         unsigned char *savergb, *rgb;
204         char transparent;
205
206         // PARSE HEADER
207
208         if ( sscanf(xpm[0], "%u%u%u%u", &w, &h, &colors, &cpp) != 4 ) {
209                 error << string_compose (_("bad XPM header %1"), xpm[0])
210                       << endmsg;
211                 return 0;
212         }
213
214         savergb = rgb = (unsigned char*) malloc (h * w * 4);
215
216         // LOAD XPM COLORMAP LONG ENOUGH TO DO CONVERSION
217
218         if (strstr (xpm[1], "None")) {
219                 sscanf (xpm[1], "%c", &transparent);
220                 t = 1;
221         } else {
222                 transparent = 0;
223                 t = 0;
224         }
225
226         for (; t < colors; ++t) {
227                 sscanf (xpm[t+1], "%c c #%lx", &c, &val);
228                 vals[c] = val;
229         }
230
231         // COLORMAP -> RGB CONVERSION
232         //    Get low 3 bytes from vals[]
233         //
234
235         const char *p;
236         for (y = h-1; y > 0; --y) {
237
238                 char alpha;
239
240                 for (p = xpm[1+colors+(h-y-1)], x = 0; x < w; x++, rgb += 4) {
241
242                         if (transparent && (*p++ == transparent)) {
243                                 alpha = 0;
244                                 val = 0;
245                         } else {
246                                 alpha = 255;
247                                 val = vals[(int)*p];
248                         }
249
250                         *(rgb+3) = alpha;                  // 3: alpha
251                         *(rgb+2) = val & 0xff; val >>= 8;  // 2:B
252                         *(rgb+1) = val & 0xff; val >>= 8;  // 1:G
253                         *(rgb+0) = val & 0xff;             // 0:R
254                 }
255         }
256
257         return (savergb);
258 }
259
260 /** Returns a Pango::FontDescription given a string describing the font.
261  *
262  * If the returned FontDescription does not specify a family, then
263  * the family is set to "Sans". This mirrors GTK's behaviour in
264  * gtkstyle.c.
265  *
266  * Some environments will force Pango to specify the family
267  * even if it was not specified in the string describing the font.
268  * Such environments should be left unaffected by this function,
269  * since the font family will be left alone.
270  *
271  * There may be other similar font specification enforcement
272  * that we might add here later.
273  */
274 Pango::FontDescription
275 ARDOUR_UI_UTILS::sanitized_font (std::string const& name)
276 {
277         Pango::FontDescription fd (name);
278
279         if (fd.get_family().empty()) {
280                 /* default: "Sans" or "ArdourSans" */
281                 fd.set_family (UIConfiguration::instance ().get_ui_font_family ());
282         }
283
284         return fd;
285 }
286
287 Pango::FontDescription
288 ARDOUR_UI_UTILS::get_font_for_style (string widgetname)
289 {
290         Gtk::Window window (WINDOW_TOPLEVEL);
291         Gtk::Label foobar;
292         Glib::RefPtr<Gtk::Style> style;
293
294         window.add (foobar);
295         foobar.set_name (widgetname);
296         foobar.ensure_style();
297
298         style = foobar.get_style ();
299
300         Glib::RefPtr<const Pango::Layout> layout = foobar.get_layout();
301
302         PangoFontDescription *pfd = const_cast<PangoFontDescription *> (pango_layout_get_font_description(const_cast<PangoLayout *>(layout->gobj())));
303
304         if (!pfd) {
305
306                 /* layout inherited its font description from a PangoContext */
307
308                 PangoContext* ctxt = (PangoContext*) pango_layout_get_context (const_cast<PangoLayout*>(layout->gobj()));
309                 pfd =  pango_context_get_font_description (ctxt);
310                 return Pango::FontDescription (pfd); /* make a copy */
311         }
312
313         return Pango::FontDescription (pfd); /* make a copy */
314 }
315
316 Gdk::Color
317 ARDOUR_UI_UTILS::gdk_color_from_rgb (uint32_t rgb)
318 {
319         Gdk::Color c;
320         set_color_from_rgb (c, rgb);
321         return c;
322 }
323
324 Gdk::Color
325 ARDOUR_UI_UTILS::gdk_color_from_rgba (uint32_t rgba)
326 {
327         Gdk::Color c;
328         set_color_from_rgb (c, rgba >> 8);
329         return c;
330 }
331
332 void
333 ARDOUR_UI_UTILS::set_color_from_rgb (Gdk::Color& c, uint32_t rgb)
334 {
335         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
336            multiplying by 256.
337         */
338         c.set_rgb ((rgb >> 16)*256, ((rgb & 0xff00) >> 8)*256, (rgb & 0xff)*256);
339 }
340
341 void
342 ARDOUR_UI_UTILS::set_color_from_rgba (Gdk::Color& c, uint32_t rgba)
343 {
344         /* Gdk::Color color ranges are 16 bit, so scale from 8 bit by
345            multiplying by 256.
346         */
347         c.set_rgb ((rgba >> 24)*256, ((rgba & 0xff0000) >> 16)*256, ((rgba & 0xff00) >> 8)*256);
348 }
349
350 uint32_t
351 ARDOUR_UI_UTILS::gdk_color_to_rgba (Gdk::Color const& c)
352 {
353         /* since alpha value is not available from a Gdk::Color, it is
354            hardcoded as 0xff (aka 255 or 1.0)
355         */
356
357         const uint32_t r = c.get_red_p () * 255.0;
358         const uint32_t g = c.get_green_p () * 255.0;
359         const uint32_t b = c.get_blue_p () * 255.0;
360         const uint32_t a = 0xff;
361
362         return RGBA_TO_UINT (r,g,b,a);
363 }
364
365 bool
366 ARDOUR_UI_UTILS::relay_key_press (GdkEventKey* ev, Gtk::Window* win)
367 {
368         return ARDOUR_UI::instance()->key_event_handler (ev, win);
369 }
370
371 bool
372 ARDOUR_UI_UTILS::emulate_key_event (unsigned int keyval)
373 {
374         GdkDisplay  *display = gtk_widget_get_display (GTK_WIDGET(ARDOUR_UI::instance()->main_window().gobj()));
375         GdkKeymap   *keymap  = gdk_keymap_get_for_display (display);
376         GdkKeymapKey *keymapkey = NULL;
377         gint n_keys;
378
379         if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keymapkey, &n_keys)) return false;
380         if (n_keys !=1) { g_free(keymapkey); return false;}
381
382         Gtk::Window& main_window (ARDOUR_UI::instance()->main_window());
383
384         GdkEventKey ev;
385         ev.type = GDK_KEY_PRESS;
386         ev.window = main_window.get_window()->gobj();
387         ev.send_event = FALSE;
388         ev.time = 0;
389         ev.state = 0;
390         ev.keyval = keyval;
391         ev.length = 0;
392         ev.string = const_cast<gchar*> ("");
393         ev.hardware_keycode = keymapkey[0].keycode;
394         ev.group = keymapkey[0].group;
395         g_free(keymapkey);
396
397         relay_key_press (&ev, &main_window);
398         ev.type = GDK_KEY_RELEASE;
399         return relay_key_press(&ev, &main_window);
400 }
401
402 Glib::RefPtr<Gdk::Pixbuf>
403 ARDOUR_UI_UTILS::get_xpm (std::string name)
404 {
405         if (!xpm_map[name]) {
406
407                 Searchpath spath(ARDOUR::ardour_data_search_path());
408
409                 spath.add_subdirectory_to_paths("pixmaps");
410
411                 std::string data_file_path;
412
413                 if(!find_file (spath, name, data_file_path)) {
414                         fatal << string_compose (_("cannot find XPM file for %1"), name) << endmsg;
415                 }
416
417                 try {
418                         xpm_map[name] =  Gdk::Pixbuf::create_from_file (data_file_path);
419                 } catch (const Glib::Error& e) {
420                         warning << "Caught Glib::Error: " << e.what() << endmsg;
421                 }
422         }
423
424         return xpm_map[name];
425 }
426
427 void
428 ARDOUR_UI_UTILS::get_color_themes (map<std::string,std::string>& themes)
429 {
430         Searchpath spath(ARDOUR::theme_search_path());
431
432         for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
433
434                 vector<string> entries;
435
436                 find_files_matching_pattern (entries, *s, string ("*") + UIConfiguration::color_file_suffix);
437
438                 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
439
440                         XMLTree tree;
441
442                         tree.read ((*e).c_str());
443                         XMLNode* root = tree.root();
444
445                         if (!root || root->name() != X_("Ardour")) {
446                                 continue;
447                         }
448
449                         XMLProperty const* prop = root->property (X_("theme-name"));
450
451                         if (!prop) {
452                                 continue;
453                         }
454
455                         std::string color_name = basename_nosuffix(*e);
456                         size_t sep = color_name.find_first_of("-");
457                         if (sep != string::npos) {
458                                 color_name = color_name.substr (0, sep);
459                         }
460                         themes.insert (make_pair (prop->value(), color_name));
461                 }
462         }
463 }
464
465 vector<string>
466 ARDOUR_UI_UTILS::get_icon_sets ()
467 {
468         Searchpath spath(ARDOUR::ardour_data_search_path());
469         spath.add_subdirectory_to_paths ("icons");
470         vector<string> r;
471
472         r.push_back (_("default"));
473
474         for (vector<string>::iterator s = spath.begin(); s != spath.end(); ++s) {
475
476                 vector<string> entries;
477
478                 get_paths (entries, *s, false, false);
479
480                 for (vector<string>::iterator e = entries.begin(); e != entries.end(); ++e) {
481                         if (Glib::file_test (*e, Glib::FILE_TEST_IS_DIR)) {
482                                 r.push_back (Glib::filename_to_utf8 (Glib::path_get_basename(*e)));
483                         }
484                 }
485         }
486
487         return r;
488 }
489
490 std::string
491 ARDOUR_UI_UTILS::get_icon_path (const char* cname, string icon_set, bool is_image)
492 {
493         std::string data_file_path;
494         string name = cname;
495
496         if (is_image) {
497                 name += X_(".png");
498         }
499
500         Searchpath spath(ARDOUR::ardour_data_search_path());
501
502         if (!icon_set.empty() && icon_set != _("default")) {
503
504                 /* add "icons/icon_set" but .. not allowed to add both of these at once */
505                 spath.add_subdirectory_to_paths ("icons");
506                 spath.add_subdirectory_to_paths (icon_set);
507
508                 find_file (spath, name, data_file_path);
509         } else {
510                 spath.add_subdirectory_to_paths ("icons");
511                 find_file (spath, name, data_file_path);
512         }
513
514         if (data_file_path.empty()) {
515                 Searchpath rc (ARDOUR::ardour_data_search_path());
516                 rc.add_subdirectory_to_paths ("resources");
517                 find_file (rc, name, data_file_path);
518         }
519
520         if (is_image && data_file_path.empty()) {
521
522                 if (!icon_set.empty() && icon_set != _("default")) {
523                         warning << string_compose (_("icon \"%1\" not found for icon set \"%2\", fallback to default"), cname, icon_set) << endmsg;
524                 }
525
526                 Searchpath def (ARDOUR::ardour_data_search_path());
527                 def.add_subdirectory_to_paths ("icons");
528
529                 if (!find_file (def, name, data_file_path)) {
530                         fatal << string_compose (_("cannot find icon image for %1 using %2"), name, spath.to_string()) << endmsg;
531                         abort(); /*NOTREACHED*/
532                 }
533         }
534
535         return data_file_path;
536 }
537
538 Glib::RefPtr<Gdk::Pixbuf>
539 ARDOUR_UI_UTILS::get_icon (const char* cname, string icon_set)
540 {
541         Glib::RefPtr<Gdk::Pixbuf> img;
542         try {
543                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname, icon_set));
544         } catch (const Gdk::PixbufError &e) {
545                 cerr << "Caught PixbufError: " << e.what() << endl;
546         } catch (...) {
547                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
548         }
549
550         return img;
551 }
552
553 namespace ARDOUR_UI_UTILS {
554 Glib::RefPtr<Gdk::Pixbuf>
555 get_icon (const char* cname)
556 {
557         Glib::RefPtr<Gdk::Pixbuf> img;
558         try {
559                 img = Gdk::Pixbuf::create_from_file (get_icon_path (cname));
560         } catch (const Gdk::PixbufError &e) {
561                 cerr << "Caught PixbufError: " << e.what() << endl;
562         } catch (...) {
563                 error << string_compose (_("Caught exception while loading icon named %1"), cname) << endmsg;
564         }
565
566         return img;
567 }
568 }
569
570 string
571 ARDOUR_UI_UTILS::longest (vector<string>& strings)
572 {
573         if (strings.empty()) {
574                 return string ("");
575         }
576
577         vector<string>::iterator longest = strings.begin();
578         string::size_type longest_length = (*longest).length();
579
580         vector<string>::iterator i = longest;
581         ++i;
582
583         while (i != strings.end()) {
584
585                 string::size_type len = (*i).length();
586
587                 if (len > longest_length) {
588                         longest = i;
589                         longest_length = len;
590                 }
591
592                 ++i;
593         }
594
595         return *longest;
596 }
597
598 bool
599 ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (guint keyval)
600 {
601         /* we assume that this does not change over the life of the process
602          */
603
604         static int comma_decimal = -1;
605
606         switch (keyval) {
607         case GDK_period:
608         case GDK_comma:
609                 if (comma_decimal < 0) {
610                         std::lconv* lc = std::localeconv();
611                         if (strchr (lc->decimal_point, ',') != 0) {
612                                 comma_decimal = 1;
613                         } else {
614                                 comma_decimal = 0;
615                         }
616                 }
617                 break;
618         default:
619                 break;
620         }
621
622         switch (keyval) {
623         case GDK_decimalpoint:
624         case GDK_KP_Separator:
625                 return true;
626
627         case GDK_period:
628                 if (comma_decimal) {
629                         return false;
630                 } else {
631                         return true;
632                 }
633                 break;
634         case GDK_comma:
635                 if (comma_decimal) {
636                         return true;
637                 } else {
638                         return false;
639                 }
640                 break;
641         case GDK_minus:
642         case GDK_plus:
643         case GDK_0:
644         case GDK_1:
645         case GDK_2:
646         case GDK_3:
647         case GDK_4:
648         case GDK_5:
649         case GDK_6:
650         case GDK_7:
651         case GDK_8:
652         case GDK_9:
653         case GDK_KP_Add:
654         case GDK_KP_Subtract:
655         case GDK_KP_Decimal:
656         case GDK_KP_0:
657         case GDK_KP_1:
658         case GDK_KP_2:
659         case GDK_KP_3:
660         case GDK_KP_4:
661         case GDK_KP_5:
662         case GDK_KP_6:
663         case GDK_KP_7:
664         case GDK_KP_8:
665         case GDK_KP_9:
666         case GDK_Return:
667         case GDK_BackSpace:
668         case GDK_Delete:
669         case GDK_KP_Enter:
670         case GDK_Home:
671         case GDK_End:
672         case GDK_Left:
673         case GDK_Right:
674                 return true;
675
676         default:
677                 break;
678         }
679
680         return false;
681 }
682
683 void
684 ARDOUR_UI_UTILS::resize_window_to_proportion_of_monitor (Gtk::Window* window, int max_width, int max_height)
685 {
686         Glib::RefPtr<Gdk::Screen> screen = window->get_screen ();
687         Gdk::Rectangle monitor_rect;
688         screen->get_monitor_geometry (0, monitor_rect);
689
690         int const w = std::min (int (monitor_rect.get_width() * 0.8), max_width);
691         int const h = std::min (int (monitor_rect.get_height() * 0.8), max_height);
692
693         window->resize (w, h);
694 }
695
696
697 /** Replace _ with __ in a string; for use with menu item text to make underscores displayed correctly */
698 string
699 ARDOUR_UI_UTILS::escape_underscores (string const & s)
700 {
701         string o;
702         string::size_type const N = s.length ();
703
704         for (string::size_type i = 0; i < N; ++i) {
705                 if (s[i] == '_') {
706                         o += "__";
707                 } else {
708                         o += s[i];
709                 }
710         }
711
712         return o;
713 }
714
715 Gdk::Color
716 ARDOUR_UI_UTILS::unique_random_color (list<Gdk::Color>& used_colors)
717 {
718         Gdk::Color newcolor;
719
720         while (1) {
721
722                 double h, s, v;
723
724                 h = fmod (random(), 360.0);
725                 s = (random() % 65535) / 65535.0;
726                 v = (random() % 65535) / 65535.0;
727
728                 s = min (0.5, s); /* not too saturated */
729                 v = max (0.9, v);  /* not too bright */
730                 newcolor.set_hsv (h, s, v);
731
732                 if (used_colors.size() == 0) {
733                         used_colors.push_back (newcolor);
734                         return newcolor;
735                 }
736
737                 for (list<Gdk::Color>::iterator i = used_colors.begin(); i != used_colors.end(); ++i) {
738                   Gdk::Color c = *i;
739                         float rdelta, bdelta, gdelta;
740
741                         rdelta = newcolor.get_red() - c.get_red();
742                         bdelta = newcolor.get_blue() - c.get_blue();
743                         gdelta = newcolor.get_green() - c.get_green();
744
745                         if (sqrt (rdelta*rdelta + bdelta*bdelta + gdelta*gdelta) > 25.0) {
746                                 /* different enough */
747                                 used_colors.push_back (newcolor);
748                                 return newcolor;
749                         }
750                 }
751
752                 /* XXX need throttle here to make sure we don't spin for ever */
753         }
754 }
755
756 string
757 ARDOUR_UI_UTILS::rate_as_string (float r)
758 {
759         char buf[32];
760         if (fmod (r, 1000.0f)) {
761                 snprintf (buf, sizeof (buf), "%.1f kHz", r/1000.0);
762         } else {
763                 snprintf (buf, sizeof (buf), "%.0f kHz", r/1000.0);
764         }
765         return buf;
766 }
767
768 string
769 ARDOUR_UI_UTILS::samples_as_time_string (samplecnt_t s, float rate, bool show_samples)
770 {
771         char buf[32];
772         if (rate <= 0) {
773                 snprintf (buf, sizeof (buf), "--");
774         } else if (s == 0) {
775                 snprintf (buf, sizeof (buf), "0");
776         } else if (s < 1000 && show_samples) {
777                 /* 0 .. 999 spl */
778                 snprintf (buf, sizeof (buf), "%" PRId64" spl", s);
779         } else if (s < (rate / 1000.f)) {
780                 /* 0 .. 999 usec */
781                 snprintf (buf, sizeof (buf), "%.0f \u00B5s", s * 1e+6f / rate);
782         } else if (s < (rate / 100.f)) {
783                 /* 1.000 .. 9.999 ms */
784                 snprintf (buf, sizeof (buf), "%.3f ms", s * 1e+3f / rate);
785         } else if (s < (rate / 10.f)) {
786                 /* 1.00 .. 99.99 ms */
787                 snprintf (buf, sizeof (buf), "%.2f ms", s * 1e+3f / rate);
788         } else if (s < rate) {
789                 /* 100.0 .. 999.9 ms */
790                 snprintf (buf, sizeof (buf), "%.1f ms", s * 1e+3f / rate);
791         } else if (s < rate * 10.f) {
792                 /* 1.000 s .. 9.999 s */
793                 snprintf (buf, sizeof (buf), "%.3f s", s / rate);
794         } else if (s < rate * 90.f) {
795                 /* 10.00 s .. 89.99 s */
796                 snprintf (buf, sizeof (buf), "%.2f s", s / rate);
797         } else {
798                 /* 1m30.0 ...  */
799                 snprintf (buf, sizeof (buf), "'%.0fm%.1f", s / (60.f * rate), fmodf (s / rate, 60));
800         }
801         buf[31] = '\0';
802         return buf;
803 }
804
805 bool
806 ARDOUR_UI_UTILS::windows_overlap (Gtk::Window *a, Gtk::Window *b)
807 {
808
809         if (!a || !b) {
810                 return false;
811         }
812         if (a->get_screen() == b->get_screen()) {
813                 gint ex, ey, ew, eh;
814                 gint mx, my, mw, mh;
815
816                 a->get_position (ex, ey);
817                 a->get_size (ew, eh);
818                 b->get_position (mx, my);
819                 b->get_size (mw, mh);
820
821                 GdkRectangle e;
822                 GdkRectangle m;
823                 GdkRectangle r;
824
825                 e.x = ex;
826                 e.y = ey;
827                 e.width = ew;
828                 e.height = eh;
829
830                 m.x = mx;
831                 m.y = my;
832                 m.width = mw;
833                 m.height = mh;
834
835                 if (gdk_rectangle_intersect (&e, &m, &r)) {
836                         return true;
837                 }
838         }
839         return false;
840 }
841
842 bool
843 ARDOUR_UI_UTILS::overwrite_file_dialog (Gtk::Window& parent, string title, string text)
844 {
845         ArdourDialog dialog (parent, title, true);
846         Label label (text);
847
848         dialog.get_vbox()->pack_start (label, true, true);
849         dialog.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
850         dialog.add_button (_("Overwrite"), Gtk::RESPONSE_ACCEPT);
851         dialog.show_all ();
852
853         switch (dialog.run()) {
854         case RESPONSE_ACCEPT:
855                 return true;
856         case RESPONSE_CANCEL:
857         default:
858                 return false;
859         }
860 }
861
862 bool
863 ARDOUR_UI_UTILS::running_from_source_tree ()
864 {
865         gchar const *x = g_getenv ("ARDOUR_THEMES_PATH");
866         return x && (string (x).find ("gtk2_ardour") != string::npos);
867 }
868
869 Gtk::Menu*
870 ARDOUR_UI_UTILS::shared_popup_menu ()
871 {
872         return ARDOUR_UI::instance()->shared_popup_menu ();
873 }