libyui-gtk  2.42.2
 All Classes
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  PangoFontMetrics *metrics = pango_context_get_metrics (context,
466  gtk_style_context_get_font(style_ctx, GTK_STATE_FLAG_NORMAL), NULL);
467 
468  int width = pango_font_metrics_get_approximate_char_width (metrics);
469  pango_font_metrics_unref (metrics);
470 
471  return PANGO_PIXELS (width) * chars_nb;
472 }
473 
474 int YGUtils::getCharsHeight (GtkWidget *widget, int chars_nb)
475 {
476  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
477  PangoContext *context = gtk_widget_get_pango_context (widget);
478  PangoFontMetrics *metrics = pango_context_get_metrics (context,
479  gtk_style_context_get_font(style_ctx, GTK_STATE_FLAG_NORMAL), NULL);
480 
481  int height = pango_font_metrics_get_ascent (metrics) +
482  pango_font_metrics_get_descent (metrics);
483  pango_font_metrics_unref (metrics);
484 
485  return PANGO_PIXELS (height) * chars_nb;
486 }
487 
488 void YGUtils::setWidgetFont (GtkWidget *widget, PangoStyle style, PangoWeight weight,
489  double scale)
490 {
491  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
492  const PangoFontDescription *font_desc = gtk_style_context_get_font(style_ctx, GTK_STATE_FLAG_NORMAL);
493 
494  int size = pango_font_description_get_size (font_desc);
495  PangoFontDescription* font = pango_font_description_new();
496  pango_font_description_set_weight (font, weight);
497  pango_font_description_set_size (font, (int)(size * scale));
498  pango_font_description_set_style (font, style);
499  gtk_widget_override_font (widget, font);
500  pango_font_description_free (font);
501 }
502 
503 void ygutils_setWidgetFont (GtkWidget *widget, PangoStyle style, PangoWeight weight, double scale)
504 { YGUtils::setWidgetFont (widget, style, weight, scale); }
505 
506 static void paned_allocate_cb (GtkWidget *paned, GtkAllocation *alloc, gpointer _rel)
507 {
508  if (!g_object_get_data (G_OBJECT (paned), "init")) { // only once
509  gdouble rel = GPOINTER_TO_INT (_rel) / 100.;
510  gint parent_size;
511  GtkAllocation alloc;
512  gtk_widget_get_allocation(paned, &alloc);
513 
514  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (paned)) == GTK_ORIENTATION_HORIZONTAL)
515  parent_size = alloc.width;
516  else
517  parent_size = alloc.height;
518  int pos = parent_size * rel;
519  gtk_paned_set_position (GTK_PANED (paned), pos);
520  g_object_set_data (G_OBJECT (paned), "init", GINT_TO_POINTER (1));
521  }
522 }
523 
524 void YGUtils::setPaneRelPosition (GtkWidget *paned, gdouble rel)
525 {
526  gint _rel = rel * 100;
527  g_signal_connect_after (G_OBJECT (paned), "size-allocate",
528  G_CALLBACK (paned_allocate_cb), GINT_TO_POINTER (_rel));
529 }
530 
531 void ygutils_setPaneRelPosition (GtkWidget *paned, gdouble rel)
532 { YGUtils::setPaneRelPosition (paned, rel); }
533 
534 GdkPixbuf *YGUtils::loadPixbuf (const std::string &filename)
535 {
536  GdkPixbuf *pixbuf = NULL;
537  if (!filename.empty()) {
538  GError *error = 0;
539  pixbuf = gdk_pixbuf_new_from_file (filename.c_str(), &error);
540  if (!pixbuf)
541  yuiWarning() << "Could not load icon: " << filename << "\n"
542  "Reason: " << error->message << "\n";
543  }
544  return pixbuf;
545 }
546 
547 // Code from Banshee: shades a pixbuf a bit, used e.g. for hover effects
548 static inline guchar pixel_clamp (int val)
549 { return MAX (0, MIN (255, val)); }
550 GdkPixbuf *YGUtils::setOpacity (const GdkPixbuf *src, int opacity, bool touchAlpha)
551 {
552  if (!src) return NULL;
553  int shift = 255 - ((opacity * 255) / 100);
554  int rgb_shift = 0, alpha_shift = 0;
555  if (touchAlpha)
556  alpha_shift = shift;
557  else
558  rgb_shift = shift;
559 
560  int width = gdk_pixbuf_get_width (src), height = gdk_pixbuf_get_height (src);
561  gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
562 
563  GdkPixbuf *dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
564  has_alpha, gdk_pixbuf_get_bits_per_sample (src), width, height);
565 
566  guchar *src_pixels_orig = gdk_pixbuf_get_pixels (src);
567  guchar *dest_pixels_orig = gdk_pixbuf_get_pixels (dest);
568 
569  int src_rowstride = gdk_pixbuf_get_rowstride (src);
570  int dest_rowstride = gdk_pixbuf_get_rowstride (dest);
571  int i, j;
572  for (i = 0; i < height; i++) {
573  guchar *src_pixels = src_pixels_orig + (i * src_rowstride);
574  guchar *dest_pixels = dest_pixels_orig + (i * dest_rowstride);
575  for (j = 0; j < width; j++) {
576  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
577  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
578  *(dest_pixels++) = pixel_clamp (*(src_pixels++) + rgb_shift);
579  if (has_alpha)
580  *(dest_pixels++) = pixel_clamp (*(src_pixels++) - alpha_shift);
581  }
582  }
583  return dest;
584 }
585 
586 GdkPixbuf *YGUtils::setGray (const GdkPixbuf *src)
587 {
588  int width = gdk_pixbuf_get_width (src), height = gdk_pixbuf_get_height (src);
589  gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
590 
591  GdkPixbuf *dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
592  has_alpha, gdk_pixbuf_get_bits_per_sample (src), width, height);
593 
594  guchar *src_pixels_orig = gdk_pixbuf_get_pixels (src);
595  guchar *dest_pixels_orig = gdk_pixbuf_get_pixels (dest);
596 
597  int src_rowstride = gdk_pixbuf_get_rowstride (src);
598  int dest_rowstride = gdk_pixbuf_get_rowstride (dest);
599  int i, j;
600  for (i = 0; i < height; i++) {
601  guchar *src_pixels = src_pixels_orig + (i * src_rowstride);
602  guchar *dest_pixels = dest_pixels_orig + (i * dest_rowstride);
603  for (j = 0; j < width; j++) {
604  int clr = (src_pixels[0] + src_pixels[1] + src_pixels[2]) / 3;
605  *(dest_pixels++) = clr;
606  *(dest_pixels++) = clr;
607  *(dest_pixels++) = clr;
608  src_pixels += 3;
609  if (has_alpha)
610  *(dest_pixels++) = *(src_pixels++);
611  }
612  }
613  return dest;
614 }
615 
616 GdkPixbuf *ygutils_setOpacity (const GdkPixbuf *src, int opacity, gboolean useAlpha)
617 { return YGUtils::setOpacity (src, opacity, useAlpha); }
618 
619 static std::string cutUnderline (const std::string &str)
620 {
621  std::string ret (str);
622  std::string::size_type i = 0;
623  if ((i = ret.find ('_', i)) != std::string::npos)
624  ret.erase (i, 1);
625  return ret;
626 }
627 
628 static void stripStart (std::string &str, char ch)
629 {
630  while (!str.empty() && str[0] == ch)
631  str.erase (0, 1);
632 }
633 static void stripEnd (std::string &str, char ch)
634 {
635  while (!str.empty() && str[str.size()-1] == ch)
636  str.erase (str.size()-1, 1);
637 }
638 
639 struct StockMap {
640  const char *english, *locale, *stock;
641 };
642 static const StockMap stock_map[] = {
643  { "Apply", _("Apply"), GTK_STOCK_APPLY },
644  { "Accept", _("Accept"), GTK_STOCK_APPLY },
645  { "Install", _("Install"), GTK_STOCK_APPLY },
646  { "OK", _("OK"), GTK_STOCK_OK },
647  { "Cancel", _("Cancel"), GTK_STOCK_CANCEL },
648  { "Abort", _("Abort"), GTK_STOCK_CANCEL },
649  { "Close", _("Close"), GTK_STOCK_CLOSE },
650  { "Yes", _("Yes"), GTK_STOCK_YES },
651  { "No", _("No"), GTK_STOCK_NO },
652  { "Add", _("Add"), GTK_STOCK_ADD },
653  { "Edit", _("Edit"), GTK_STOCK_EDIT },
654  { "Delete", _("Delete"), GTK_STOCK_DELETE },
655  { "Up", _("Up"), GTK_STOCK_GO_UP },
656  { "Down", _("Down"), GTK_STOCK_GO_DOWN },
657  { "Enable", _("Enable"), GTK_STOCK_YES },
658  { "Disable", _("Disable"), GTK_STOCK_NO },
659  { "Exit", _("Exit"), GTK_STOCK_QUIT },
660 };
661 #define stock_map_length (sizeof (stock_map) / sizeof (StockMap))
662 
663 const char *YGUtils::mapStockIcon (const std::string &label)
664 {
665  static bool firstTime = true; static std::map <std::string, std::string> stockMap;
666  if (firstTime) {
667  firstTime = false;
668 
669  // match GTK stock labels to yast ones
670  GSList *list = gtk_stock_list_ids();
671  for (GSList *i = list; i; i = i->next) {
672  gchar *id = (gchar *) i->data;
673  GtkStockItem item;
674  if (gtk_stock_lookup (id, &item)) {
675  const gchar *_id = id;
676  if (!strcmp (id, GTK_STOCK_MEDIA_NEXT) || !strcmp (id, GTK_STOCK_MEDIA_FORWARD))
677  _id = GTK_STOCK_GO_FORWARD;
678  else if (!strcmp (id, GTK_STOCK_MEDIA_PREVIOUS) || !strcmp (id, GTK_STOCK_MEDIA_REWIND))
679  _id = GTK_STOCK_GO_BACK;
680  else if (!strcmp (id, GTK_STOCK_MEDIA_RECORD))
681  _id = GTK_STOCK_SAVE;
682  else if (!strcmp (id, GTK_STOCK_CLEAR))
683  _id = GTK_STOCK_DELETE;
684  else if (!strcmp (id, GTK_STOCK_QUIT))
685  _id = GTK_STOCK_APPLY;
686  else if (!strcmp (id, GTK_STOCK_JUMP_TO))
687  _id = GTK_STOCK_OK;
688  else if (!strncmp (id, "gtk-dialog-", 11))
689  _id = 0;
690 
691  if (_id)
692  stockMap[cutUnderline (item.label)] = _id;
693  }
694  // some may not have a stock item because they can't be set on a label
695  // e.g.: gtk-directory, gtk-missing-image, gtk-dnd
696  g_free (id);
697  }
698  g_slist_free (list);
699 
700  for (unsigned int j = 0; j < 2; j++) // add both current locale & english terms
701  for (unsigned int i = 0; i < stock_map_length; i++)
702  stockMap [stock_map[i].english+j] = stock_map[i].stock;
703  }
704 
705  std::string id = cutUnderline (label);
706  stripStart (id, ' ');
707  stripEnd (id, ' ');
708  stripEnd (id, '.');
709 
710  std::map <std::string, std::string>::const_iterator it;
711  it = stockMap.find (id);
712  if (it != stockMap.end())
713  return it->second.c_str();
714  return NULL;
715 }
716 
717 const char *YGUtils::setStockIcon (GtkWidget *button, const std::string &label,
718  const char *fallbackIcon)
719 {
720  const char *icon = mapStockIcon (label);
721  GtkStyleContext *ctx = gtk_widget_get_style_context(button);
722 
723  if (!icon && label.size() < 22)
724  icon = fallbackIcon;
725  if (icon) {
726  if (gtk_style_context_lookup_icon_set (ctx, icon)) {
727  // we want to use GtkImage stock mode so it honors sensitive
728  GtkWidget *image = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_BUTTON);
729  gtk_button_set_image (GTK_BUTTON (button), image);
730  }
731  }
732  else {
733  GtkWidget *image = gtk_button_get_image (GTK_BUTTON (button));
734  if (image)
735  gtk_widget_hide (image);
736  }
737  return icon;
738 }
739 
740 void YGUtils::shrinkWidget (GtkWidget *widget)
741 {
742  static bool first_time = true;
743  GtkCssProvider *provider;
744 
745  if (first_time) {
746  provider = gtk_css_provider_new ();
747  gtk_css_provider_load_from_data (provider,
748  "style \"small-widget-style\"\n"
749  "{\n"
750  " GtkWidget::focus-padding = 0\n"
751  " GtkWidget::focus-line-width = 0\n"
752  " xthickness = 0\n"
753  " ythickness = 0\n"
754  "}\n"
755  "widget \"*.small-widget\" style \"small-widget-style\"", -1, NULL);
756  gtk_style_context_add_provider (gtk_widget_get_style_context (widget),
757  GTK_STYLE_PROVIDER (provider),
758  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
759  g_object_unref (provider);
760  gtk_widget_set_name (widget, "small-widget");
761  first_time = false;
762  }
763 }
764 
765 /*
766  * construct a help string by dropping the title, and mentioning
767  * the first sentence for a dialog sub-title
768  */
769 gchar *
770 ygutils_headerize_help (const char *help_text, gboolean *cut)
771 {
772  char *text = ygutils_convert_to_xhtml (help_text);
773 
774  GString *str = g_string_new ("");
775  int i;
776  gboolean copy_word = FALSE;
777  for (i = 0; text[i]; i++) {
778  if (text[i] == '<') {
779  int a = i;
780  for (; text[i]; i++)
781  if (text[i] == '>')
782  break;
783 
784  if (!strncasecmp (text+a, "<h", 2) || !strncasecmp (text+a, "<big>", 5) ||
785  (!str->len && !strncasecmp (text+a, "<b>", 3))) {
786  for (i++; text[i]; i++) {
787  if (text[i] == '<')
788  a = i;
789  if (text[i] == '>') {
790  if (!strncasecmp (text+a, "</h", 3) || !strncasecmp (text+a, "</big>", 6) ||
791  !strncasecmp (text+a, "</b>", 4))
792  break;
793  }
794  }
795  }
796  }
797  else if (g_ascii_isspace (text[i])) {
798  if (copy_word)
799  g_string_append_c (str, ' ');
800  copy_word = FALSE;
801  }
802  else {
803  copy_word = TRUE;
804  g_string_append_c (str, text[i]);
805  if (text[i] == '.') {
806  if (g_ascii_isspace (text[i+1]) || text[i+1] == '<') {
807  i++;
808  break;
809  }
810  }
811  }
812  }
813  *cut = FALSE;
814  gboolean markup = FALSE;
815  for (; text[i]; i++) {
816  if (markup) {
817  if (text[i] == '>')
818  markup = FALSE;
819  }
820  else {
821  if (text[i] == '<')
822  markup = TRUE;
823  else if (!g_ascii_isspace (text[i])) {
824  *cut = TRUE;
825  break;
826  }
827  }
828  }
829  g_free (text);
830  return g_string_free (str, FALSE);
831 }
832 
833 const char *ygutils_mapStockIcon (const char *label)
834 { return YGUtils::mapStockIcon (label); }
835 
836 const char *ygutils_setStockIcon (GtkWidget *button, const char *label, const char *fallback)
837 { return YGUtils::setStockIcon (button, label, fallback); }
838 
839 /* interactive busy cursor */
840 // half cursor, half clock cursor is not a Xlib theme icon, but there is
841 // a hack to load it like: (if we ever want to use it...)
842 #if 0
843 __LEFT_PTR_WATCH = None
844 def set_busy_cursor (window):
845  global __LEFT_PTR_WATCH
846  if __LEFT_PTR_WATCH is None:
847  os.environ['XCURSOR_DISCOVER'] = '1' #Turn on logging in Xlib
848  # Busy cursor code from Padraig Brady <P@draigBrady.com>
849  # cursor_data hash is 08e8e1c95fe2fc01f976f1e063a24ccd
850  cursor_data = "\
851 \x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\
852 \x0c\x00\x00\x00\x1c\x00\x00\x00\x3c\x00\x00\x00\
853 \x7c\x00\x00\x00\xfc\x00\x00\x00\xfc\x01\x00\x00\
854 \xfc\x3b\x00\x00\x7c\x38\x00\x00\x6c\x54\x00\x00\
855 \xc4\xdc\x00\x00\xc0\x44\x00\x00\x80\x39\x00\x00\
856 \x80\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
857 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
858 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
859 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
860 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
861 \x00\x00\x00\x00\x00\x00\x00\x00"
862 
863  try:
864  pix = gtk.gdk.bitmap_create_from_data(None, cursor_data, 32, 32)
865  color = gtk.gdk.Color()
866  __LEFT_PTR_WATCH = gtk.gdk.Cursor(pix, pix, color, color, 2, 2)
867  except TypeError:
868  # old bug http://bugzilla.gnome.org/show_bug.cgi?id=103616
869  # default "WATCH" cursor
870  __LEFT_PTR_WATCH = gtk.gdk.Cursor(gtk.gdk.WATCH)
871  window.set_cursor (__LEFT_PTR_WATCH)
872 #endif
873 
874 
875 gboolean YGUtils::empty_row_is_separator_cb (
876  GtkTreeModel *model, GtkTreeIter *iter, gpointer _text_col)
877 {
878  int text_col = GPOINTER_TO_INT (_text_col);
879  gchar *str;
880  gtk_tree_model_get (model, iter, text_col, &str, -1);
881  bool ret = !str || !(*str);
882  g_free (str);
883  return ret;
884 }
885