utils_ui.cc 18.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Glom
 *
 * Copyright (C) 2001-2004 Murray Cumming
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
17
18
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
19
20
 */

Murray Cumming's avatar
Murray Cumming committed
21
#include "config.h" // For GLOM_ENABLE_CLIENT_ONLY
22
23

#include <glom/utils_ui.h>
24
#include <glom/appwindow.h>
Murray Cumming's avatar
Murray Cumming committed
25
#include <libglom/utils.h>
26
27
28
29
#include <libglom/connectionpool.h>
#include <libglom/data_structure/layout/report_parts/layoutitem_fieldsummary.h>
#include <libglom/data_structure/glomconversions.h>

30
31
32
33
#include <libglom/data_structure/layout/layoutitem_image.h> // For GLOM_IMAGE_FORMAT
#include <gdkmm/pixbufloader.h>
#include <libgda/gda-blob-op.h> // For gda_blob_op_read_all()

34
#include <gtkmm/messagedialog.h>
35
36
37
#include <glibmm/convert.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
38
#include <glibmm/main.h>
39
#include <glibmm/i18n.h>
40
41
42
43
#include <iostream>   // for cout, endl

#include <stack>

44
45
46
47
48
// For ShellExecute:
#ifdef G_OS_WIN32
# include <windows.h>
#endif

49
50
51
namespace
{

52
53
static void on_css_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error,
    const Glib::ustring& error_clue)
54
55
{
  std::cerr << G_STRFUNC << ": Parsing error: " << error.what() << std::endl;
56
  std::cerr << "  css: " << error_clue << std::endl;
57
58
59

  if(section)
  {
60
61
62
    const auto file = section->get_file();
    if (file)
    {
Murray Cumming's avatar
Murray Cumming committed
63
      std::cerr << " URI = " << file->get_uri() << std::endl;
64
65
    }

66
67
68
69
70
71
72
    std::cerr << " start_line = " << section->get_start_line()+1
      << ", end_line = " << section->get_end_line()+1 << std::endl;
    std::cerr << " start_position = " << section->get_start_position()
      << ", end_position = " << section->get_end_position() << std::endl;
  }
}

73
static Glib::RefPtr<Gtk::CssProvider> create_css_provider(Gtk::Widget& widget, const Glib::ustring& error_clue)
74
75
76
{
  // Add a StyleProvider so we can change the color, background color, and font.
  // This was easier before Gtk::Widget::override_color() was deprecated.
77
  auto css_provider = Gtk::CssProvider::create();
78

79
  auto refStyleContext = widget.get_style_context();
80
81
82
83
  if(refStyleContext)
    refStyleContext->add_provider(css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

  css_provider->signal_parsing_error().connect(
84
85
86
    sigc::bind(
      sigc::ptr_fun(&on_css_parsing_error),
      error_clue));
87
88
89
90

  return css_provider;
}

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

static void load_into_css_provider(Gtk::Widget& widget, const Glib::ustring& css)
{
  auto css_provider = create_css_provider(widget, css);

  try
  {
    css_provider->load_from_data(css);
  }
  catch(const Gtk::CssProviderError& ex)
  {
    std::cerr << G_STRFUNC << ": Gtk::CssProvider::load_from_data() failed with CssProviderError: "
      << ex.what() << std::endl;
    std::cerr << "  with css: " << css << std::endl;
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << G_STRFUNC << ": Gtk::CssProvider::load_from_data() failed with Error: "
      << ex.what() << std::endl;
    std::cerr << "  with css: " << css << std::endl;
  }
}

114
115
116
117
118
} //anonymous namespace

namespace Glom
{

119
// Run dialog and response on Help if appropriate.
120
int UiUtils::dialog_run_with_help(Gtk::Dialog* dialog, const Glib::ustring& id)
121
{
Murray Cumming's avatar
Murray Cumming committed
122
  int result = dialog->run();
Murray Cumming's avatar
Murray Cumming committed
123

Murray Cumming's avatar
Murray Cumming committed
124
  while (result == Gtk::RESPONSE_HELP)
125
  {
126
    show_help(dialog, id);
127
128
129
130
131
132
133
134
135
136
137
138
139
    result = dialog->run();
  }

  dialog->hide();
  return result;
}

/*
 * Help::show_help(const std::string& id)
 *
 * Launch a help browser with the glom help and load the given id if given
 * If the help cannot be found an error dialog will be shown
 */
140
void UiUtils::show_help(Gtk::Window* parent_window, const Glib::ustring& id)
141
{
142
143
144
  //TODO: Check that this actually works for any dialog that has an ID in the help files.
  Glib::ustring uri = "help:glom";
  if (!id.empty())
145
    uri += "/" + id;
Murray Cumming's avatar
Murray Cumming committed
146

147
148
  try
  {
149
150
151
    //Use the GNOME help browser:
    GError* gerror = nullptr;

152
    if(!gtk_show_uri_on_window(parent_window ? parent_window->gobj() : nullptr,
153
      uri.c_str(), GDK_CURRENT_TIME, &gerror))
154
    {
155
156
157
158
159
      std::cerr << G_STRFUNC << ": " << gerror->message << std::endl;

      const Glib::ustring message(gerror->message);
      g_error_free(gerror);
      throw std::runtime_error(message);
160
161
162
163
    }
  }
  catch(const std::exception& ex)
  {
Murray Cumming's avatar
Murray Cumming committed
164
    const auto message = Glib::ustring::compose(_("Could not display help: %1"), Glib::ustring(ex.what()));
165
166
167
168
169
    Gtk::MessageDialog dialog(message, false, Gtk::MESSAGE_ERROR);
    dialog.run();
  }
}

170
void UiUtils::show_ok_dialog(const Glib::ustring& title, const Glib::ustring& message, Gtk::Window* parent, Gtk::MessageType message_type)
171
172
173
174
175
176
177
178
179
{
  Gtk::MessageDialog dialog("<b>" + title + "</b>", true /* markup */, message_type, Gtk::BUTTONS_OK);
  dialog.set_secondary_text(message);
  if(parent)
    dialog.set_transient_for(*parent);

  dialog.run();
}

180
void UiUtils::show_ok_dialog(const Glib::ustring& title, const Glib::ustring& message, Gtk::Window& parent, Gtk::MessageType message_type)
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
{
  show_ok_dialog(title, message, &parent, message_type);
}

namespace
{

static void on_window_hide(Glib::RefPtr<Glib::MainLoop> main_loop, sigc::connection handler_connection)
{
  handler_connection.disconnect(); //This should release a main_loop reference.
  main_loop->quit();

  //main_loop should be destroyed soon, because nothing else is using it.
}

} //anonymous namespace.

198
void UiUtils::show_window_until_hide(Gtk::Window* window)
199
200
201
202
{
  if(!window)
    return;

203
  auto main_loop = Glib::MainLoop::create(false /* not running */);
204
205
206

  //Stop the main_loop when the window is hidden:
  sigc::connection handler_connection; //TODO: There seems to be a crash if this is on the same line.
Murray Cumming's avatar
Murray Cumming committed
207
  handler_connection = window->signal_hide().connect(
208
209
210
211
    sigc::bind(
      sigc::ptr_fun(&on_window_hide),
      main_loop, handler_connection
    ) );
Murray Cumming's avatar
Murray Cumming committed
212

213
214
215
216
  window->show();
  main_loop->run(); //Run and block until it is stopped by the hide signal handler.
}

217
Glib::ustring UiUtils::bold_message(const Glib::ustring& message)
218
219
220
221
222
{
  return "<b>" + message + "</b>";
}


223
Glib::RefPtr<Gdk::Pixbuf> UiUtils::get_pixbuf_for_gda_value(const Gnome::Gda::Value& value)
224
225
226
227
228
229
230
231
232
{
  Glib::RefPtr<Gdk::Pixbuf> result;

  if(value.get_value_type() == GDA_TYPE_BINARY || value.get_value_type() == GDA_TYPE_BLOB)
  {
    glong buffer_binary_length;
    gconstpointer buffer_binary;
    if(value.get_value_type() == GDA_TYPE_BLOB)
    {
Murray Cumming's avatar
Murray Cumming committed
233
      const auto blob = value.get_blob();
234
235
236
237
238
239
240
241
      if(gda_blob_op_read_all(blob->op, const_cast<GdaBlob*>(blob)))
      {
        buffer_binary_length = blob->data.binary_length;
        buffer_binary = blob->data.data;
      }
      else
      {
        buffer_binary_length = 0;
Murray Cumming's avatar
Murray Cumming committed
242
        buffer_binary = nullptr;
243
        std::cerr << G_STRFUNC << ": Failed to read BLOB data\n";
244
245
246
247
248
249
250
251
252
253
      }
    }
    else
    {
      buffer_binary = value.get_binary(buffer_binary_length);
    }

    /* Note that this is regular binary data, not escaped text representing the binary data: */
    if(buffer_binary && buffer_binary_length)
    {
254
      //typedef std::vector<Gdk::PixbufFormat> type_list_formats;
Murray Cumming's avatar
Murray Cumming committed
255
      //const auto formats = Gdk::Pixbuf::get_formats();
256
      //std::cout << "Debug: Supported pixbuf formats:\n";
257
      //for(const auto& item : formats)
258
259
260
261
      //{
      //  std::cout << " name=" << iter->get_name() << ", writable=" << iter->is_writable() << std::endl;
      //}

Murray Cumming's avatar
Murray Cumming committed
262
      Glib::RefPtr<Gdk::PixbufLoader> refPixbufLoader;
263
264
      try
      {
Armin Burgmeier's avatar
Armin Burgmeier committed
265
        refPixbufLoader = Gdk::PixbufLoader::create();
266
267
268
269
      }
      catch(const Gdk::PixbufError& ex)
      {
        refPixbufLoader.reset();
270
        std::cerr << G_STRFUNC << ": PixbufLoader::create failed: " << ex.what() << std::endl;
271
272
273
274
275
276
277
      }

      if(refPixbufLoader)
      {
        guint8* puiData = (guint8*)buffer_binary;
        try
        {
278
          refPixbufLoader->write(puiData, static_cast<gsize>(buffer_binary_length));
279
280
281
282
283
284
285
          result = refPixbufLoader->get_pixbuf();

          refPixbufLoader->close(); //This throws if write() threw, so it must be inside the try block.
        }

        catch(const Glib::Exception& ex)
        {
286
          std::cerr << G_STRFUNC << ": PixbufLoader::write() failed: " << ex.what() << std::endl;
287
288
289
290
291
292
293
294
295
296
297
298
        }
      }

      //TODO: load the image, using the mime type stored elsewhere.
      //pixbuf = Gdk::Pixbuf::create_from_data(
    }

  }

  return result;
}

299
300
namespace {

301
302
303
static int get_width_for_text(Gtk::Widget& widget, const Glib::ustring& text)
{
  //Get the width required for this string in the current font:
304
  auto refLayout = widget.create_pango_layout(text);
305
306
307
308
309
310
311
312
313
314
315
  int width = 0;
  int height = 0;
  refLayout->get_pixel_size(width, height);
  int result = width;

  //Add a bit more:
  result += 10;

  return result;
}

Murray Cumming's avatar
Murray Cumming committed
316
} //anonymous namespace.
317

Murray Cumming's avatar
Murray Cumming committed
318
int UiUtils::get_suitable_field_width_for_widget(Gtk::Widget& widget, const std::shared_ptr<const LayoutItem_Field>& field_layout, bool or_title, bool for_treeview)
319
{
320
  int result = 150; //Suitable default.
321

Murray Cumming's avatar
Murray Cumming committed
322
  const auto field_type = field_layout->get_glom_type();
323
324
325
326

  Glib::ustring example_text;
  switch(field_type)
  {
327
    case(Field::glom_field_type::DATE):
328
    {
329
      const Glib::Date date(31, Glib::Date::Month(12), 2000);
330
331
332
      example_text = Conversions::get_text_for_gda_value(field_type, Gnome::Gda::Value(date));
      break;
    }
333
    case(Field::glom_field_type::TIME):
334
335
336
337
338
339
340
341
    {
      Gnome::Gda::Time time = {0, 0, 0, 0, 0};
      time.hour = 24;
      time.minute = 59;
      time.second = 59;
      example_text = Conversions::get_text_for_gda_value(field_type, Gnome::Gda::Value(time));
      break;
    }
342
    case(Field::glom_field_type::NUMERIC):
343
    {
344
345
346
347
      if(for_treeview)
        example_text = "EUR 999.99";
      else
        example_text = "EUR 9999999999";
Murray Cumming's avatar
Murray Cumming committed
348

349
350
      break;
    }
351
352
    case(Field::glom_field_type::TEXT):
    case(Field::glom_field_type::IMAGE): //Give images the same width as text fields, so they will often line up.
353
    {
354
355
356
      if(for_treeview)
        example_text = "AAAAAAAAAAAA";
      else
357
        example_text = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
358

359
360
361
362
363
364
365
366
367
368
369
      break;
    }
    default:
    {
      break;
    }
  }

  if(!example_text.empty())
  {
    //Get the width required for this string in the current font:
370
    result = get_width_for_text(widget, example_text);
371
372
373
374
375
  }

  if(or_title)
  {
    //Make sure that there's enough space for the title too.
Murray Cumming's avatar
Murray Cumming committed
376
    const auto title_width = get_width_for_text(widget, item_get_title(field_layout));
377
378
    if(title_width > result)
      result = title_width;
379
380
381
382
383
384
  }

  return result;
}


385
std::string UiUtils::get_filepath_with_extension(const std::string& filepath, const std::string& extension)
386
387
388
389
390
391
392
393
394
395
396
397
{
  std::string result = filepath;

  bool add_ext = false;
  const std::string str_ext = "." + extension;

  if(result.size() < str_ext.size()) //It can't have the ext already if it's not long enough.
  {
    add_ext = true; //It isn't there already.
  }
  else
  {
Murray Cumming's avatar
Murray Cumming committed
398
    const auto strEnd = result.substr(result.size() - str_ext.size());
399
400
401
402
403
404
405
406
407
408
409
410
411
    if(strEnd != str_ext) //If it doesn't already have the extension
      add_ext = true;
  }

  //Add extension if necessay.
  if(add_ext)
    result += str_ext;

  //TODO: Do not replace existing extensions, so it could be e.g. 'something.blah.theext'

  return result;
}

Murray Cumming's avatar
Merged.    
Murray Cumming committed
412

413
//static:
414
Glib::RefPtr<Gdk::Pixbuf> UiUtils::image_scale_keeping_ratio(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf, int target_height, int target_width)
415
416
417
418
419
420
421
{
  if( (target_height == 0) || (target_width == 0) )
    return Glib::RefPtr<Gdk::Pixbuf>(); //This shouldn't happen anyway.

  if(!pixbuf)
    return pixbuf;

422
  enum class enum_scale_mode
423
  {
424
425
426
427
    WIDTH,
    HEIGHT,
    BOTH,
    NONE
428
429
  };

430
  enum_scale_mode scale_mode = enum_scale_mode::NONE; //Start with either the width or height, and scale the other according to the ratio.
431

Murray Cumming's avatar
Murray Cumming committed
432
433
  const auto pixbuf_height = pixbuf->get_height();
  const auto pixbuf_width = pixbuf->get_width();
434
435
436
437
438

  if(pixbuf_height > target_height)
  {
    if(pixbuf_width > target_width)
    {
439
      scale_mode = enum_scale_mode::BOTH;
440
441
442
443
    }
    else
    {
      //Only the height is bigger:
444
      scale_mode = enum_scale_mode::HEIGHT;
445
446
447
448
449
    }
  }
  else if(pixbuf_width > target_width)
  {
    //Only the height is bigger:
450
    scale_mode = enum_scale_mode::WIDTH;
451
452
  }

453
  if(scale_mode == enum_scale_mode::NONE)
454
    return pixbuf;
455
  else if(scale_mode == enum_scale_mode::HEIGHT)
456
  {
Murray Cumming's avatar
Murray Cumming committed
457
    const float ratio = (float)target_height / (float)pixbuf_height;
458
459
    target_width = (int)((float)pixbuf_width * ratio);
  }
460
  else if(scale_mode == enum_scale_mode::WIDTH)
461
462
463
  {
    const float ratio = (float)target_width / (float) pixbuf_width;
    target_height = (int)((float)pixbuf_height * ratio);
464
  }
465
  else if(scale_mode == enum_scale_mode::BOTH)
466
  {
Murray Cumming's avatar
Murray Cumming committed
467
    const auto ratio = std::min(
468
469
470
471
      (float)target_width / (float) pixbuf_width,
      (float)target_height / (float) pixbuf_height);
    target_width = (int)((float)pixbuf_width * ratio);
    target_height = (int)((float)pixbuf_height * ratio);
472
473
474
475
476
477
478
479
480
481
  }

 if( (target_height == 0) || (target_width == 0) )
 {
   return Glib::RefPtr<Gdk::Pixbuf>(); //This shouldn't happen anyway. It seems to happen sometimes though, when ratio is very small.
 }

  return pixbuf->scale_simple(target_width, target_height, Gdk::INTERP_NEAREST);
}

482
bool UiUtils::show_warning_no_records_found(Gtk::Window& transient_for)
483
{
Murray Cumming's avatar
Murray Cumming committed
484
  const auto message = _("Your find criteria did not match any records in the table.");
485

486
  Gtk::MessageDialog dialog(UiUtils::bold_message(_("No Records Found")), true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
487
488
  dialog.set_secondary_text(message);
  dialog.set_transient_for(transient_for);
489

490
  dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
491
492
  dialog.add_button(_("New Find"), Gtk::RESPONSE_OK);

Murray Cumming's avatar
Murray Cumming committed
493
  const auto find_again = (dialog.run() == Gtk::RESPONSE_OK);
494
495
496
  return find_again;
}

497

498
void UiUtils::show_report_in_browser(const std::string& filepath, Gtk::Window* parent_window)
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
{
  //Give the user a clue, in case the web browser opens in the background, for instance in a new tab:
  if(parent_window)
    show_ok_dialog(_("Report Finished"), _("The report will now be opened in your web browser."), *parent_window, Gtk::MESSAGE_INFO);

#ifdef G_OS_WIN32
  // gtk_show_uri doesn't seem to work on Win32, at least not for local files
  // We use Windows API instead.
  // TODO: Check it again and file a bug if necessary.
  ShellExecute(0, "open", filepath.c_str(), 0, 0, SW_SHOW);
#else

  Glib::ustring uri;
  try
  {
    uri = Glib::filename_to_uri(filepath);
  }
  catch(const Glib::ConvertError& ex)
  {
    std::cerr << G_STRFUNC << ": Could not convert filepath to URI: " << filepath << std::endl;
    return;
  }

  //Use the GNOME browser:
Murray Cumming's avatar
Murray Cumming committed
523
  GError* gerror = nullptr;
524

525
  if(!gtk_show_uri_on_window(parent_window ? parent_window->gobj() : nullptr,
526
    uri.c_str(), GDK_CURRENT_TIME, &gerror))
527
528
529
530
531
532
533
  {
    std::cerr << G_STRFUNC << ": " << gerror->message << std::endl;
    g_error_free(gerror);
  }
#endif //G_OS_WIN32
}

534
std::string UiUtils::get_icon_path(const Glib::ustring& filename)
535
{
536
  return  "/org/gnome/glom/data/icons/" + filename;
537
538
}

539
bool UiUtils::script_check_for_pygtk2_with_warning(const Glib::ustring& script, Gtk::Window* parent_window)
540
541
542
{
  if(!Utils::script_check_for_pygtk2(script))
  {
543
    UiUtils::show_ok_dialog(_("Script Uses PyGTK 2"),
544
545
546
547
548
549
550
      _("Glom cannot run this script because it uses pygtk 2, but Glom uses GTK+ 3, and attempting to use pygtk 2 would cause Glom to crash."), parent_window, Gtk::MESSAGE_ERROR);
    return false;
  }

  return true;
}

551
void UiUtils::treeview_delete_all_columns(Gtk::TreeView* treeview)
552
553
554
555
556
557
558
559
560
561
562
{
  if(!treeview)
    return;

  //We use this instead of just Gtk::TreeView::remove_all_columns()
  //because that deletes columns as a side-effect of unreferencing them,
  //and that behaviour might be fixed in gtkmm sometime,
  //and whether they should be deleted by that would depend on whether we used Gtk::manage().
  //Deleting them explicitly is safer and clearer. murrayc.

  //Remove all View columns:
563
564
  auto vecViewColumns = treeview->get_columns();
  for (auto& view_column : vecViewColumns)
565
  {
566
    if(!view_column)
567
568
      continue;

Murray Cumming's avatar
Murray Cumming committed
569
    GtkTreeViewColumn* weak_ptr = nullptr;
570
    g_object_add_weak_pointer (G_OBJECT (view_column->gobj()), (gpointer*)&weak_ptr);
571
572
573
574
575

    //Keep the object alive, instead of letting gtk_tree_view_remove_column() delete it by reducing its reference to 0,
    //so we can explicitly delete it.
    //This feels safer, considering some strange crashes I've seen when using Gtk::TreeView::remove_all_columns(),
    //though that might have been just because we didn't reset m_treeviewcolumn_button. murrayc.
576
577
578
    view_column->reference();
    treeview->remove_column(*view_column);
    delete view_column; //This should cause it to be removed.
579
580
581

    if(weak_ptr)
    {
582
      std::cerr << G_STRFUNC << ": The GtkTreeViewColumn was not destroyed as expected.\n";
583
584
585
586
    }
  }
}

587
588
589
590
void UiUtils::container_remove_all(Gtk::Container& container)
{
  //Remove all (usally just one) widgets from m_vbox_parent:
  //Gtk::Bin::remove() is easier but after GtkAlignment was deprecated, there is no suitable widget.
591
  for(const auto& child : container.get_children())
592
593
  {
    if(child)
594
      container.remove(*child);
595
596
597
  }
}

598
void UiUtils::load_font_into_css_provider(Gtk::Widget& widget, const Glib::ustring& pango_font_name)
599
{
600
601
602
603
604
605
606
607
608
  const Pango::FontDescription font_desc(pango_font_name);
  const auto font_family = font_desc.get_family();
  const auto font_size = font_desc.get_size();
  const auto css =
    "* {\n" +
    (font_family.empty() ? "" : "    font-family: " + font_desc.get_family() + ";\n") +
    (font_size == 0 ? "" : "    font-size: " + std::to_string(font_size / PANGO_SCALE) + "pt;\n") +
    "}";

609
  load_into_css_provider(widget, css);
610
611
612
613
}

void UiUtils::load_color_into_css_provider(Gtk::Widget& widget, const Glib::ustring& color)
{
614
615
  const auto css = "* { color: " + color + "; }";
  load_into_css_provider(widget, css);
616
617
618
619
}

void UiUtils::load_background_color_into_css_provider(Gtk::Widget& widget, const Glib::ustring& color)
{
620
  auto css = "* { background-color: " + color + "; }";
621
622
623
624
625
626
627
628
629
630
631
/*
      "GtkTextView {\n"
      "  background-color: " + color + "; }\n"
      "GtkTextView:backdrop {\n"
      "  background-color: " + color + "; }\n"
      "GtkTextView.view:selected {\n"
      "  background-color: " + color + "; }\n"
      "GtkTextView.view:insensitive {\n"
      "  background-color: " + color + "; }"
*/

632
  load_into_css_provider(widget, css);
633
634
}

635
} //namespace Glom