libyui-gtk-pkg  2.42.9
 All Classes
ygtkpkgdetailview.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 /* YGtkPkgDetailView, selectable info-box */
5 // check the header file for information about this widget
6 
7 /*
8  Textdomain "gtk"
9  */
10 
11 #include "YGi18n.h"
12 #include "config.h"
13 #include "YGDialog.h"
14 #include "YGUtils.h"
15 #include "ygtkpkgdetailview.h"
16 #include <gtk/gtk.h>
17 #include <gdk/gdkkeysyms.h>
18 #include "YGPackageSelector.h"
19 #include "ygtkrichtext.h"
20 #include "ygtkpkgsearchentry.h"
21 #include "ygtkpkglistview.h"
22 #include <YStringTree.h>
23 
24 #define BROWSER_BIN "/usr/bin/firefox"
25 #define GNOME_OPEN_BIN "/usr/bin/gnome-open"
26 
27 static std::string onlyInstalledMsg() {
28  std::string s ("<i>");
29  s += _("Information only available for installed packages.");
30  s += "</i>";
31  return s;
32 }
33 
34 static const char *keywordOpenTag = "<keyword>", *keywordCloseTag = "</keyword>";
35 
36 struct BusyOp {
37  BusyOp() {
38  YGDialog::currentDialog()->busyCursor();
39  while (g_main_context_iteration (NULL, FALSE)) ;
40  }
41  ~BusyOp() {
42  YGDialog::currentDialog()->normalCursor();
43  }
44 };
45 
46 struct DetailWidget {
47  virtual ~DetailWidget() {}
48  virtual GtkWidget *getWidget() = 0;
49  virtual void refreshList (Ypp::List list) = 0;
50  virtual void setList (Ypp::List list) = 0;
51 };
52 
53 struct DetailName : public DetailWidget {
54  GtkWidget *hbox, *icon, *text;
55 
56  DetailName()
57  {
58  text = ygtk_rich_text_new();
59  icon = gtk_image_new();
60 
61  hbox = gtk_hbox_new (FALSE, 2);
62  gtk_box_pack_start (GTK_BOX (hbox), text, TRUE, TRUE, 0);
63  gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, TRUE, 0);
64  }
65 
66  virtual GtkWidget *getWidget()
67  { return hbox; }
68 
69  virtual void refreshList (Ypp::List list)
70  {
71  std::string str;
72  str.reserve (1024);
73  if (list.size() > 0) {
74  Ypp::ListProps props (list);
75  str = "<p bgcolor=\"";
76  str += props.toModify() ? "#f4f4b7" : "#ededed";
77  str += "\"><font color=\"#000000\">";
78  if (list.size() == 1) {
79  Ypp::Selectable &sel = list.get (0);
80  if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
81  str += sel.summary() + " - " + "<b>" + sel.name() + "</b>";
82  else
83  str += "<b>" + sel.name() + "</b> - " + sel.summary();
84  }
85  else {
86  str += "<b>";
87  str += _("Several selected");
88  str += "</b>";
89  }
90  str += "</font></p>";
91  }
92  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), str.c_str());
93  }
94 
95  virtual void setList (Ypp::List list)
96  {
97  gtk_widget_hide (icon);
98  if (list.size() == 1) {
99  Ypp::Selectable &sel = list.get (0);
100 
101  gtk_image_clear (GTK_IMAGE (icon));
102  GtkIconTheme *icons = gtk_icon_theme_get_default();
103  GdkPixbuf *pixbuf = gtk_icon_theme_load_icon (icons,
104  sel.name().c_str(), 32, GtkIconLookupFlags (0), NULL);
105  if (pixbuf) {
106  gtk_image_set_from_pixbuf (GTK_IMAGE (icon), pixbuf);
107  g_object_unref (G_OBJECT (pixbuf));
108  gtk_widget_show (icon);
109  }
110  }
111 
112  refreshList (list);
113  }
114 };
115 
117  GtkWidget *text, *popup;
118  std::string link_str;
119 
120  DetailDescription() : popup (NULL)
121  {
122  text = ygtk_rich_text_new();
123  g_signal_connect (G_OBJECT (text), "link-clicked",
124  G_CALLBACK (link_clicked_cb), this);
125  }
126 
127  virtual ~DetailDescription()
128  { if (popup) gtk_widget_destroy (popup); }
129 
130  virtual GtkWidget *getWidget()
131  { return text; }
132 
133  virtual void refreshList (Ypp::List list)
134  { setList (list); }
135 
136  virtual void setList (Ypp::List list)
137  {
138  std::string str;
139  str.reserve (2048);
140  if (list.size() == 1) {
141  Ypp::Selectable &sel = list.get (0);
142  str = sel.description (true);
143 
144  YGtkPkgSearchEntry *search = YGPackageSelector::get()->getSearchEntry();
145  if (search->getAttribute() == Ypp::PoolQuery::DESCRIPTION) {
146  std::list <std::string> keywords;
147  keywords = YGPackageSelector::get()->getSearchEntry()->getText();
148  highlightMarkup (str, keywords, keywordOpenTag, keywordCloseTag,
149  strlen (keywordOpenTag), strlen (keywordCloseTag));
150  }
151 
152  if (sel.type() == Ypp::Selectable::PACKAGE) {
153  Ypp::Package pkg (sel);
154  std::string url (pkg.url());
155  if (!url.empty()) {
156  str += "<p><i>"; str += _("Web site:");
157  str += "</i> <a href=\""; str += url; str += "\">";
158  str += url; str += "</a></p>";
159  }
160  if (pkg.isCandidatePatch()) {
161  Ypp::Selectable _patch = pkg.getCandidatePatch();
162  Ypp::Patch patch (_patch);
163  str += "<p><i>"; str += _("Patch issued:");
164  str += "</i> "; str += _patch.summary();
165  str += " <b>("; str += Ypp::Patch::prioritySummary (patch.priority());
166  str += ")</b>"; str += "</p>";
167 
168  }
169  }
170  }
171  else {
172  if (list.size() > 0) {
173  str += "<ul>";
174  for (int i = 0; i < list.size(); i++)
175  str += "<li>" + list.get (i).name() + "</li>";
176  str += "</ul>";
177  }
178  }
179  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), str.c_str());
180  }
181 
182  static void link_clicked_cb (YGtkRichText *text, const gchar *link, DetailDescription *pThis)
183  {
184  if (!pThis->popup) {
185  GtkWidget *menu = pThis->popup = gtk_menu_new();
186  gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (text), NULL);
187 
188  GtkWidget *item;
189  if (g_file_test (BROWSER_BIN, G_FILE_TEST_IS_EXECUTABLE)) {
190  std::string label (YGUtils::mapKBAccel ("&Open"));
191  if (getuid() == 0) {
192  const char *username = getenv ("USERNAME");
193  if (!username || !(*username))
194  username = "root";
195  label += " ("; label += _("as");
196  label += " "; label += username; label += ")";
197  }
198  item = gtk_image_menu_item_new_with_mnemonic (label.c_str());
199  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
200  gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU));
201  gtk_widget_show (item);
202  g_signal_connect (G_OBJECT (item), "activate",
203  G_CALLBACK (open_link_cb), pThis);
204  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
205  }
206  item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
207  gtk_widget_show (item);
208  g_signal_connect (G_OBJECT (item), "activate",
209  G_CALLBACK (copy_link_cb), pThis);
210  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
211  }
212  gtk_menu_popup (GTK_MENU (pThis->popup), NULL, NULL, NULL, NULL,
213  0, gtk_get_current_event_time());
214  pThis->link_str = link;
215  }
216 
217  static void open_link_cb (GtkMenuItem *item, DetailDescription *pThis)
218  {
219  const std::string &link = pThis->link_str;
220  std::string command;
221  command.reserve (256);
222 
223  const char *username = 0;
224  if (getuid() == 0) {
225  username = getenv ("USERNAME");
226  if (username && !(*username))
227  username = 0;
228  }
229 
230  if (username) {
231  command += "gnomesu -u ";
232  command += username;
233  command += " -c \"" BROWSER_BIN " --new-window ";
234  command += link;
235  command += "\"";
236  }
237  else {
238  command += BROWSER_BIN " --new-window ";
239  command += link;
240  }
241  command += " &";
242  (void) system (command.c_str());
243  }
244 
245  static void copy_link_cb (GtkMenuItem *item, DetailDescription *pThis)
246  {
247  const std::string &link = pThis->link_str;
248  GtkClipboard *clipboard =
249  gtk_widget_get_clipboard (pThis->text, GDK_SELECTION_CLIPBOARD);
250  gtk_clipboard_set_text (clipboard, link.c_str(), -1);
251  }
252 };
253 
254 struct DetailExpander : public DetailWidget {
255  GtkWidget *expander;
256  Ypp::List list;
257  bool dirty;
258 
259  DetailExpander (const std::string &label, bool default_expanded)
260  : list (0), dirty (false)
261  {
262  std::string str = "<b>" + label + "</b>";
263  expander = gtk_expander_new (str.c_str());
264  gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE);
265  gtk_expander_set_expanded (GTK_EXPANDER (expander), default_expanded);
266  g_signal_connect_after (G_OBJECT (expander), "notify::expanded",
267  G_CALLBACK (expanded_cb), this);
268  }
269 
270  virtual GtkWidget *getWidget() { return expander; }
271 
272  void updateList()
273  {
274  if (dirty)
275  if (gtk_expander_get_expanded (GTK_EXPANDER (expander)))
276  if (visible()) {
277  showList (list);
278  dirty = false;
279  }
280  }
281 
282  bool visible()
283  {
284  if (onlySingleList())
285  return list.size() == 1;
286  return list.size() > 0;
287  }
288 
289  virtual void setList (Ypp::List list)
290  {
291  this->list = list;
292  dirty = true;
293  visible() ? gtk_widget_show (expander) : gtk_widget_hide (expander);
294  updateList();
295  }
296 
297  virtual void refreshList (Ypp::List list)
298  {
299  if (gtk_expander_get_expanded (GTK_EXPANDER (expander)))
300  showRefreshList (list);
301  else
302  dirty = true;
303  }
304 
305  static void expanded_cb (GObject *object, GParamSpec *param_spec, DetailExpander *pThis)
306  { pThis->updateList(); }
307 
308  void setChild (GtkWidget *child)
309  { gtk_container_add (GTK_CONTAINER (expander), child); }
310 
311  // use this method to only request data when/if necessary; so to speed up expander children
312  virtual void showList (Ypp::List list) = 0;
313  virtual void showRefreshList (Ypp::List list) = 0;
314  virtual bool onlySingleList() = 0; // show only when list.size()==1
315 };
316 
318  GtkWidget *text;
319 
321  : DetailExpander (_("Details"), true)
322  {
323  text = ygtk_rich_text_new();
324  gtk_widget_set_size_request (text, 160, -1);
325  setChild (text);
326  }
327 
328  virtual bool onlySingleList() { return true; }
329 
330  virtual void showList (Ypp::List list)
331  {
332  Ypp::Selectable sel = list.get (0);
333  ZyppSelectable zsel = sel.zyppSel();
334  ZyppResObject zobj = zsel->theObj();
335  ZyppPackage zpkg = castZyppPackage (zobj);
336 
337  std::string str;
338  std::string b ("<i>"), _b ("</i> "), i (""), _i(""), br ("<br/>");
339  str.reserve (2048);
340  str += b + _("Size:") + _b + i + zobj->installSize().asString() + _i;
341  str += br + b + _("License:") + _b + i + zpkg->license() + _i;
342  if (zsel->hasInstalledObj())
343  str += br + b + ("Installed at:") + _b + i +
344  zsel->installedObj()->installtime().form ("%x") + _i;
345  if (zsel->hasCandidateObj())
346  str += br + b + ("Latest build:") + _b + i +
347  zsel->candidateObj()->buildtime().form ("%x") + _i;
348  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), str.c_str());
349  }
350 
351  virtual void showRefreshList (Ypp::List list) {}
352 };
353 
354 typedef zypp::ResPoolProxy::const_iterator ZyppPoolIterator;
355 inline ZyppPoolIterator zyppSrcPkgBegin()
356 { return zyppPool().byKindBegin<zypp::SrcPackage>(); }
357 inline ZyppPoolIterator zyppSrcPkgEnd()
358 { return zyppPool().byKindEnd<zypp::SrcPackage>(); }
359 
361  GtkWidget *versions_box;
362  std::list <Ypp::Version> versions;
363 
365  : DetailExpander (_("Versions"), false)
366  {
367  versions_box = gtk_vbox_new (FALSE, 2);
368 
369  // draw border
370  GtkWidget *frame = gtk_frame_new (NULL);
371  gtk_container_set_border_width (GTK_CONTAINER (frame), 2);
372  gtk_container_add (GTK_CONTAINER (frame), versions_box);
373  setChild (frame);
374  }
375 
376  GtkWidget *addVersion (Ypp::Selectable &sel, Ypp::Version version, GtkWidget *group)
377  {
378  GtkWidget *button = gtk_toggle_button_new();
379  gtk_container_add (GTK_CONTAINER (button), gtk_image_new());
380 
381  GtkWidget *label = gtk_label_new ("");
382  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
383 
384  GtkWidget *hbox = gtk_hbox_new (FALSE, 6);
385  gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
386  gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
387  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
388  gtk_widget_show_all (hbox);
389 
390  g_object_set_data (G_OBJECT (button), "nb", GINT_TO_POINTER (versions.size()));
391  updateVersionWidget (hbox, sel, version);
392  g_signal_connect (G_OBJECT (button), "toggled",
393  G_CALLBACK (button_clicked_cb), this);
394 
395  GtkWidget *widget = hbox;
396  if ((versions.size() % 2) == 1)
397  g_signal_connect (G_OBJECT (widget), "draw",
398  G_CALLBACK (draw_gray_cb), NULL);
399  versions.push_back (version);
400  gtk_box_pack_start (GTK_BOX (versions_box), widget, FALSE, TRUE, 0);
401  return widget;
402  }
403 
404  void updateVersionLabel (GtkWidget *label, Ypp::Selectable &sel, Ypp::Version &version)
405  {
406  std::string repo; bool modified;
407  if (version.isInstalled()) {
408  repo = _("Installed");
409  modified = sel.toRemove();
410  }
411  else {
412  repo = version.repository().name();
413  modified = sel.toInstall();
414  }
415  modified = modified && version.toModify();
416 
417  std::string number (version.number()), arch (version.arch());
418  char *tooltip = g_strdup_printf ("%s <small>(%s)</small>\n<small>%s</small>",
419  number.c_str(), arch.c_str(), repo.c_str());
420  number = YGUtils::truncate (number, 20, 0);
421  char *str = g_strdup_printf ("%s%s <small>(%s)</small>\n<small>%s</small>%s",
422  modified ? "<i>" : "", number.c_str(), arch.c_str(), repo.c_str(),
423  modified ? "</i>" : "");
424 
425  gtk_label_set_markup (GTK_LABEL (label), str);
426  if (number.size() > 20)
427  gtk_widget_set_tooltip_markup (label, tooltip);
428  g_free (tooltip); g_free (str);
429  }
430 
431  void updateVersionButton (GtkWidget *button, Ypp::Selectable &sel, Ypp::Version &version)
432  {
433  const char *stock, *tooltip;
434  bool modified, can_modify = true;
435  if (version.isInstalled()) {
436  tooltip = _("Remove");
437  stock = GTK_STOCK_DELETE;
438  can_modify = sel.canRemove();
439  modified = sel.toRemove();
440  }
441  else {
442  if (sel.hasInstalledVersion()) {
443  Ypp::Version installed = sel.installed();
444  if (installed < version) {
445  tooltip = _("Upgrade");
446  stock = GTK_STOCK_GO_UP;
447  }
448  else if (installed > version) {
449  tooltip = _("Downgrade");
450  stock = GTK_STOCK_GO_DOWN;
451  }
452  else {
453  tooltip = _("Re-install");
454  stock = GTK_STOCK_REFRESH;
455  }
456  }
457  else {
458  tooltip = _("Install");
459  stock = GTK_STOCK_SAVE;
460  }
461  modified = sel.toInstall();
462  }
463  modified = modified && version.toModify();
464  if (modified)
465  tooltip = _("Undo");
466 
467  g_signal_handlers_block_by_func (button, (gpointer) button_clicked_cb, this);
468  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), modified);
469  g_signal_handlers_unblock_by_func (button, (gpointer) button_clicked_cb, this);
470  can_modify ? gtk_widget_show (button) : gtk_widget_hide (button);
471  gtk_widget_set_tooltip_text (button, tooltip);
472  gtk_image_set_from_stock (GTK_IMAGE (gtk_bin_get_child (GTK_BIN(button))),
473  stock, GTK_ICON_SIZE_BUTTON);
474  }
475 
476  void updateVersionWidget (GtkWidget *widget, Ypp::Selectable &sel, Ypp::Version &version)
477  {
478  GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
479  GtkWidget *label = (GtkWidget *) children->data;
480  GtkWidget *button = (GtkWidget *) children->next->data;
481  g_list_free (children);
482 
483  updateVersionLabel (label, sel, version);
484  updateVersionButton (button, sel, version);
485  }
486 
487  void updateMultiselectionButton (GtkWidget *button)
488  {
489  Ypp::ListProps props (list);
490 
491  const char *stock, *tooltip;
492  bool modified, hide = false;
493  if (props.isInstalled()) {
494  tooltip = _("Remove");
495  stock = GTK_STOCK_DELETE;
496  }
497  else if (props.isNotInstalled()) {
498  tooltip = _("Install");
499  stock = GTK_STOCK_SAVE;
500  }
501  else
502  hide = true;
503  modified = props.toModify();
504  if (modified)
505  tooltip = _("Undo");
506 
507  g_signal_handlers_block_by_func (button, (gpointer) button_clicked_cb, this);
508  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), modified);
509  g_signal_handlers_unblock_by_func (button, (gpointer) button_clicked_cb, this);
510  hide ? gtk_widget_hide (button) : gtk_widget_show (button);
511  gtk_widget_set_tooltip_text (button, tooltip);
512  gtk_image_set_from_stock (GTK_IMAGE (gtk_bin_get_child (GTK_BIN(button))),
513  stock, GTK_ICON_SIZE_BUTTON);
514  }
515 
516  void clearVersions()
517  {
518  GList *children = gtk_container_get_children (GTK_CONTAINER (versions_box));
519  for (GList *i = children; i; i = i->next)
520  gtk_container_remove (GTK_CONTAINER (versions_box), (GtkWidget *) i->data);
521  g_list_free (children);
522  versions.clear();
523  }
524 
525  Ypp::Version &getVersion (GtkToggleButton *button)
526  {
527  int nb = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "nb"));
528  std::list <Ypp::Version>::iterator it = versions.begin();
529  for (int i = 0; i < nb; i++) it++;
530  return *it;
531  }
532 
533  static void button_clicked_cb (GtkToggleButton *button, VersionExpander *pThis)
534  {
535  BusyOp op;
536  if (gtk_toggle_button_get_active (button)) { // was un-pressed
537  if (pThis->list.size() > 1) {
538  Ypp::ListProps props (pThis->list);
539  if (props.hasUpgrade())
540  pThis->list.install();
541  else if (props.isInstalled())
542  pThis->list.remove();
543  else if (props.isNotInstalled())
544  pThis->list.install();
545  else if (props.toModify())
546  pThis->list.undo();
547  }
548  else {
549  Ypp::Selectable sel = pThis->list.get (0);
550  Ypp::Version &version = pThis->getVersion (button);
551 
552  if (sel.toModify())
553  sel.undo();
554  if (version.isInstalled())
555  sel.remove();
556  else {
557  sel.setCandidate (version);
558  sel.install();
559  }
560  }
561  }
562  else // button was pressed
563  pThis->list.undo();
564  }
565 
566  virtual bool onlySingleList() { return false; }
567 
568  virtual void showList (Ypp::List list)
569  {
570  Ypp::ListProps props (list);
571  clearVersions();
572 
573  if (list.size() == 1) {
574  Ypp::Selectable sel = list.get (0);
575  GtkWidget *radio = 0;
576  for (int i = 0; i < sel.totalVersions(); i++)
577  radio = addVersion (sel, sel.version (i), radio);
578  }
579  else {
580  GtkWidget *button = gtk_toggle_button_new();
581  gtk_container_add (GTK_CONTAINER (button), gtk_image_new());
582 
583  GtkWidget *label = gtk_label_new (_("Several selected"));
584  GtkWidget *hbox = gtk_hbox_new (FALSE, 6);
585  gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
586  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
587  gtk_box_pack_start (GTK_BOX (versions_box), hbox, FALSE, TRUE, 0);
588  gtk_widget_show_all (hbox);
589 
590  updateMultiselectionButton (button);
591  g_signal_connect (G_OBJECT (button), "toggled",
592  G_CALLBACK (button_clicked_cb), this);
593  }
594 
595  gtk_widget_set_sensitive (versions_box, !props.isLocked());
596  }
597 
598  virtual void showRefreshList (Ypp::List list)
599  {
600  // update radios
601  GList *children = gtk_container_get_children (GTK_CONTAINER (versions_box));
602  if (list.size() == 1) {
603  Ypp::Selectable &sel = list.get (0);
604  std::list <Ypp::Version>::iterator it = versions.begin();
605  for (GList *i = children; i; i = i->next, it++)
606  updateVersionWidget ((GtkWidget *) i->data, sel, *it);
607  }
608  else {
609  GtkWidget *hbox = (GtkWidget *) children->data;
610  GList *l = gtk_container_get_children (GTK_CONTAINER (hbox));
611  GtkWidget *button = (GtkWidget *) l->next->data;
612  g_list_free (l);
613  updateMultiselectionButton (button);
614  }
615  g_list_free (children);
616  }
617 
618  static gboolean draw_gray_cb (GtkWidget *widget, cairo_t *cr)
619  {
620  int w = gtk_widget_get_allocated_width(widget);
621  int h = gtk_widget_get_allocated_height(widget);
622 
623  cairo_rectangle (cr, 0, 0, w, h);
624  // use alpha to cope with styles who might not have a white background
625  cairo_set_source_rgba (cr, 0, 0, 0, .060);
626  cairo_fill (cr);
627  return FALSE;
628  }
629 };
630 
631 struct SupportExpander : public DetailWidget {
632  // not derived from DetailExpander as this one behaves a little differently
633  GtkWidget *text, *expander;
634 
636  {
637  text = ygtk_rich_text_new();
638  expander = gtk_expander_new ("");
639  gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE);
640  gtk_container_add (GTK_CONTAINER (expander), text);
641  }
642 
643  virtual GtkWidget *getWidget() { return expander; }
644 
645  virtual void refreshList (Ypp::List list) {}
646 
647  virtual void setList (Ypp::List list)
648  {
649  if (list.size() == 1) {
650  Ypp::Selectable sel = list.get (0);
651  Ypp::Package pkg (sel);
652 
653  int support = pkg.support();
654 
655  std::string label ("<b>" + std::string (_("Supportability:")) + "</b> ");
656  label += Ypp::Package::supportSummary (support);
657  gtk_expander_set_label (GTK_EXPANDER (expander), label.c_str());
658  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text),
659  Ypp::Package::supportDescription (support).c_str());
660 
661  if (support == 0)
662  gtk_widget_hide(expander);
663  else
664  gtk_widget_show (expander);
665  }
666  else
667  gtk_widget_hide (expander);
668  }
669 };
670 
672  GtkWidget *grid;
673 
675  : DetailExpander (_("Dependencies"), false)
676  {
677  grid = gtk_grid_new();
678  gtk_widget_set_hexpand (grid, TRUE);
679  gtk_widget_set_vexpand (grid, FALSE);
680  setChild (grid);
681  }
682 
683  void clear()
684  {
685  GList *children = gtk_container_get_children (GTK_CONTAINER (grid));
686  for (GList *i = children; i; i = i->next)
687  gtk_container_remove (GTK_CONTAINER (grid), (GtkWidget *) i->data);
688  g_list_free (children);
689  }
690 
691  void addLine (const std::string &col1, const std::string &col2,
692  const std::string &col3, int dep)
693  {
694 // if (dep >= 0)
695 // gtk_box_pack_start (GTK_BOX (vbox), gtk_hseparator_new(), FALSE, TRUE, 0);
696 
697  GtkWidget *cols[3];
698  for(int i = 0; i < 3; i++) {
699  cols[i] = ygtk_rich_text_new();
700  gtk_widget_set_size_request(cols[i], 100, -1);
701  }
702 
703  ygtk_rich_text_set_text (YGTK_RICH_TEXT (cols[0]), ("<i>" + col1 + "</i>").c_str());
704  ygtk_rich_text_set_text (YGTK_RICH_TEXT (cols[1]), col2.c_str());
705  ygtk_rich_text_set_text (YGTK_RICH_TEXT (cols[2]), col3.c_str());
706 
707  for(int i = 1; i < 3; i++) {
708  if (dep == 0)
709  g_signal_connect (G_OBJECT (cols[i]), "link-clicked",
710  G_CALLBACK (requires_link_cb), NULL);
711  else if (dep == 2)
712  g_signal_connect (G_OBJECT (cols[i]), "link-clicked",
713  G_CALLBACK (provides_link_cb), NULL);
714  }
715 
716  GList *children = gtk_container_get_children(GTK_CONTAINER(grid));
717  guint top = g_list_length (children) / 3;
718  g_list_free (children);
719 
720  for(int i = 0; i < 3; i++)
721  gtk_grid_attach (GTK_GRID (grid), cols[i], i, top, 1, 1);
722  }
723 
724  static void requires_link_cb (GtkWidget *widget, const gchar *link)
725  { YGPackageSelector::get()->searchFor (Ypp::PoolQuery::REQUIRES, link); }
726 
727  static void provides_link_cb (GtkWidget *widget, const gchar *link)
728  { YGPackageSelector::get()->searchFor (Ypp::PoolQuery::PROVIDES, link); }
729 
730 
731  virtual bool onlySingleList() { return true; }
732 
733  virtual void showList (Ypp::List list)
734  {
735  Ypp::Selectable sel = list.get (0);
736 
737  clear();
738  std::string i ("<i>"), _i ("</i>"), b ("<b>"), _b ("</b>");
739  std::string installed_str (i + b + _("Installed Version:") + _b + _i);
740  std::string candidate_str (i + b + _("Available Version:") + _b + _i);
741  bool hasInstalled = sel.hasInstalledVersion(),
742  hasCandidate = sel.hasCandidateVersion();
743  if (hasInstalled)
744  installed_str += "<br>" + sel.installed().number();
745  if (hasCandidate)
746  candidate_str += "<br>" + sel.candidate().number();
747  addLine ("", installed_str, candidate_str, -1);
748  for (int dep = 0; dep < VersionDependencies::total(); dep++) {
749  std::string inst, cand;
750  if (hasInstalled)
751  inst = VersionDependencies (sel.installed()).getText (dep);
752  if (hasCandidate)
753  cand = VersionDependencies (sel.candidate()).getText (dep);
754  if (!inst.empty() || !cand.empty())
755  addLine (VersionDependencies::getLabel (dep), inst, cand, dep);
756  }
757  gtk_widget_show_all (grid);
758  }
759 
760  virtual void showRefreshList (Ypp::List list)
761  { showList (list); }
762 
765  : m_version (version) {}
766 
767  static int total()
768  { return ((int) zypp::Dep::SUPPLEMENTS_e) + 1; }
769 
770  static const char *getLabel (int dep)
771  {
772 
773  switch ((zypp::Dep::for_use_in_switch) dep) {
774  case zypp::Dep::PROVIDES_e: return "Provides:";
775  case zypp::Dep::PREREQUIRES_e: return "Pre-requires:";
776  case zypp::Dep::REQUIRES_e: return "Requires:";
777  case zypp::Dep::CONFLICTS_e: return "Conflicts:";
778  case zypp::Dep::OBSOLETES_e: return "Obsoletes:";
779  case zypp::Dep::RECOMMENDS_e: return "Recommends:";
780  case zypp::Dep::SUGGESTS_e: return "Suggests:";
781  case zypp::Dep::ENHANCES_e: return "Enhances:";
782  case zypp::Dep::SUPPLEMENTS_e: return "Supplements:";
783  }
784  return 0;
785  }
786 
787  // zypp::Dep(zypp::Dep::for_use_in_switch) construtor is private
788  static zypp::Dep getDep (int dep)
789  {
790  switch (dep) {
791  case 0: return zypp::Dep::PROVIDES;
792  case 1: return zypp::Dep::PREREQUIRES;
793  case 2: return zypp::Dep::REQUIRES;
794  case 3: return zypp::Dep::CONFLICTS;
795  case 4: return zypp::Dep::OBSOLETES;
796  case 5: return zypp::Dep::RECOMMENDS;
797  case 6: return zypp::Dep::SUGGESTS;
798  case 7: return zypp::Dep::ENHANCES;
799  case 8: return zypp::Dep::SUPPLEMENTS;
800  }
801  return zypp::Dep::PROVIDES;
802  }
803 
804 
805  std::string getText (int dep)
806  {
807  std::string keyword;
808  YGtkPkgSearchEntry *search = YGPackageSelector::get()->getSearchEntry();
809  if ((dep == 0 && search->getAttribute() == Ypp::PoolQuery::PROVIDES) ||
810  (dep == 2 && search->getAttribute() == Ypp::PoolQuery::REQUIRES))
811  keyword = search->getTextStr();
812 
813  ZyppResObject zobj = m_version.zyppObj();
814  zypp::Capabilities caps = zobj->dep (getDep (dep));
815  std::string ret;
816  ret.reserve (4092);
817  for (zypp::Capabilities::const_iterator it = caps.begin();
818  it != caps.end(); it++) {
819  if (!ret.empty())
820  ret += ", ";
821 
822  std::string str (it->asString());
823  bool highlight = (str == keyword);
824 
825 
826  if (dep == 0 || dep == 2) {
827  std::string::size_type i;
828  i = MIN (str.find (' '), str.find ('('));
829 
830  std::string name (str, 0, i);
831  ret += "<a href=\"" + name + "\">" + YGUtils::escapeMarkup (name) + "</a>";
832  if (i != std::string::npos) {
833  std::string rest (str, i);
834  ret += YGUtils::escapeMarkup (rest);
835  }
836  }
837  else
838  ret += YGUtils::escapeMarkup (str);
839 
840  if (highlight)
841  ret += keywordCloseTag;
842  }
843  return ret;
844  }
845 
846  Ypp::Version m_version;
847  };
848 };
849 
851  GtkWidget *text;
852 
854  : DetailExpander (_("File list"), false)
855  {
856  text = ygtk_rich_text_new();
857  g_signal_connect (G_OBJECT (text), "link-clicked",
858  G_CALLBACK (dirname_pressed_cb), this);
859  setChild (text);
860  }
861 
862  virtual bool onlySingleList() { return true; }
863 
864  virtual void showList (Ypp::List list)
865  {
866  Ypp::Selectable sel = list.get (0);
867  if (sel.isInstalled())
868  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), filelist (sel).c_str());
869  else
870  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), onlyInstalledMsg().c_str());
871  }
872 
873  virtual void showRefreshList (Ypp::List list) {}
874 
875  static void dirname_pressed_cb (GtkWidget *text, const gchar *link, FilelistExpander *pThis)
876  {
877  gchar *cmd = g_strdup_printf (GNOME_OPEN_BIN " %s &", link);
878  (void) system (cmd);
879  g_free (cmd);
880  }
881 
882  static std::string filelist (Ypp::Selectable &sel)
883  {
884  std::string text;
885  text.reserve (4096);
886 
887  ZyppResObject zobj = sel.installed().zyppObj();
888  ZyppPackage zpkg = castZyppPackage (zobj);
889  if (zpkg) {
890  YStringTree tree ("");
891 
892  zypp::Package::FileList files = zpkg->filelist();
893  for (zypp::Package::FileList::iterator it = files.begin();
894  it != files.end(); it++)
895  tree.addBranch (*it, '/');
896 
897  struct inner {
898  static std::string path (YStringTreeItem *item)
899  {
900  std::string str;
901  str.reserve (1024);
902  for (YStringTreeItem *i = item->parent(); i->parent(); i = i->parent()) {
903  std::string val (i->value().orig());
904  std::string prefix ("/");
905  str = prefix + val + str;
906  }
907  return str;
908  }
909 
910  static void traverse (YStringTreeItem *item, std::string &text)
911  {
912  if (!item) return;
913 
914  // traverse nodes first
915  bool has_leaves = false;
916  for (YStringTreeItem *i = item; i; i = i->next()) {
917  YStringTreeItem *child = i->firstChild();
918  if (child)
919  traverse (child, text);
920  else
921  has_leaves = true;
922  }
923 
924  // traverse leaves now
925  if (has_leaves) {
926  std::string dirname (path (item));
927  text += "<a href=\"" + dirname + "\">" + dirname + "</a>";
928  text += "<blockquote>";
929 
930  std::string keyword;
931  YGtkPkgSearchEntry *search = YGPackageSelector::get()->getSearchEntry();
932  if (search->getAttribute() == Ypp::PoolQuery::FILELIST)
933  keyword = search->getTextStr();
934  if (!keyword.empty() && keyword[0] == '/') {
935  std::string _dirname (keyword, 0, keyword.find_last_of ('/'));
936  if (dirname == _dirname)
937  keyword = std::string (keyword, _dirname.size()+1);
938  else
939  keyword.clear();
940  }
941 
942  for (YStringTreeItem *i = item; i; i = i->next())
943  if (!i->firstChild()) {
944  if (i != item) // not first
945  text += ", ";
946  std::string basename (i->value().orig());
947 
948  bool highlight = (basename == keyword);
949  if (highlight)
950  text += keywordOpenTag;
951  text += basename;
952  if (highlight)
953  text += keywordCloseTag;
954  }
955 
956  text += "</blockquote>";
957  }
958  }
959  };
960 
961  inner::traverse (tree.root(), text);
962  }
963  return text;
964  }
965 };
966 
968  GtkWidget *text;
969 
971  : DetailExpander (_("Changelog"), false)
972  {
973  text = ygtk_rich_text_new();
974  setChild (text);
975  }
976 
977  virtual bool onlySingleList() { return true; }
978 
979  virtual void showList (Ypp::List list)
980  {
981  Ypp::Selectable sel = list.get (0);
982  if (sel.isInstalled())
983  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), changelog (sel).c_str());
984  else
985  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), onlyInstalledMsg().c_str());
986  }
987 
988  virtual void showRefreshList (Ypp::List list) {}
989 
990  static std::string changelog (Ypp::Selectable &sel)
991  {
992  std::string text;
993  text.reserve (32768);
994  text += "<p><i>";
995  text += _("Changelog applies only to the installed version.");
996  text += "</i></p>";
997  ZyppResObject zobj = sel.installed().zyppObj();
998  ZyppPackage zpkg = castZyppPackage (zobj);
999  if (zpkg) {
1000  const std::list <zypp::ChangelogEntry> &logs = zpkg->changelog();
1001  for (std::list <zypp::ChangelogEntry>::const_iterator it = logs.begin();
1002  it != logs.end(); it++) {
1003  std::string date (it->date().form ("%d %B %Y")), author (it->author()),
1004  changes (it->text());
1005  author = YGUtils::escapeMarkup (author);
1006  changes = YGUtils::escapeMarkup (changes);
1007  YGUtils::replace (changes, "\n", 1, "<br>");
1008  if (author.compare (0, 2, "- ", 2) == 0) // zypp returns a lot of author strings as
1009  author.erase (0, 2); // "- author". wtf?
1010  text += "<i>" + date + " (" + author + "):</i><br><blockquote>" + changes + "</blockquote>";
1011  }
1012  }
1013  return text;
1014  }
1015 };
1016 
1018  GtkWidget *text;
1019 
1020  AuthorsExpander()
1021  : DetailExpander (_("Authors"), false)
1022  {
1023  text = ygtk_rich_text_new();
1024  setChild (text);
1025  }
1026 
1027  virtual bool onlySingleList() { return true; }
1028 
1029  virtual void showList (Ypp::List list)
1030  {
1031  Ypp::Selectable sel = list.get (0);
1032  std::string str (authors (sel));
1033  if (str.empty()) {
1034  str = "<i>"; str += _("Attribute not-specified."); str += "</i>";
1035  }
1036  ygtk_rich_text_set_text (YGTK_RICH_TEXT (text), str.c_str());
1037  }
1038 
1039  virtual void showRefreshList (Ypp::List list) {}
1040 
1041  static std::string authors (Ypp::Selectable &sel)
1042  {
1043  std::string text;
1044  ZyppResObject object = sel.zyppSel()->theObj();
1045  ZyppPackage package = castZyppPackage (object);
1046  if (package) {
1047  std::string packager = package->packager(), vendor = package->vendor(), authors;
1048  packager = YGUtils::escapeMarkup (packager);
1049  vendor = YGUtils::escapeMarkup (vendor);
1050  const std::list <std::string> &authorsList = package->authors();
1051  for (std::list <std::string>::const_iterator it = authorsList.begin();
1052  it != authorsList.end(); it++) {
1053  std::string author (*it);
1054  author = YGUtils::escapeMarkup (author);
1055  if (!authors.empty())
1056  authors += "<br>";
1057  authors += author;
1058  }
1059  // look for Authors line in description
1060  std::string description = package->description();
1061  std::string::size_type i = description.find ("\nAuthors:\n-----", 0);
1062  if (i != std::string::npos) {
1063  i += sizeof ("\nAuthors:\n----");
1064  i = description.find ("\n", i);
1065  if (i != std::string::npos)
1066  i++;
1067  }
1068  else {
1069  i = description.find ("\nAuthor:", 0);
1070  if (i == std::string::npos) {
1071  i = description.find ("\nAuthors:", 0);
1072  if (i != std::string::npos)
1073  i++;
1074  }
1075  if (i != std::string::npos)
1076  i += sizeof ("\nAuthor:");
1077  }
1078  if (i != std::string::npos) {
1079  std::string str = description.substr (i);
1080  str = YGUtils::escapeMarkup (str);
1081  YGUtils::replace (str, "\n", 1, "<br>");
1082  authors += str;
1083  }
1084 
1085  if (!authors.empty()) {
1086  text = "<i>"; text += _("Developed by:"); text += "</i>";
1087  text += ("<blockquote>" + authors) + "</blockquote>";
1088  if (!packager.empty() || !vendor.empty()) {
1089  text += "<i>"; text += _("Packaged by:"); text += "</i>";
1090  text += "<blockquote>";
1091  if (!packager.empty()) text += packager + " ";
1092  if (!vendor.empty()) text += "(" + vendor + ")";
1093  text += "</blockquote>";
1094  }
1095  }
1096  }
1097  return text;
1098  }
1099 };
1100 
1102  YGtkPkgListView *view;
1103 
1105  : DetailExpander (_("Applies to"), false)
1106  {
1107  view = new YGtkPkgListView (true, Ypp::List::NAME_SORT, false, false);
1108  view->addCheckColumn (INSTALLED_CHECK_PROP);
1109  view->addTextColumn (_("Name"), NAME_SUMMARY_PROP, true, -1);
1110  view->addTextColumn (_("Version"), VERSION_PROP, true, 125);
1111  view->addTextColumn (_("Size"), SIZE_PROP, false, 85);
1112  view->addTextColumn (_("Repository"), REPOSITORY_PROP, false, 180);
1113  view->addTextColumn (_("Supportability"), SUPPORT_PROP, false, 120);
1114  gtk_widget_set_size_request (view->getWidget(), -1, 150);
1115 
1116  setChild (view->getWidget());
1117  }
1118 
1119  virtual ~ContentsExpander()
1120  { delete view; }
1121 
1122  virtual bool onlySingleList() { return true; }
1123 
1124  virtual void showList (Ypp::List list)
1125  {
1126  Ypp::Selectable sel = list.get (0);
1127  Ypp::Collection col (sel);
1128 
1129  Ypp::PoolQuery query (Ypp::Selectable::PACKAGE);
1130  query.addCriteria (new Ypp::FromCollectionMatch (col));
1131  view->setQuery (query);
1132  }
1133 
1134  virtual void showRefreshList (Ypp::List list) {}
1135 };
1136 
1138 {
1139 std::list <DetailWidget *> m_widgets;
1140 GtkWidget *m_scroll;
1141 Ypp::List m_list;
1142 
1143  Impl() : m_list (0)
1144  {
1145  DetailWidget *widget;
1146 
1147  GtkWidget *side_vbox = gtk_vbox_new (FALSE, 0);
1148 
1149  if (!YGPackageSelector::get()->onlineUpdateMode()) {
1150  widget = new DetailsExpander();
1151  m_widgets.push_back (widget);
1152  gtk_box_pack_start (GTK_BOX (side_vbox), widget->getWidget(), FALSE, TRUE, 0);
1153  }
1154  widget = new VersionExpander();
1155  m_widgets.push_back (widget);
1156  gtk_box_pack_start (GTK_BOX (side_vbox), widget->getWidget(), FALSE, TRUE, 0);
1157 
1158  GtkWidget *main_vbox = gtk_vbox_new (FALSE, 0);
1159 
1160  widget = new DetailName();
1161  m_widgets.push_back (widget);
1162  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1163 
1164  widget = new DetailDescription();
1165  m_widgets.push_back (widget);
1166  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1167  GtkWidget *detail_description = widget->getWidget();
1168 
1169  if (YGPackageSelector::get()->onlineUpdateMode()) {
1170  widget = new ContentsExpander();
1171  m_widgets.push_back (widget);
1172  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1173  }
1174  else {
1175  widget = new FilelistExpander();
1176  m_widgets.push_back (widget);
1177  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1178  widget = new ChangelogExpander();
1179  m_widgets.push_back (widget);
1180  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1181  widget = new AuthorsExpander();
1182  m_widgets.push_back (widget);
1183  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1184  widget = new DependenciesExpander();
1185  m_widgets.push_back (widget);
1186  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1187  widget = new SupportExpander();
1188  m_widgets.push_back (widget);
1189  gtk_box_pack_start (GTK_BOX (main_vbox), widget->getWidget(), FALSE, TRUE, 0);
1190  }
1191 
1192  GtkWidget *hbox = gtk_hbox_new (FALSE, 2);
1193  gtk_box_pack_start (GTK_BOX (hbox), main_vbox, TRUE, TRUE, 0);
1194  gtk_box_pack_start (GTK_BOX (hbox), side_vbox, FALSE, TRUE, 0);
1195 
1196  GtkWidget *child = gtk_event_box_new();
1197  gtk_container_add (GTK_CONTAINER (child), hbox);
1198 
1199  GdkColor *color = &gtk_widget_get_style(detail_description)->base [GTK_STATE_NORMAL];
1200  gtk_widget_modify_bg (child, GTK_STATE_NORMAL, color);
1201 
1202  m_scroll = gtk_scrolled_window_new (NULL, NULL);
1203  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (m_scroll),
1204  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1205  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (m_scroll), child);
1206 
1207  gtk_widget_show_all (m_scroll);
1208  g_signal_connect (G_OBJECT (m_scroll), "realize",
1209  G_CALLBACK (scroll_realize_cb), this);
1210  Ypp::addSelListener (this);
1211  }
1212 
1213  ~Impl()
1214  {
1215  for (std::list <DetailWidget *>::iterator it = m_widgets.begin();
1216  it != m_widgets.end(); it++)
1217  delete *it;
1218  Ypp::removeSelListener (this);
1219  }
1220 
1221  void refreshList (Ypp::List list)
1222  {
1223  for (std::list <DetailWidget *>::iterator it = m_widgets.begin();
1224  it != m_widgets.end(); it++)
1225  (*it)->refreshList (list);
1226  }
1227 
1228  void setList (Ypp::List list)
1229  {
1230  for (std::list <DetailWidget *>::iterator it = m_widgets.begin();
1231  it != m_widgets.end(); it++)
1232  (*it)->setList (list);
1233 
1234  m_list = list;
1235  scrollTop();
1236  }
1237 
1238  virtual void selectableModified()
1239  { refreshList (m_list); }
1240 
1241  void scrollTop()
1242  {
1243  GtkScrolledWindow *scroll = GTK_SCROLLED_WINDOW (m_scroll);
1244  GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (scroll);
1245  YGUtils::scrollWidget (vadj, true);
1246  }
1247 
1248  // fix cursor keys support
1249  static void move_cursor_cb (GtkTextView *view, GtkMovementStep step, gint count,
1250  gboolean extend_selection, GtkWidget *scroll)
1251  {
1252  GtkScrolledWindow *_scroll = GTK_SCROLLED_WINDOW (scroll);
1253  GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment (_scroll);
1254  GtkAllocation alloc;
1255  gtk_widget_get_allocation(scroll, &alloc);
1256 
1257  int height = alloc.height;
1258  gdouble increment;
1259  switch (step) {
1260  case GTK_MOVEMENT_DISPLAY_LINES:
1261  increment = height / 10.0;
1262  break;
1263  case GTK_MOVEMENT_PAGES:
1264  increment = height * 0.9;
1265  break;
1266  case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
1267  increment = gtk_adjustment_get_upper(adj) - gtk_adjustment_get_lower(adj);
1268  break;
1269  default:
1270  increment = 0.0;
1271  break;
1272  }
1273 
1274  gdouble value = gtk_adjustment_get_value(adj) + (count * increment);
1275  value = MIN (value, gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj));
1276  value = MAX (value, gtk_adjustment_get_lower(adj));
1277  if (value != gtk_adjustment_get_value(adj))
1278  gtk_adjustment_set_value (adj, value);
1279  }
1280 
1281  static void fix_keys (GtkWidget *widget, void *_scroll)
1282  {
1283  GtkWidget *scroll = (GtkWidget *) _scroll;
1284  if (GTK_IS_TEXT_VIEW (widget))
1285  g_signal_connect (G_OBJECT (widget), "move-cursor",
1286  G_CALLBACK (move_cursor_cb), scroll);
1287  else if (GTK_IS_CONTAINER (widget))
1288  gtk_container_foreach (GTK_CONTAINER (widget), fix_keys, _scroll);
1289  }
1290 
1291  static void scroll_realize_cb (GtkWidget *widget, Impl *pThis)
1292  { fix_keys (widget, widget); }
1293 };
1294 
1295 YGtkPkgDetailView::YGtkPkgDetailView()
1296 : impl (new Impl()) {}
1297 
1298 YGtkPkgDetailView::~YGtkPkgDetailView()
1299 { delete impl; }
1300 
1301 GtkWidget *YGtkPkgDetailView::getWidget()
1302 { return impl->m_scroll; }
1303 
1304 void YGtkPkgDetailView::setSelectable (Ypp::Selectable &sel)
1305 {
1306  Ypp::List list (1);
1307  list.append (sel);
1308  impl->setList (list);
1309 }
1310 
1311 void YGtkPkgDetailView::setList (Ypp::List list)
1312 { impl->setList (list); }
1313