libyui-gtk  2.44.7
YGUtils.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 /*
5  Textdomain "gtk"
6  */
7 
8 #define YUILogComponent "gtk"
9 #include <yui/Libyui_config.h>
10 #include <string.h>
11 #include "YGUtils.h"
12 #include "YGUI.h"
13 #include "YGi18n.h"
14 
15 static inline void skipSpace (const char *instr, int *i)
16 { while (g_ascii_isspace (instr[*i])) (*i)++; }
17 
18 typedef struct {
19  GString *tag;
20  int tag_len : 31;
21  unsigned int early_closer : 1;
22 } TagEntry;
23 
24 static TagEntry *
25 tag_entry_new (GString *tag, int tag_len)
26 {
27  static const char *early_closers[] = { "p", "li" };
28  TagEntry *entry = g_new (TagEntry, 1);
29  entry->tag = tag;
30  entry->tag_len = tag_len;
31  entry->early_closer = FALSE;
32 
33  unsigned int i;
34  for (i = 0; i < G_N_ELEMENTS (early_closers); i++)
35  if (!g_ascii_strncasecmp (tag->str, early_closers[i], tag_len))
36  entry->early_closer = TRUE;
37  return entry;
38 }
39 
40 static void
41 tag_entry_free (TagEntry *entry)
42 {
43  if (entry && entry->tag)
44  g_string_free (entry->tag, TRUE);
45  g_free (entry);
46 }
47 
48 static void
49 emit_unclosed_tags_for (GString *outp, GQueue *tag_queue, const char *tag_str, int tag_len)
50 {
51  gboolean matched = FALSE;
52 
53  // top-level tag ...
54  if (g_queue_is_empty (tag_queue))
55  return;
56 
57  do {
58  TagEntry *last_entry = (TagEntry *)g_queue_pop_tail (tag_queue);
59  if (!last_entry)
60  break;
61 
62  if (last_entry->tag_len != tag_len ||
63  g_ascii_strncasecmp (last_entry->tag->str, tag_str, tag_len)) {
64  /* different tag - emit a close ... */
65  g_string_append (outp, "</");
66  g_string_append_len (outp, last_entry->tag->str, last_entry->tag_len);
67  g_string_append_c (outp, '>');
68  } else
69  matched = TRUE;
70 
71  tag_entry_free (last_entry);
72  } while (!matched);
73 }
74 
75 static gboolean
76 check_early_close (GString *outp, GQueue *tag_queue, TagEntry *entry)
77 {
78  TagEntry *last_tag;
79 
80  // Early closers:
81  if (!entry->early_closer)
82  return FALSE;
83 
84  last_tag = (TagEntry *) g_queue_peek_tail (tag_queue);
85  if (!last_tag || !last_tag->early_closer)
86  return FALSE;
87 
88  if (entry->tag_len != last_tag->tag_len ||
89  g_ascii_strncasecmp (last_tag->tag->str, entry->tag->str, entry->tag_len))
90  return FALSE;
91 
92  // Emit close & leave last tag on the stack
93 
94  g_string_append (outp, "</");
95  g_string_append_len (outp, entry->tag->str, entry->tag_len);
96  g_string_append_c (outp, '>');
97 
98  return TRUE;
99 }
100 
101 /* Some entities are translated by the xhtml parser, but not all... */
102 typedef struct EntityMap {
103  const gchar *html, *text;
104 } EntityMap;
105 
106 static const EntityMap entities[] = {
107  { "nbsp", " " },
108  { "product", 0 }, // dynamic
109 };
110 
111 static const EntityMap *lookup_entity (const char *html)
112 {
113  unsigned int i;
114  for (i = 0; i < sizeof (entities) / sizeof (EntityMap); i++)
115  if (!g_ascii_strncasecmp (html+1, entities[i].html, strlen (entities[i].html)))
116  return entities+i;
117  return NULL;
118 }
119 
120 // We have to:
121 // + rewrite <br> and <hr> tags
122 // + deal with <a attrib=noquotes>
123 gchar *ygutils_convert_to_xhtml (const char *instr)
124 {
125  GString *outp = g_string_new ("");
126  GQueue *tag_queue = g_queue_new();
127  int i = 0;
128 
129  gboolean allow_space = FALSE, pre_mode = FALSE;
130  skipSpace (instr, &i);
131 
132  // we must add an outer tag to make GMarkup happy
133  g_string_append (outp, "<body>");
134 
135  for (; instr[i] != '\0'; i++)
136  {
137  // Tag foo
138  if (instr[i] == '<') {
139  // ignore comments
140  if (strncmp (&instr[i], "<!--", 4) == 0) {
141  for (i += 3; instr[i] != '\0'; i++)
142  if (strncmp (&instr[i], "-->", 3) == 0) {
143  i += 2;
144  break;
145  }
146  continue;
147  }
148 
149  gint j;
150  gboolean is_close = FALSE;
151  gboolean in_tag;
152  int tag_len;
153  GString *tag = g_string_sized_new (20);
154 
155  i++;
156  skipSpace (instr, &i);
157 
158  if (instr[i] == '/') {
159  i++;
160  is_close = TRUE;
161  }
162 
163  skipSpace (instr, &i);
164 
165  // find the tag name piece
166  in_tag = TRUE;
167  tag_len = 0;
168  for (; instr[i] != '>' && instr[i]; i++) {
169  if (in_tag) {
170  if (!g_ascii_isalnum(instr[i]))
171  in_tag = FALSE;
172  else
173  tag_len++;
174  }
175  g_string_append_c (tag, instr[i]);
176  }
177 
178  // Unmatched tags
179  if (!is_close && tag_len == 2 &&
180  (!g_ascii_strncasecmp (tag->str, "hr", 2) ||
181  !g_ascii_strncasecmp (tag->str, "br", 2)) &&
182  tag->str[tag->len - 1] != '/')
183  g_string_append_c (tag, '/');
184 
185  if (!g_ascii_strncasecmp (tag->str, "pre", 3))
186  pre_mode = !is_close;
187 
188  // Add quoting for un-quoted attributes
189  unsigned int k;
190  for (k = 0; k < tag->len; k++) {
191  if (tag->str[k] == '=') {
192  gboolean unquote = tag->str[k+1] != '"';
193  if (unquote)
194  g_string_insert_c (tag, k+1, '"');
195  else
196  k++;
197  for (k++; tag->str[k]; k++) {
198  if (unquote && g_ascii_isspace (tag->str[k]))
199  break;
200  else if (!unquote && tag->str[k] == '"')
201  break;
202  }
203  if (unquote)
204  g_string_insert_c (tag, k, '"');
205  }
206  else
207  tag->str[k] = g_ascii_tolower (tag->str[k]);
208  }
209 
210  // Is it an open or close ?
211  j = tag->len - 1;
212 
213  while (j > 0 && g_ascii_isspace (tag->str[j])) j--;
214 
215  gboolean is_open_close = (tag->str[j] == '/');
216  if (is_open_close)
217  ; // ignore it
218  else if (is_close)
219  emit_unclosed_tags_for (outp, tag_queue, tag->str, tag_len);
220  else {
221  TagEntry *entry = tag_entry_new (tag, tag_len);
222 
223  entry->tag = tag;
224  entry->tag_len = tag_len;
225 
226  if (!check_early_close (outp, tag_queue, entry))
227  g_queue_push_tail (tag_queue, entry);
228  else {
229  entry->tag = NULL;
230  tag_entry_free (entry);
231  }
232  }
233 
234  g_string_append_c (outp, '<');
235  if (is_close)
236  g_string_append_c (outp, '/');
237  g_string_append_len (outp, tag->str, tag->len);
238  g_string_append_c (outp, '>');
239 
240  if (is_close || is_open_close)
241  g_string_free (tag, TRUE);
242 
243  allow_space = is_close; // don't allow space after opening a tag
244  }
245 
246  else if (instr[i] == '&') { // Entity
247  const EntityMap *entity = lookup_entity (instr+i);
248  if (entity) {
249  if (!strcmp (entity->html, "product"))
250  g_string_append (outp, YUI::app()->productName().c_str());
251  else
252  g_string_append (outp, entity->text);
253  i += strlen (entity->html);
254  if (instr[i+1] == ';') i++;
255  }
256  else {
257  int j;
258  // check it is a valid entity - not a floating '&' in a <pre> tag eg.
259  for (j = i + 1; instr[j] != '\0'; j++) {
260  if (!g_ascii_isalnum (instr[j]) && instr[j] != '#')
261  break;
262  }
263  if (instr[j] != ';') // entity terminator
264  g_string_append (outp, "&amp;");
265  else
266  g_string_append_c (outp, instr[i]);
267  }
268  allow_space = TRUE;
269  }
270 
271  else { // Normal text
272  if (!pre_mode && g_ascii_isspace (instr[i])) {
273  if (allow_space)
274  g_string_append_c (outp, ' ');
275  allow_space = FALSE; // one space is enough
276  }
277  else {
278  allow_space = TRUE;
279  g_string_append_c (outp, instr[i]);
280  }
281  }
282  }
283 
284  emit_unclosed_tags_for (outp, tag_queue, "", 0);
285  g_queue_free (tag_queue);
286  g_string_append (outp, "</body>");
287 
288  gchar *ret = g_string_free (outp, FALSE);
289  return ret;
290 }
291 
292 std::string YGUtils::mapKBAccel (const std::string &src)
293 {
294  // conversion pairs: ('_', '__') ('&&', '&') ('&', '_')
295  std::string::size_type length = src.length(), i;
296  std::string str;
297  str.reserve (length);
298  for (i = 0; i < length; i++) {
299  if (src[i] == '_')
300  str += "__";
301  else if (src[i] == '&') {
302  if (i+1 < length && src[i+1] == '&') {
303  str += '&'; // escaping
304  i++;
305  }
306  else
307  str += '_';
308  }
309  else
310  str += src[i];
311  }
312  return str;
313 }
314 
315 char *ygutils_mapKBAccel (const char *src)
316 {
317  std::string ret (YGUtils::mapKBAccel (src));
318  return strdup (ret.c_str());
319 }
320 
321 void YGUtils::setFilter (GtkEntry *entry, const std::string &validChars)
322 {
323  struct inner {
324  static void insert_text_cb (GtkEditable *editable, const gchar *new_text,
325  gint new_text_length, gint *pos)
326  {
327  const gchar *valid_chars = (gchar *) g_object_get_data (G_OBJECT (editable),
328  "valid-chars");
329  if (valid_chars) {
330  const gchar *i, *j;
331  for (i = new_text; *i; i++) {
332  for (j = valid_chars; *j; j++) {
333  if (*i == *j)
334  break;
335  }
336  if (!*j) {
337  // not valid text
338  g_signal_stop_emission_by_name (editable, "insert_text");
339  gtk_widget_error_bell (GTK_WIDGET (editable));
340  return;
341  }
342  }
343  }
344  }
345  };
346 
347  if (g_object_get_data (G_OBJECT (entry), "insert-text-set"))
348  g_object_disconnect (G_OBJECT (entry), "insert-text",
349  G_CALLBACK (inner::insert_text_cb), NULL);
350 
351  if (!validChars.empty()) {
352  gchar *chars = g_strdup (validChars.c_str());
353  g_object_set_data_full (G_OBJECT (entry), "valid-chars", chars, g_free);
354  g_signal_connect (G_OBJECT (entry), "insert-text",
355  G_CALLBACK (inner::insert_text_cb), NULL);
356  g_object_set_data (G_OBJECT (entry), "insert-text-set", GINT_TO_POINTER (1));
357  }
358  else
359  g_object_set_data (G_OBJECT (entry), "insert-text-set", GINT_TO_POINTER (0));
360 }
361 
362 void ygutils_setFilter (GtkEntry *entry, const char *validChars)
363 { YGUtils::setFilter (entry, validChars); }
364 
365 void YGUtils::replace (std::string &str, const char *mouth, int mouth_len, const char *food)
366 {
367  if (mouth_len < 0)
368  mouth_len = strlen (mouth);
369  std::string::size_type i = 0;
370  while ((i = str.find (mouth, i)) != std::string::npos) {
371  str.erase (i, mouth_len);
372  str.insert (i, food);
373  }
374 }
375 
376 std::string YGUtils::truncate (const std::string &str, int length, int pos)
377 {
378  std::string ret (str);
379  const char *pstr = ret.c_str(); char *pi;
380  int size = g_utf8_strlen (pstr, -1);
381  if (size > length) {
382  if (pos > 0) {
383  pi = g_utf8_offset_to_pointer (pstr, length-3);
384  ret.erase (pi-pstr);
385  ret.append ("...");
386  }
387  else if (pos < 0) {
388  pi = g_utf8_offset_to_pointer (pstr, size-(length-3));
389  ret.erase (0, pi-pstr);
390  ret.insert (0, "...");
391  }
392  else /* (pos == 0) */ {
393  pi = g_utf8_offset_to_pointer (pstr, size/2);
394  int delta = size - (length-3);
395  gchar *pn = pi, *pp = pi;
396  for (int i = 0;;) {
397  if (i++ == delta) break;
398  pn = g_utf8_next_char (pn);
399  if (i++ == delta) break;
400  pp = g_utf8_prev_char (pp);
401  }
402  g_assert (pp != NULL && pn != NULL);
403 
404  ret.erase (pp-pstr, pn-pp);
405  ret.insert (pp-pstr, "...");
406  }
407  }
408  return ret;
409 }
410 
411 static gboolean scroll_down_cb (void *pData)
412 {
413  GtkAdjustment *vadj = (GtkAdjustment *) pData;
414  gtk_adjustment_set_value (vadj, gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj));
415  return FALSE;
416 }
417 
418 void YGUtils::scrollWidget (GtkAdjustment *vadj, bool top)
419 {
420  if (top)
421  gtk_adjustment_set_value (vadj, gtk_adjustment_get_lower(vadj));
422  else
423  // since we usually want to call this together with a text change, we
424  // must wait till that gets in effect
425  g_idle_add_full (G_PRIORITY_LOW, scroll_down_cb, vadj, NULL);
426 }
427 
428 void ygutils_scrollAdj (GtkAdjustment *vadj, gboolean top)
429 { YGUtils::scrollWidget (vadj, top); }
430 
431 std::string YGUtils::escapeMarkup (const std::string &ori)
432 {
433  std::string::size_type length = ori.length(), i;
434  std::string ret;
435  ret.reserve (length * 1.5);
436  for (i = 0; i < length; i++)
437  switch (ori[i]) {
438  case '<':
439  ret += "&lt;";
440  break;
441  case '>':
442  ret += "&gt;";
443  break;
444  case '&':
445  ret += "&amp;";
446  break;
447  default:
448  ret += ori[i];
449  break;
450  }
451  return ret;
452 }
453 
454 bool YGUtils::endsWith (const std::string &str, const std::string &key)
455 {
456  if (str.size() < key.size())
457  return false;
458  return str.compare (str.size()-key.size(), key.size(), key) == 0;
459 }
460 
461 int YGUtils::getCharsWidth (GtkWidget *widget, int chars_nb)
462 {
463  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
464  PangoContext *context = gtk_widget_get_pango_context (widget);
465  PangoFontDescription *font_desc;
466  gtk_style_context_get (style_ctx, GTK_STATE_FLAG_NORMAL, "font", &font_desc, NULL);
467  PangoFontMetrics *metrics = pango_context_get_metrics (context, font_desc, NULL);
468 
469  int width = pango_font_metrics_get_approximate_char_width (metrics);
470  pango_font_metrics_unref (metrics);
471 
472  return PANGO_PIXELS (width) * chars_nb;
473 }
474 
475 int YGUtils::getCharsHeight (GtkWidget *widget, int chars_nb)
476 {
477  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
478  PangoContext *context = gtk_widget_get_pango_context (widget);
479  PangoFontDescription *font_desc;
480  gtk_style_context_get (style_ctx, GTK_STATE_FLAG_NORMAL, "font", &font_desc, NULL);
481  PangoFontMetrics *metrics = pango_context_get_metrics (context, font_desc, NULL);
482 
483  int height = pango_font_metrics_get_ascent (metrics) +
484  pango_font_metrics_get_descent (metrics);
485  pango_font_metrics_unref (metrics);
486 
487  return PANGO_PIXELS (height) * chars_nb;
488 }
489 
490 void YGUtils::setWidgetFont (GtkWidget *widget, PangoStyle style, PangoWeight weight,
491  double scale)
492 {
493  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
494  PangoFontDescription *font_desc;
495  gtk_style_context_get (style_ctx, GTK_STATE_FLAG_NORMAL, "font", &font_desc, NULL);
496 
497  int size = pango_font_description_get_size (font_desc);
498  PangoFontDescription* font = pango_font_description_new();
499  pango_font_description_set_weight (font, weight);
500  pango_font_description_set_size (font, (int)(size * scale));
501  pango_font_description_set_style (font, style);
502  gtk_widget_override_font (widget, font);
503 }
504 
505 void ygutils_setWidgetFont (GtkWidget *widget, PangoStyle style, PangoWeight weight, double scale)
506 { YGUtils::setWidgetFont (widget, style, weight, scale); }
507 
508 static void paned_allocate_cb (GtkWidget *paned, GtkAllocation *alloc, gpointer _rel)
509 {
510  if (!g_object_get_data (G_OBJECT (paned), "init")) { // only once
511  gdouble rel = GPOINTER_TO_INT (_rel) / 100.;
512  gint parent_size;
513  GtkAllocation alloc;
514  gtk_widget_get_allocation(paned, &alloc);
515 
516  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (paned)) == GTK_ORIENTATION_HORIZONTAL)
517  parent_size = alloc.width;
518  else
519  parent_size = alloc.height;
520  int pos = parent_size * rel;
521  gtk_paned_set_position (GTK_PANED (paned), pos);
522  g_object_set_data (G_OBJECT (paned), "init", GINT_TO_POINTER (1));
523  }
524 }
525 
526 void YGUtils::setPaneRelPosition (GtkWidget *paned, gdouble rel)
527 {
528  gint _rel = rel * 100;
529  g_signal_connect_after (G_OBJECT (paned), "size-allocate",
530  G_CALLBACK (paned_allocate_cb), GINT_TO_POINTER (_rel));
531 }
532 
533 void ygutils_setPaneRelPosition (GtkWidget *paned, gdouble rel)
534 { YGUtils::setPaneRelPosition (paned, rel); }
535 
536 GdkPixbuf *YGUtils::loadPixbuf (const std::string &filename)
537 {
538  GdkPixbuf *pixbuf = NULL;
539  if (!filename.empty()) {
540  GError *error = 0;
541  pixbuf = gdk_pixbuf_new_from_file (filename.c_str(), &error);
542  if (!pixbuf)
543  yuiWarning() << "Could not load icon: " << filename << "\n"
544  "Reason: " << error->message << "\n";
545  }
546  return pixbuf;
547 }
548 
549 // Code from Banshee: shades a pixbuf a bit, used e.g. for hover effects
550 static inline guchar pixel_clamp (int val)
551 { return MAX (0, MIN (255, val)); }
552 GdkPixbuf *YGUtils::setOpacity (const GdkPixbuf *src, int opacity, bool touchAlpha)
553 {
554  if (!src) return NULL;
555  int shift = 255 - ((opacity * 255) / 100);
556  int rgb_shift = 0, alpha_shift = 0;
557  if (touchAlpha)
558  alpha_shift = shift;
559  else
560  rgb_shift = shift;
561 
562  int width = gdk_pixbuf_get_width (src), height = gdk_pixbuf_get_height (src);
563  gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
564 
565  GdkPixbuf *dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
566  has_alpha, gdk_pixbuf_get_bits_per_sample (src), width, height);
567 
568  guchar *src_pixels_orig = gdk_pixbuf_get_pixels (src);
569  guchar *dest_pixels_orig = gdk_pixbuf_get_pixels (dest);
570 
571  int src_rowstride = gdk_pixbuf_get_rowstride (src);
572  int dest_rowstride = gdk_pixbuf_get_rowstride (dest);
573  int i, j;
574  for (i = 0; i < height; i++) {
575  guchar *src_pixels = src_pixels_orig + (i * src_rowstride);
576  guchar *dest_pixels = dest_pixels_orig + (i * dest_rowstride);
577  for (j = 0; j < width; j++) {
578  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
579  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
580  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
581  if (has_alpha)
582  *(dest_pixels++) = pixel_clamp (*(src_pixels++) - alpha_shift);
583  }
584  }
585  return dest;
586 }
587 
588 GdkPixbuf *YGUtils::setGray (const GdkPixbuf *src)
589 {
590  int width = gdk_pixbuf_get_width (src), height = gdk_pixbuf_get_height (src);
591  gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
592 
593  GdkPixbuf *dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
594  has_alpha, gdk_pixbuf_get_bits_per_sample (src), width, height);
595 
596  guchar *src_pixels_orig = gdk_pixbuf_get_pixels (src);
597  guchar *dest_pixels_orig = gdk_pixbuf_get_pixels (dest);
598 
599  int src_rowstride = gdk_pixbuf_get_rowstride (src);
600  int dest_rowstride = gdk_pixbuf_get_rowstride (dest);
601  int i, j;
602  for (i = 0; i < height; i++) {
603  guchar *src_pixels = src_pixels_orig + (i * src_rowstride);
604  guchar *dest_pixels = dest_pixels_orig + (i * dest_rowstride);
605  for (j = 0; j < width; j++) {
606  int clr = (src_pixels[0] + src_pixels[1] + src_pixels[2]) / 3;
607  *(dest_pixels++) = clr;
608  *(dest_pixels++) = clr;
609  *(dest_pixels++) = clr;
610  src_pixels += 3;
611  if (has_alpha)
612  *(dest_pixels++) = *(src_pixels++);
613  }
614  }
615  return dest;
616 }
617 
618 GdkPixbuf *ygutils_setOpacity (const GdkPixbuf *src, int opacity, gboolean useAlpha)
619 { return YGUtils::setOpacity (src, opacity, useAlpha); }
620 
621 
622 struct StockMap {
623  const char *english, *locale, *stock;
624 };
625 static const StockMap stock_map[] = {
626  { "Apply", _("Apply"), "application-exit" },
627  { "Accept", _("Accept"), "application-exit" },
628  { "Install", _("Install"), "application-exit" },
629  { "OK", _("OK"), "document-save" },
630  { "Cancel", _("Cancel"), "application-exit" },
631  { "Abort", _("Abort"), "application-exit" },
632  { "Close", _("Close"), "window-close" },
633  { "Yes", _("Yes"), "document-save" },
634  { "No", _("No"), "application-exit" },
635  { "Add", _("Add"), "list-add" },
636  { "Edit", _("Edit"), "edit-paste" },
637  { "Delete", _("Delete"), "list-remove" },
638  { "Up", _("Up"), "go-up" },
639  { "Down", _("Down"), "go-down" },
640  { "Enable", _("Enable"), "document-save" },
641  { "Disable", _("Disable"), "application-exit" },
642  { "Exit", _("Exit"), "application-exit" },
643  { "Back", _("Back"), "go-previous" },
644  { "Next", _("Next"), "go-next" },
645 };
646 #define stock_map_length (sizeof (stock_map) / sizeof (StockMap))
647 
648 static std::string cutUnderline (const std::string &str)
649 {
650  std::string ret (str);
651  std::string::size_type i = 0;
652  if ((i = ret.find ('_', i)) != std::string::npos)
653  ret.erase (i, 1);
654  return ret;
655 }
656 
657 static void stripStart (std::string &str, char ch)
658 {
659  while (!str.empty() && str[0] == ch)
660  str.erase (0, 1);
661 }
662 
663 static void stripEnd (std::string &str, char ch)
664 {
665  while (!str.empty() && str[str.size()-1] == ch)
666  str.erase (str.size()-1, 1);
667 }
668 
669 const char *YGUtils::mapIconname(const std::string &label )
670 {
671  std::map <std::string, std::string> stockMap;
672 
673  std::string id = cutUnderline (std::string(label));
674  stripStart (id, ' ');
675  stripEnd (id, ' ');
676  stripEnd (id, '.');
677 
678  std::map <std::string, std::string>::const_iterator it;
679  it = stockMap.find (id);
680  if (it != stockMap.end())
681  return it->second.c_str();
682 
683  return NULL;
684 }
685 
686 const char *YGUtils::setStockIcon (GtkWidget *button, const std::string &label,
687  const char *fallbackIcon)
688 {
689  const char *icon = mapIconname (label);
690 
691  if (!icon && label.size() < 22)
692  icon = fallbackIcon;
693  if (icon) {
694  if (gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default(), icon, GTK_ICON_SIZE_BUTTON, GTK_ICON_LOOKUP_USE_BUILTIN )) {
695  // we want to use GtkImage stock mode so it honors sensitive
696  GtkWidget *image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
697  gtk_button_set_always_show_image(GTK_BUTTON (button),true);
698  gtk_button_set_image (GTK_BUTTON (button), image);
699  }
700  }
701  else {
702  GtkWidget *image = gtk_button_get_image (GTK_BUTTON (button));
703  if (image)
704  gtk_widget_hide (image);
705  }
706  return icon;
707 }
708 
709 void YGUtils::shrinkWidget (GtkWidget *widget)
710 {
711  static bool first_time = true;
712  GtkCssProvider *provider;
713 
714  if (first_time) {
715  provider = gtk_css_provider_new ();
716  gtk_css_provider_load_from_data (provider,
717  "style \"small-widget-style\"\n"
718  "{\n"
719  " GtkWidget::focus-padding = 0\n"
720  " GtkWidget::focus-line-width = 0\n"
721  " xthickness = 0\n"
722  " ythickness = 0\n"
723  "}\n"
724  "widget \"*.small-widget\" style \"small-widget-style\"", -1, NULL);
725  gtk_style_context_add_provider (gtk_widget_get_style_context (widget),
726  GTK_STYLE_PROVIDER (provider),
727  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
728  g_object_unref (provider);
729  gtk_widget_set_name (widget, "small-widget");
730  first_time = false;
731  }
732 }
733 
734 /*
735  * construct a help string by dropping the title, and mentioning
736  * the first sentence for a dialog sub-title
737  */
738 gchar *
739 ygutils_headerize_help (const char *help_text, gboolean *cut)
740 {
741  char *text = ygutils_convert_to_xhtml (help_text);
742 
743  GString *str = g_string_new ("");
744  int i;
745  gboolean copy_word = FALSE;
746  for (i = 0; text[i]; i++) {
747  if (text[i] == '<') {
748  int a = i;
749  for (; text[i]; i++)
750  if (text[i] == '>')
751  break;
752 
753  if (!strncasecmp (text+a, "<h", 2) || !strncasecmp (text+a, "<big>", 5) ||
754  (!str->len && !strncasecmp (text+a, "<b>", 3))) {
755  for (i++; text[i]; i++) {
756  if (text[i] == '<')
757  a = i;
758  if (text[i] == '>') {
759  if (!strncasecmp (text+a, "</h", 3) || !strncasecmp (text+a, "</big>", 6) ||
760  !strncasecmp (text+a, "</b>", 4))
761  break;
762  }
763  }
764  }
765  }
766  else if (g_ascii_isspace (text[i])) {
767  if (copy_word)
768  g_string_append_c (str, ' ');
769  copy_word = FALSE;
770  }
771  else {
772  copy_word = TRUE;
773  g_string_append_c (str, text[i]);
774  if (text[i] == '.') {
775  if (g_ascii_isspace (text[i+1]) || text[i+1] == '<') {
776  i++;
777  break;
778  }
779  }
780  }
781  }
782  *cut = FALSE;
783  gboolean markup = FALSE;
784  for (; text[i]; i++) {
785  if (markup) {
786  if (text[i] == '>')
787  markup = FALSE;
788  }
789  else {
790  if (text[i] == '<')
791  markup = TRUE;
792  else if (!g_ascii_isspace (text[i])) {
793  *cut = TRUE;
794  break;
795  }
796  }
797  }
798  g_free (text);
799  return g_string_free (str, FALSE);
800 }
801 
802 
803 const char *ygutils_mapIconname (const std::string &label)
804 { return YGUtils::mapIconname (label); }
805 
806 
807 const char *ygutils_setStockIcon (GtkWidget *button, const char *label, const char *fallback)
808 { return YGUtils::setStockIcon (button, label, fallback); }
809 
810 /* interactive busy cursor */
811 // half cursor, half clock cursor is not a Xlib theme icon, but there is
812 // a hack to load it like: (if we ever want to use it...)
813 #if 0
814 __LEFT_PTR_WATCH = None
815 def set_busy_cursor (window):
816  global __LEFT_PTR_WATCH
817  if __LEFT_PTR_WATCH is None:
818  os.environ['XCURSOR_DISCOVER'] = '1' #Turn on logging in Xlib
819  # Busy cursor code from Padraig Brady <P@draigBrady.com>
820  # cursor_data hash is 08e8e1c95fe2fc01f976f1e063a24ccd
821  cursor_data = "\
822 \x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\
823 \x0c\x00\x00\x00\x1c\x00\x00\x00\x3c\x00\x00\x00\
824 \x7c\x00\x00\x00\xfc\x00\x00\x00\xfc\x01\x00\x00\
825 \xfc\x3b\x00\x00\x7c\x38\x00\x00\x6c\x54\x00\x00\
826 \xc4\xdc\x00\x00\xc0\x44\x00\x00\x80\x39\x00\x00\
827 \x80\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
828 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
829 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
830 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
831 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
832 \x00\x00\x00\x00\x00\x00\x00\x00"
833 
834  try:
835  pix = gtk.gdk.bitmap_create_from_data(None, cursor_data, 32, 32)
836  color = gtk.gdk.Color()
837  __LEFT_PTR_WATCH = gtk.gdk.Cursor(pix, pix, color, color, 2, 2)
838  except TypeError:
839  # old bug http://bugzilla.gnome.org/show_bug.cgi?id=103616
840  # default "WATCH" cursor
841  __LEFT_PTR_WATCH = gtk.gdk.Cursor(gtk.gdk.WATCH)
842  window.set_cursor (__LEFT_PTR_WATCH)
843 #endif
844 
845 
846 gboolean YGUtils::empty_row_is_separator_cb (
847  GtkTreeModel *model, GtkTreeIter *iter, gpointer _text_col)
848 {
849  int text_col = GPOINTER_TO_INT (_text_col);
850  gchar *str;
851  gtk_tree_model_get (model, iter, text_col, &str, -1);
852  bool ret = !str || !(*str);
853  g_free (str);
854  return ret;
855 }
856