libyui-gtk-pkg  2.42.9
 All Classes
ygtkpkglistview.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 /* YGtkPkgListView, Zypp GtkTreeView implementation */
5 // check the header file for information about this widget
6 
7 /*
8  Textdomain "gtk"
9  */
10 
11 #define YUILogComponent "gtk"
12 #include "config.h"
13 #include "YGi18n.h"
14 #include "YGUtils.h"
15 #include "YGUI.h"
16 #include "YGPackageSelector.h"
17 #include "ygtkpkglistview.h"
18 #include "ygtktreeview.h"
19 #include "ygtktreemodel.h"
20 #include "ygtkcellrenderertext.h"
21 #include "ygtkcellrendererbutton.h"
22 #include "ygtkcellrenderersidebutton.h"
23 #include <gtk/gtk.h>
24 #include <string.h>
25 
26 #define GRAY_COLOR "#727272"
27 
28 //** Model
29 
30 enum ImplProperty {
31  // booleans
32  HAS_UPGRADE_PROP = TOTAL_PROPS, TO_UPGRADE_PROP, CAN_TOGGLE_INSTALL_PROP,
33  MANUAL_MODIFY_PROP, IS_LOCKED_PROP,
34  // integer
35  XPAD_PROP,
36  // string
37  FOREGROUND_PROP, BACKGROUND_PROP, REPOSITORY_STOCK_PROP,
38  ACTION_ICON_PROP,
39  // pointer
40  PTR_PROP,
41  TOTAL_IMPL_PROPS
42 };
43 
44 static GType _columnType (int col)
45 {
46  switch (col) {
47  case NAME_PROP: case ACTION_NAME_PROP: case NAME_SUMMARY_PROP:
48  case VERSION_PROP: case SINGLE_VERSION_PROP: case REPOSITORY_PROP:
49  case SUPPORT_PROP: case SIZE_PROP: case STATUS_ICON_PROP:
50  case ACTION_BUTTON_PROP: case ACTION_ICON_PROP: case FOREGROUND_PROP:
51  case BACKGROUND_PROP: case REPOSITORY_STOCK_PROP:
52  return G_TYPE_STRING;
53  case INSTALLED_CHECK_PROP:
54  case HAS_UPGRADE_PROP: case TO_UPGRADE_PROP: case CAN_TOGGLE_INSTALL_PROP:
55  case MANUAL_MODIFY_PROP: case IS_LOCKED_PROP:
56  return G_TYPE_BOOLEAN;
57  case XPAD_PROP:
58  return G_TYPE_INT;
59  case PTR_PROP:
60  return G_TYPE_POINTER;
61  }
62  return 0;
63 }
64 
66 {
67  // we pass GtkTreeView to the model, so we can test for selected rows
68  // and modify text markup appropriely (trashing the data-view model, heh)
69 
70  YGtkZyppModel (Ypp::List list) : m_list (list.clone())
71  { addSelListener (this); }
72 
73  ~YGtkZyppModel()
74  { removeSelListener (this); }
75 
76  void setHighlight (std::list <std::string> keywords)
77  { m_keywords = keywords; }
78 
79 protected:
80  Ypp::List m_list;
81  std::list <std::string> m_keywords;
82 
83  virtual int rowsNb() { return m_list.size(); }
84  virtual int columnsNb() const { return TOTAL_IMPL_PROPS; }
85  virtual bool showEmptyEntry() const { return false; }
86 
87  virtual GType columnType (int col) const
88  { return _columnType (col); }
89 
90  virtual void getValue (int row, int col, GValue *value)
91  {
92  Ypp::Selectable &sel = m_list.get (row);
93  switch (col) {
94  case NAME_PROP: {
95  std::string str (sel.name());
96  highlightMarkupSpan (str, m_keywords);
97  g_value_set_string (value, str.c_str());
98  break;
99  }
100  case ACTION_NAME_PROP: {
101  std::string str ("<b>"), name (sel.name());
102  str.reserve (name.size() + 35);
103  str += getStatusAction (&sel);
104  str += "</b> ";
105  str += name;
106  g_value_set_string (value, str.c_str());
107  break;
108  }
109  case NAME_SUMMARY_PROP: {
110  std::string name (sel.name()), summary (sel.summary()), str;
111  summary = YGUtils::escapeMarkup (summary);
112  highlightMarkupSpan (name, m_keywords);
113  highlightMarkupSpan (summary, m_keywords);
114  str.reserve (name.size() + summary.size() + 64);
115  str = name;
116  if (!summary.empty()) {
117  str += "\n";
118  str += "<span color=\"" GRAY_COLOR "\">";
119  str += "<small>" + summary + "</small>";
120  str += "</span>";
121  }
122  g_value_set_string (value, str.c_str());
123  break;
124  }
125  case REPOSITORY_PROP: {
126  std::string str;
127  if (sel.hasCandidateVersion()) {
128  Ypp::Repository repo (sel.candidate().repository());
129  str = getRepositoryLabel (repo);
130  }
131 #if 0
132  else {
133  Ypp::Repository repo (sel.installed().repository());
134  str = getRepositoryLabel (repo);
135  }
136 #endif
137  g_value_set_string (value, str.c_str());
138  break;
139  }
140  case REPOSITORY_STOCK_PROP: {
141  const char *stock = 0;
142  if (sel.hasCandidateVersion()) {
143  Ypp::Repository repo (sel.candidate().repository());
144  stock = getRepositoryStockIcon (repo);
145  }
146 #if 0
147  else {
148  Ypp::Repository repo (sel.installed().repository());
149  stock = getRepositoryStockIcon (repo);
150  }
151 #endif
152  g_value_set_string (value, stock);
153  break;
154  }
155  case SUPPORT_PROP: {
156  if (sel.type() == Ypp::Selectable::PACKAGE) {
157  Ypp::Package pkg (sel);
158  std::string str (Ypp::Package::supportSummary (pkg.support()));
159  g_value_set_string (value, str.c_str());
160  }
161  break;
162  }
163  case SIZE_PROP: {
164  Size_t size = sel.anyVersion().size();
165  g_value_set_string (value, size.asString().c_str());
166  break;
167  }
168  case VERSION_PROP: {
169  std::string str;
170  str.reserve (128);
171  int cmp = 0;
172  bool hasCandidate = sel.hasCandidateVersion();
173  bool patch = false;
174  if (hasCandidate) {
175  Ypp::Version candidate = sel.candidate();
176  if (sel.isInstalled()) {
177  Ypp::Version installed = sel.installed();
178  if (candidate > installed)
179  cmp = 1;
180  if (candidate < installed)
181  cmp = -1;
182  }
183  }
184  if (cmp > 0) {
185  str += "<span color=\"blue\">";
186 
187  if (sel.type() == Ypp::Selectable::PACKAGE) {
188  Ypp::Package pkg (sel);
189  patch = pkg.isCandidatePatch();
190  }
191  }
192  else if (cmp < 0)
193  str += "<span color=\"red\">";
194 
195  if (cmp == 0) {
196  if (sel.isInstalled()) {
197  if (!hasCandidate) // red for orphan too
198  str += "<span color=\"red\">";
199  str += sel.installed().number();
200  if (!hasCandidate)
201  str += "</span>";
202  }
203  else
204  str += sel.candidate().number();
205  }
206  else {
207  str += sel.candidate().number();
208  if (patch)
209  { str += " <small>"; str += _("patch"); str += "</small>"; }
210  str += "</span>\n<small>";
211  str += sel.installed().number();
212  str += "</small>";
213  }
214  g_value_set_string (value, str.c_str());
215  break;
216  }
217  case SINGLE_VERSION_PROP: {
218  std::string str;
219  str.reserve (128);
220  if (sel.hasCandidateVersion() && !sel.toRemove())
221  str = sel.candidate().number();
222  else
223  str = sel.installed().number();
224  g_value_set_string (value, str.c_str());
225  break;
226  }
227  case INSTALLED_CHECK_PROP: {
228  bool installed; // whether it is installed or will be at apply
229  if (sel.toInstall())
230  installed = true;
231  else if (sel.toRemove())
232  installed = false;
233  else
234  installed = sel.isInstalled();
235  g_value_set_boolean (value, installed);
236  break;
237  }
238  case HAS_UPGRADE_PROP:
239  g_value_set_boolean (value, sel.hasUpgrade());
240  break;
241  case TO_UPGRADE_PROP:
242  g_value_set_boolean (value, sel.hasUpgrade() && sel.toInstall());
243  break;
244  case CAN_TOGGLE_INSTALL_PROP:
245  g_value_set_boolean (value, !sel.isInstalled() || sel.canRemove());
246  break;
247  case MANUAL_MODIFY_PROP:
248  g_value_set_boolean (value, sel.toModify() && !sel.toModifyAuto());
249  break;
250  case IS_LOCKED_PROP:
251  g_value_set_boolean (value, !sel.isLocked());
252  break;
253  case BACKGROUND_PROP: {
254  const char *color = 0;
255  if (sel.toModify())
256  color = "#f4f4b7";
257  g_value_set_string (value, color);
258  break;
259  }
260  case FOREGROUND_PROP: {
261  const char *color = 0;
262  if (sel.toModifyAuto())
263  color = "#6f6f6f";
264  g_value_set_string (value, color);
265  break;
266  }
267  case XPAD_PROP: {
268  int xpad = sel.toModifyAuto() ? 20 : 0;
269  g_value_set_int (value, xpad);
270  break;
271  }
272  case STATUS_ICON_PROP:
273  g_value_set_string (value, getStatusStockIcon (sel));
274  break;
275  case ACTION_ICON_PROP: {
276  const char *stock;
277  if (sel.toModify())
278  stock = GTK_STOCK_UNDO;
279  else if (sel.isInstalled())
280  stock = GTK_STOCK_REMOVE;
281  else
282  stock = GTK_STOCK_ADD;
283  g_value_set_string (value, stock);
284  break;
285  }
286  case ACTION_BUTTON_PROP: {
287  const char *text;
288  if (sel.toModify())
289  text = _("Undo");
290  else if (sel.isInstalled())
291  text = _("Remove");
292  else
293  text = _("Install");
294  g_value_set_string (value, text);
295  break;
296  }
297  case PTR_PROP:
298  g_value_set_pointer (value, (void *) &sel);
299  break;
300  }
301  }
302 
303  virtual void selectableModified()
304  {
305  for (int i = 0; i < rowsNb(); i++)
306  listener->rowChanged (i);
307  }
308 };
309 
310 static Ypp::Selectable *ygtk_zypp_model_get_sel (GtkTreeModel *model, gchar *path_str)
311 {
312  GtkTreeIter iter;
313  gtk_tree_model_get_iter_from_string (model, &iter, path_str);
314  Ypp::Selectable *sel;
315  gtk_tree_model_get (model, &iter, PTR_PROP, &sel, -1);
316  return sel;
317 }
318 
319 static Ypp::Selectable *ygtk_zypp_model_get_sel (GtkTreeModel *model, GtkTreePath *path)
320 {
321  GtkTreeIter iter;
322  gtk_tree_model_get_iter (model, &iter, path);
323  Ypp::Selectable *sel;
324  gtk_tree_model_get (model, &iter, PTR_PROP, &sel, -1);
325  return sel;
326 }
327 
328 //** View
329 
331  GtkWidget *scroll, *view;
332  YGtkPkgListView::Listener *listener;
333  Ypp::List list;
334  bool descriptiveTooltip;
335  int sort_attrb, ascendent : 2;
336  bool userModified;
337  std::list <std::string> m_keywords;
338  bool indentAuto, colorModified;
339 
340  Impl (bool descriptiveTooltip, int default_sort_attrb, bool indentAuto, bool colorModified)
341  : listener (NULL), list (0), descriptiveTooltip (descriptiveTooltip),
342  sort_attrb (default_sort_attrb), ascendent (true), userModified (false),
343  indentAuto (indentAuto), colorModified (colorModified) {}
344 
345  void setList (Ypp::List _list, int _attrb, bool _ascendent, bool userSorted, const std::list <std::string> &keywords)
346  {
347  if (userSorted) userModified = true;
348  if (_list != list || sort_attrb != _attrb || ascendent != _ascendent) {
349  if (_attrb != -1) {
350  if (_list == list && sort_attrb == _attrb)
351  _list.reverse();
352  else
353  _list.sort ((Ypp::List::SortAttribute) _attrb, _ascendent);
354  }
355  list = _list;
356  sort_attrb = _attrb;
357  ascendent = _ascendent;
358  }
359 
360  GtkTreeModel *model = ygtk_tree_model_new (new YGtkZyppModel (list));
361  gtk_tree_view_set_model (GTK_TREE_VIEW (view), model);
362  g_object_unref (G_OBJECT (model));
363  setHighlight (keywords);
364 
365  if (userModified) {
366  GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view));
367  for (GList *i = columns; i; i = i->next) {
368  GtkTreeViewColumn *column = (GtkTreeViewColumn *) i->data;
369  bool v = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (column), "attrb")) == _attrb;
370  gtk_tree_view_column_set_sort_indicator (column, v);
371  if (v)
372  gtk_tree_view_column_set_sort_order (column,
373  ascendent ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING);
374  }
375  g_list_free (columns);
376  }
377 
378  // search_column is one prop (among others) that gets reset on new model
379  gtk_tree_view_set_search_column (GTK_TREE_VIEW (view), NAME_PROP);
380  }
381 
382  void setHighlight (const std::list <std::string> &keywords)
383  {
384  if (m_keywords.empty() && keywords.empty()) return;
385  m_keywords = keywords;
386  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
387  YGtkZyppModel *zmodel = (YGtkZyppModel *) ygtk_tree_model_get_model (model);
388  zmodel->setHighlight (keywords);
389  gtk_widget_queue_draw (view);
390  }
391 };
392 
393 static void right_click_cb (YGtkTreeView *view, gboolean outreach, YGtkPkgListView *pThis)
394 {
395  struct inner {
396  static void appendItem (GtkWidget *menu, const char *_label,
397  const char *tooltip, const char *stock, bool sensitive,
398  void (& callback) (GtkMenuItem *item, YGtkPkgListView *pThis), YGtkPkgListView *pThis)
399  {
400  GtkWidget *item;
401  std::string label;
402  if (_label)
403  label = YGUtils::mapKBAccel (_label);
404  if (stock) {
405  if (_label) {
406  item = gtk_image_menu_item_new_with_mnemonic (label.c_str());
407  GtkWidget *image = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU);
408  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
409  }
410  else
411  item = gtk_image_menu_item_new_from_stock (stock, NULL);
412  }
413  else
414  item = gtk_menu_item_new_with_mnemonic (label.c_str());
415  if (tooltip)
416  gtk_widget_set_tooltip_markup (item, tooltip);
417  gtk_widget_set_sensitive (item, sensitive);
418  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
419  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback), pThis);
420  }
421  static void install_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
422  { pThis->getSelected().install(); }
423  static void reinstall_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
424  { reinstall (pThis->getSelected()); }
425  static void remove_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
426  { pThis->getSelected().remove(); }
427  static void undo_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
428  { pThis->getSelected().undo(); }
429  static void lock_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
430  { pThis->getSelected().lock (true); }
431  static void unlock_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
432  { pThis->getSelected().lock (false); }
433  static void select_all_cb (GtkMenuItem *item, YGtkPkgListView *pThis)
434  { pThis->selectAll(); }
435 
436  static bool canReinstall (Ypp::List list)
437  {
438  for(int i = 0; i < list.size(); i++) {
439  Ypp::Selectable &sel = list.get(i);
440  if (sel.hasInstalledVersion()) {
441  Ypp::Version installedVersion = sel.installed();
442  int j;
443  for (j = 0; j < sel.totalVersions(); j++) {
444  Ypp::Version version = sel.version (j);
445  if (!version.isInstalled() && version == installedVersion)
446  break;
447  }
448  if(j == sel.totalVersions())
449  return false;
450  }
451  else
452  return false;
453  }
454  return true;
455  }
456  static void reinstall (Ypp::List list)
457  {
458  for(int i = 0; i < list.size(); i++) {
459  Ypp::Selectable &sel = list.get(i);
460  Ypp::Version installedVersion = sel.installed();
461  for (int j = 0; j < sel.totalVersions(); j++) {
462  Ypp::Version version = sel.version (j);
463  if (!version.isInstalled() && version == installedVersion) {
464  sel.setCandidate (version);
465  sel.install();
466  break;
467  }
468  }
469  }
470  }
471  };
472 
473  GtkWidget *menu = gtk_menu_new();
474  Ypp::List list = pThis->getSelected();
475  Ypp::Selectable::Type type = Ypp::Selectable::PACKAGE;
476  if (list.size() > 0)
477  type = list.get(0).type();
478 
479  if (!outreach) {
480  Ypp::ListProps props (list);
481 
482  bool canLock = props.canLock(), unlocked = props.isUnlocked();
483  bool modified = props.toModify();
484  bool locked = !unlocked && canLock;
485  if (props.isNotInstalled() && !modified)
486  inner::appendItem (menu, _("&Install"), 0, GTK_STOCK_SAVE,
487  !locked, inner::install_cb, pThis);
488  if (props.hasUpgrade() && !modified)
489  inner::appendItem (menu, _("&Upgrade"), 0, GTK_STOCK_GO_UP,
490  !locked, inner::install_cb, pThis);
491  if (type == Ypp::Selectable::PACKAGE && inner::canReinstall(list) && !modified)
492  inner::appendItem (menu, _("&Re-install"), 0, GTK_STOCK_REFRESH,
493  !locked, inner::reinstall_cb, pThis);
494  if (props.isInstalled() && !modified)
495  inner::appendItem (menu, _("&Remove"), 0, GTK_STOCK_DELETE,
496  !locked && props.canRemove(), inner::remove_cb, pThis);
497  if (modified)
498  inner::appendItem (menu, _("&Undo"), 0, GTK_STOCK_UNDO,
499  true, inner::undo_cb, pThis);
500  if (canLock) {
501  static const char *lock_tooltip =
502  "<b>Package lock:</b> prevents the package status from being modified by "
503  "the dependencies resolver.";
504  if (props.isLocked())
505  inner::appendItem (menu, _("&Unlock"), _(lock_tooltip),
506  GTK_STOCK_DIALOG_AUTHENTICATION, true, inner::unlock_cb, pThis);
507  if (unlocked)
508  inner::appendItem (menu, _("&Lock"), _(lock_tooltip),
509  GTK_STOCK_DIALOG_AUTHENTICATION, !modified,
510  inner::lock_cb, pThis);
511  }
512  }
513 
514  if (type == Ypp::Selectable::PACKAGE || type == Ypp::Selectable::PATCH) {
515  GList *items = gtk_container_get_children (GTK_CONTAINER (menu));
516  g_list_free (items);
517 
518  if (items != NULL) /* add separator if there are other items */
519  gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_separator_menu_item_new());
520  inner::appendItem (menu, NULL, NULL, GTK_STOCK_SELECT_ALL,
521  true, inner::select_all_cb, pThis);
522  ygtk_tree_view_append_show_columns_item (YGTK_TREE_VIEW (pThis->impl->view), menu);
523  }
524  ygtk_tree_view_popup_menu (YGTK_TREE_VIEW (pThis->impl->view), menu);
525 }
526 
527 static void selection_changed_cb (GtkTreeSelection *selection, YGtkPkgListView *pThis)
528 {
529  if (gtk_widget_get_realized (pThis->impl->view) && pThis->impl->listener)
530  pThis->impl->listener->selectionChanged();
531 }
532 
533 static void row_activated_cb (GtkTreeView *view, GtkTreePath *path,
534  GtkTreeViewColumn *column, YGtkPkgListView *pThis)
535 {
536  YGUI::ui()->busyCursor();
537  if (YGPackageSelector::get()->yield()) return;
538 
539  GtkTreeModel *model = gtk_tree_view_get_model (view);
540  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path);
541  if (sel->toModify())
542  sel->undo();
543  else if (sel->isInstalled())
544  sel->remove();
545  else
546  sel->install();
547 
548  YGUI::ui()->normalCursor();
549 }
550 
551 static void check_toggled_cb (GtkCellRendererToggle *renderer, gchar *path_str,
552  YGtkPkgListView *pThis)
553 {
554  YGUI::ui()->busyCursor();
555  if (YGPackageSelector::get()->yield()) return;
556 
557  GtkTreeView *view = GTK_TREE_VIEW (pThis->impl->view);
558  GtkTreeModel *model = gtk_tree_view_get_model (view);
559  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path_str);
560 
561  gboolean active = gtk_cell_renderer_toggle_get_active (renderer);
562  if (sel->toModify())
563  sel->undo();
564  else
565  active ? sel->remove() : sel->install();
566 
567  YGUI::ui()->normalCursor();
568 }
569 
570 static void upgrade_toggled_cb (YGtkCellRendererButton *renderer, gchar *path_str,
571  YGtkPkgListView *pThis)
572 {
573  YGUI::ui()->busyCursor();
574  if (YGPackageSelector::get()->yield()) return;
575 
576  GtkTreeView *view = GTK_TREE_VIEW (pThis->impl->view);
577  GtkTreeModel *model = gtk_tree_view_get_model (view);
578  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path_str);
579  sel->toInstall() ? sel->undo() : sel->install();
580 
581  YGUI::ui()->normalCursor();
582 }
583 
584 static void undo_toggled_cb (YGtkCellRendererButton *renderer, gchar *path_str,
585  YGtkPkgListView *pThis)
586 {
587  GtkTreeView *view = GTK_TREE_VIEW (pThis->impl->view);
588  GtkTreeModel *model = gtk_tree_view_get_model (view);
589  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path_str);
590  sel->undo();
591 }
592 
593 static void action_button_toggled_cb (YGtkCellRendererButton *renderer, gchar *path_str,
594  YGtkPkgListView *pThis)
595 {
596  GtkTreeView *view = GTK_TREE_VIEW (pThis->impl->view);
597  GtkTreeModel *model = gtk_tree_view_get_model (view);
598  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path_str);
599  if (sel->toModify())
600  sel->undo();
601  else if (sel->isInstalled())
602  sel->remove();
603  else
604  sel->install();
605 }
606 
607 static gboolean query_tooltip_cb (GtkWidget *widget, gint x, gint y,
608  gboolean keyboard_mode, GtkTooltip *tooltip, YGtkPkgListView *pThis)
609 {
610  GtkTreeView *view = GTK_TREE_VIEW (widget);
611  GtkTreeModel *model;
612  GtkTreePath *path;
613  GtkTreeIter iter;
614  if (gtk_tree_view_get_tooltip_context (view,
615  &x, &y, keyboard_mode, &model, &path, &iter)) {
616  gtk_tree_view_set_tooltip_row (view, tooltip, path);
617 
618  Ypp::Selectable *sel = ygtk_zypp_model_get_sel (model, path);
619  gtk_tree_path_free (path);
620  std::string text;
621  text.reserve (256);
622  const char *icon = 0;
623 
624  GtkTreeViewColumn *column;
625  int bx, by;
626  gtk_tree_view_convert_widget_to_bin_window_coords (
627  view, x, y, &bx, &by);
628  gtk_tree_view_get_path_at_pos (
629  view, x, y, NULL, &column, NULL, NULL);
630 
631  GtkIconSize icon_size = GTK_ICON_SIZE_MENU;
632  if (column == ygtk_tree_view_get_column (YGTK_TREE_VIEW (view), 0)) {
633  text = getStatusSummary (*sel);
634  icon = getStatusStockIcon (*sel);
635  }
636  else if (pThis->impl->descriptiveTooltip) {
637  text = std::string ("<b>") + sel->name() + "</b>\n\n";
638  text += sel->description (false);
639  switch (sel->type()) {
640  case Ypp::Selectable::PATTERN: {
641  ZyppPattern pattern = castZyppPattern (sel->zyppSel()->theObj());
642  icon = pattern->icon().asString().c_str();
643  if (!(*icon))
644  icon = "pattern-generic";
645  break;
646  }
647  case Ypp::Selectable::PACKAGE:
648  icon = getStatusStockIcon (*sel);
649  break;
650  default: break;
651  }
652  icon_size = GTK_ICON_SIZE_DIALOG;
653  }
654 
655  if (text.empty())
656  return FALSE;
657  gtk_tooltip_set_markup (tooltip, text.c_str());
658  if (icon)
659  gtk_tooltip_set_icon_from_icon_name (tooltip, icon, icon_size);
660  return TRUE;
661  }
662  return FALSE;
663 }
664 
665 static void column_clicked_cb (GtkTreeViewColumn *column, YGtkPkgListView *pThis)
666 {
667  int attrb = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (column), "attrb"));
668  bool ascendent = true;
669  if (gtk_tree_view_column_get_sort_indicator (column))
670  ascendent = gtk_tree_view_column_get_sort_order (column) == GTK_SORT_DESCENDING;
671  pThis->impl->setList (pThis->impl->list, attrb, ascendent, true, pThis->impl->m_keywords);
672 }
673 
674 static void set_sort_column (YGtkPkgListView *pThis, GtkTreeViewColumn *column, int property)
675 {
676  int attrb = -1;
677  switch (property) {
678  case INSTALLED_CHECK_PROP: attrb = Ypp::List::IS_INSTALLED_SORT; break;
679  case NAME_PROP: case ACTION_NAME_PROP: case NAME_SUMMARY_PROP:
680  attrb = Ypp::List::NAME_SORT; break;
681  case REPOSITORY_PROP: attrb = Ypp::List::REPOSITORY_SORT; break;
682  case SUPPORT_PROP: attrb = Ypp::List::SUPPORT_SORT; break;
683  case SIZE_PROP: attrb = Ypp::List::SIZE_SORT; break;
684  }
685 
686  gtk_tree_view_column_set_clickable (column, true);
687  g_object_set_data (G_OBJECT (column), "attrb", GINT_TO_POINTER (attrb));
688  if (attrb != -1)
689  g_signal_connect (G_OBJECT (column), "clicked",
690  G_CALLBACK (column_clicked_cb), pThis);
691 }
692 
693 YGtkPkgListView::YGtkPkgListView (bool descriptiveTooltip, int default_sort, bool indentAuto, bool colorModified, bool variableHeight)
694 : impl (new Impl (descriptiveTooltip, default_sort, indentAuto, colorModified))
695 {
696  impl->scroll = gtk_scrolled_window_new (NULL, NULL);
697  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->scroll),
698  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
699  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->scroll),
700  GTK_SHADOW_IN);
701 
702  GtkTreeView *view = GTK_TREE_VIEW (impl->view = ygtk_tree_view_new (_("No matches.")));
703  if (!variableHeight)
704  gtk_tree_view_set_fixed_height_mode (view, TRUE);
705  gtk_tree_view_set_headers_visible (view, FALSE);
706 
707  GtkTreeSelection *selection = gtk_tree_view_get_selection (view);
708  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
709  g_signal_connect (G_OBJECT (selection), "changed",
710  G_CALLBACK (selection_changed_cb), this);
711 
712  g_signal_connect (G_OBJECT (view), "row-activated",
713  G_CALLBACK (row_activated_cb), this);
714  g_signal_connect (G_OBJECT (view), "right-click",
715  G_CALLBACK (right_click_cb), this);
716  gtk_widget_set_has_tooltip (impl->view, TRUE);
717  g_signal_connect (G_OBJECT (view), "query-tooltip",
718  G_CALLBACK (query_tooltip_cb), this);
719 
720  gtk_container_add (GTK_CONTAINER (impl->scroll), impl->view);
721  gtk_widget_show_all (impl->scroll);
722 }
723 
724 YGtkPkgListView::~YGtkPkgListView()
725 { delete impl; }
726 
727 GtkWidget *YGtkPkgListView::getWidget()
728 { return impl->scroll; }
729 
730 GtkWidget *YGtkPkgListView::getView()
731 { return impl->view; }
732 
733 void YGtkPkgListView::setQuery (Ypp::Query &query)
734 { setList (Ypp::List (query)); }
735 
736 void YGtkPkgListView::setList (Ypp::List list)
737 {
738  std::list <std::string> keywords;
739  impl->setList (list, impl->sort_attrb, impl->ascendent, false, keywords);
740 }
741 
742 void YGtkPkgListView::setHighlight (const std::list <std::string> &keywords)
743 {
744  impl->setHighlight (keywords);
745 
746  int index = keywords.size() == 1 ? impl->list.find (keywords.front()) : -1;
747  if (index != -1) {
748  GtkTreeView *view = GTK_TREE_VIEW (impl->view);
749  GtkTreePath *path = gtk_tree_path_new_from_indices (index, -1);
750  gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, .5, 0);
751 
752  GtkTreeSelection *selection = gtk_tree_view_get_selection (view);
753  gtk_tree_selection_select_path (selection, path);
754  gtk_tree_path_free (path);
755  }
756  else if (gtk_widget_get_realized (impl->view))
757  gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (impl->view), -1, 0);
758 }
759 
760 void YGtkPkgListView::addTextColumn (const char *header, int property, bool visible, int size)
761 {
762  GtkTreeView *view = GTK_TREE_VIEW (impl->view);
763  if (header)
764  gtk_tree_view_set_headers_visible (view, TRUE);
765  GtkTreeViewColumn *column = gtk_tree_view_column_new();
766  gtk_tree_view_column_set_title (column, header);
767 
768  GtkCellRenderer *renderer;
769  if (property == REPOSITORY_PROP) {
770  renderer = gtk_cell_renderer_pixbuf_new();
771  if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
772  gtk_tree_view_column_pack_end (column, renderer, FALSE);
773  else
774  gtk_tree_view_column_pack_start (column, renderer, FALSE);
775  gtk_tree_view_column_set_attributes (column, renderer,
776  "icon-name", REPOSITORY_STOCK_PROP, NULL);
777  }
778 
779  if (property == VERSION_PROP) {
780  renderer = ygtk_cell_renderer_side_button_new();
781  g_object_set (G_OBJECT (renderer), "stock-id", GTK_STOCK_GO_UP, NULL);
782  g_signal_connect (G_OBJECT (renderer), "toggled",
783  G_CALLBACK (upgrade_toggled_cb), this);
784  }
785  else
786  renderer = ygtk_cell_renderer_text_new();
787 
788  gtk_tree_view_column_pack_start (column, renderer, TRUE);
789  gtk_tree_view_column_set_attributes (column, renderer,
790  "markup", property, "sensitive", IS_LOCKED_PROP, NULL);
791 
792  if (impl->colorModified)
793  gtk_tree_view_column_add_attribute (column, renderer,
794  "cell-background", BACKGROUND_PROP);
795  if (impl->indentAuto) {
796  gtk_tree_view_column_add_attribute (column, renderer, "xpad", XPAD_PROP);
797  gtk_tree_view_column_add_attribute (column, renderer, "foreground", FOREGROUND_PROP);
798  }
799 
800  PangoEllipsizeMode ellipsize = PANGO_ELLIPSIZE_END;
801  if (size >= 0 && property != NAME_SUMMARY_PROP)
802  ellipsize = PANGO_ELLIPSIZE_MIDDLE;
803  g_object_set (G_OBJECT (renderer), "ellipsize", ellipsize, NULL);
804 
805  if (property == VERSION_PROP) {
806  gtk_tree_view_column_add_attribute (column, renderer,
807  "button-visible", HAS_UPGRADE_PROP);
808  gtk_tree_view_column_add_attribute (column, renderer,
809  "active", TO_UPGRADE_PROP);
810  }
811 
812  if (size != -1) // on several columns
813  gtk_tree_view_set_rules_hint (view, TRUE);
814  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
815  gtk_tree_view_column_set_resizable (column, TRUE);
816  if (size >= 0)
817  gtk_tree_view_column_set_fixed_width (column, size);
818  else
819  gtk_tree_view_column_set_expand (column, TRUE);
820  gtk_tree_view_column_set_visible (column, visible);
821  set_sort_column (this, column, property);
822  ygtk_tree_view_append_column (YGTK_TREE_VIEW (view), column);
823 }
824 
825 void YGtkPkgListView::addCheckColumn (int property)
826 {
827  GtkTreeView *view = GTK_TREE_VIEW (impl->view);
828  GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
829  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (NULL,
830  renderer, "active", property, "sensitive", IS_LOCKED_PROP, NULL);
831  if (impl->colorModified)
832  gtk_tree_view_column_add_attribute (column, renderer,
833  "cell-background", BACKGROUND_PROP);
834  if (property == INSTALLED_CHECK_PROP)
835  gtk_tree_view_column_add_attribute (column, renderer,
836  "activatable", CAN_TOGGLE_INSTALL_PROP);
837  g_signal_connect (G_OBJECT (renderer), "toggled",
838  G_CALLBACK (check_toggled_cb), this);
839 
840  // it seems like GtkCellRendererToggle has no width at start, so fixed doesn't work
841  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
842  gtk_tree_view_column_set_fixed_width (column, 25);
843  set_sort_column (this, column, property);
844  gtk_tree_view_append_column (view, column);
845 }
846 
847 void YGtkPkgListView::addImageColumn (const char *header, int property, bool onlyManualModified)
848 {
849  GtkTreeView *view = GTK_TREE_VIEW (impl->view);
850  GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
851  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (
852  header, renderer, "icon-name", property, NULL);
853  if (impl->colorModified)
854  gtk_tree_view_column_add_attribute (column, renderer,
855  "cell-background", BACKGROUND_PROP);
856  if (onlyManualModified)
857  gtk_tree_view_column_add_attribute (column, renderer,
858  "visible", MANUAL_MODIFY_PROP);
859 
860  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
861  int height = MAX (32, YGUtils::getCharsHeight (impl->view, 1));
862  gtk_cell_renderer_set_fixed_size (renderer, -1, height);
863  gtk_tree_view_column_set_fixed_width (column, 38);
864  gtk_tree_view_append_column (view, column);
865 }
866 
867 void YGtkPkgListView::addButtonColumn (const char *header, int property)
868 {
869  GtkCellRenderer *renderer = ygtk_cell_renderer_button_new();
870  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (
871  header, renderer, "sensitive", IS_LOCKED_PROP, NULL);
872  if (impl->colorModified)
873  gtk_tree_view_column_add_attribute (column, renderer,
874  "cell-background", BACKGROUND_PROP);
875 
876  gboolean show_icon;
877  g_object_get (G_OBJECT (gtk_settings_get_default()), "gtk-button-images", &show_icon, NULL);
878 
879  const char *text;
880  if (property == UNDO_BUTTON_PROP) { // static property (always "Undo")
881  text = _("Undo");
882  g_object_set (G_OBJECT (renderer), "text", text, NULL);
883  if (show_icon)
884  g_object_set (G_OBJECT (renderer), "stock-id", GTK_STOCK_UNDO, NULL);
885  gtk_tree_view_column_add_attribute (column, renderer,
886  "visible", MANUAL_MODIFY_PROP);
887  g_signal_connect (G_OBJECT (renderer), "toggled",
888  G_CALLBACK (undo_toggled_cb), this);
889  }
890  else {
891  text = "xxxxxxxxxx";
892  gtk_tree_view_column_add_attribute (column, renderer,
893  "text", property);
894  if (show_icon)
895  gtk_tree_view_column_add_attribute (column, renderer,
896  "stock-id", ACTION_ICON_PROP);
897  g_signal_connect (G_OBJECT (renderer), "toggled",
898  G_CALLBACK (action_button_toggled_cb), this);
899  }
900 
901  PangoRectangle rect;
902  int width = 0;
903  PangoLayout *layout = gtk_widget_create_pango_layout (impl->view,
904  strlen (header) > strlen (text) ? header : text);
905  pango_layout_get_pixel_extents (layout, NULL, &rect);
906  width = MAX (width, rect.width);
907  g_object_unref (G_OBJECT (layout));
908  width += 18;
909  if (show_icon) {
910  int icon_width;
911  gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (impl->view),
912  GTK_ICON_SIZE_MENU, &icon_width, NULL);
913  width += icon_width;
914  }
915 
916  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
917  gtk_tree_view_column_set_fixed_width (column, width);
918  gtk_tree_view_append_column (GTK_TREE_VIEW (impl->view), column);
919 }
920 
921 void YGtkPkgListView::setListener (YGtkPkgListView::Listener *listener)
922 { impl->listener = listener; }
923 
924 Ypp::List YGtkPkgListView::getList()
925 { return impl->list; }
926 
927 Ypp::List YGtkPkgListView::getSelected()
928 {
929  GtkTreeModel *model;
930  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->view));
931  GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
932 
933  Ypp::List list (g_list_length (rows));
934  for (GList *i = rows; i; i = i->next) {
935  GtkTreeIter iter;
936  GtkTreePath *path = (GtkTreePath *) i->data;
937  gtk_tree_model_get_iter (model, &iter, path);
938 
939  Ypp::Selectable *sel;
940  gtk_tree_model_get (model, &iter, PTR_PROP, &sel, -1);
941  gtk_tree_path_free (path);
942  list.append (*sel);
943  }
944  g_list_free (rows);
945  return list;
946 }
947 
948 void YGtkPkgListView::selectAll()
949 {
950  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->view));
951  gtk_tree_selection_select_all (selection);
952 }
953 
954 // utilities
955 
956 const char *getStatusAction (Ypp::Selectable *sel)
957 {
958  const char *action = 0;
959  if (sel->toInstall()) {
960  action = _("install");
961  if (sel->type() == Ypp::Selectable::PACKAGE) {
962  if (sel->isInstalled()) {
963  Ypp::Version candidate = sel->candidate(), installed = sel->installed();
964  if (candidate > installed)
965  action = _("upgrade");
966  else if (candidate < installed)
967  action = _("downgrade");
968  else
969  action = _("re-install");
970  }
971  }
972  }
973  else if (sel->toRemove())
974  action = _("remove");
975  else //if (sel->toModify())
976  action = _("modify"); // generic for locked and so on
977  return action;
978 }
979 
980 std::string getStatusSummary (Ypp::Selectable &sel)
981 {
982  std::string text;
983  if (sel.isLocked())
984  text = _("locked: right-click to unlock");
985  else if (sel.toInstall()) {
986  Ypp::Version candidate = sel.candidate();
987  text = _("To install") + std::string (" ") + candidate.number();
988  }
989  else if (sel.toRemove())
990  text = _("To remove");
991  else if (sel.isInstalled()) {
992  text = _("Installed");
993  if (sel.hasUpgrade()) {
994  text += " ";
995  text += _("(upgrade available)");
996  }
997  }
998  else
999  text = _("Not installed");
1000  if (sel.toModifyAuto()) {
1001  text += "\n<i>";
1002  text += _("status changed by the dependencies resolver");
1003  text += "</i>";
1004  }
1005  return text;
1006 }
1007 
1008 const char *getStatusStockIcon (Ypp::Selectable &sel)
1009 {
1010  const char *icon;
1011  if (sel.isLocked())
1012  icon = GTK_STOCK_DIALOG_AUTHENTICATION;
1013  else if (sel.toInstall()) {
1014  icon = GTK_STOCK_ADD;
1015  if (sel.type() == Ypp::Selectable::PACKAGE) {
1016  Ypp::Version candidate = sel.candidate();
1017  if (sel.isInstalled()) {
1018  Ypp::Version installed = sel.installed();
1019  if (candidate > installed)
1020  icon = GTK_STOCK_GO_UP;
1021  else if (candidate < installed)
1022  icon = GTK_STOCK_GO_DOWN;
1023  else // if (candidate == installed)
1024  icon = GTK_STOCK_REFRESH;
1025  }
1026  }
1027  }
1028  else if (sel.toRemove())
1029  icon = GTK_STOCK_REMOVE;
1030  else if (sel.isInstalled())
1031  icon = GTK_STOCK_HARDDISK;
1032  else
1033  icon = "package";
1034  return icon;
1035 }
1036 
1037 std::string getRepositoryLabel (Ypp::Repository &repo)
1038 {
1039  std::string name (repo.name()), url, str;
1040  url = repo.isSystem() ? _("Installed packages") : repo.url();
1041  str.reserve (name.size() + url.size() + 64);
1042  str = name + "\n";
1043  str += "<span color=\"" GRAY_COLOR "\">";
1044  str += "<small>" + url + "</small>";
1045  str += "</span>";
1046  return str;
1047 }
1048 
1049 const char *getRepositoryStockIcon (const std::string &url)
1050 {
1051  if (url.empty())
1052  return GTK_STOCK_MISSING_IMAGE;
1053  if (url.compare (0, 2, "cd", 2) == 0 || url.compare (0, 3, "dvd", 3) == 0)
1054  return GTK_STOCK_CDROM;
1055  if (url.compare (0, 3, "iso", 3) == 0)
1056  return GTK_STOCK_FILE;
1057  if (url.find ("KDE") != std::string::npos)
1058  return "pattern-kde";
1059  if (url.find ("GNOME") != std::string::npos)
1060  return "pattern-gnome";
1061  if (url.find ("update") != std::string::npos)
1062  return "yast-update";
1063  if (url.find ("home") != std::string::npos)
1064  return "yast-users";
1065  return GTK_STOCK_NETWORK;
1066 }
1067 
1068 const char *getRepositoryStockIcon (Ypp::Repository &repo)
1069 {
1070  if (repo.isSystem())
1071  return "yast-host";
1072  return getRepositoryStockIcon (repo.url());
1073 }
1074 
1075 void highlightMarkup (std::string &text, const std::list <std::string> &keywords,
1076  const char *openTag, const char *closeTag, int openTagLen, int closeTagLen)
1077 {
1078  if (keywords.empty()) return;
1079  text.reserve ((openTagLen + closeTagLen + 2) * 6);
1080  const char *i = text.c_str();
1081  while (*i) {
1082  std::list <std::string>::const_iterator it;
1083  for (it = keywords.begin(); it != keywords.end(); it++) {
1084  const std::string &keyword = *it;
1085  int len = keyword.size();
1086  if (strncasecmp (i, keyword.c_str(), len) == 0) {
1087  int pos = i - text.c_str();
1088  text.insert (pos+len, closeTag);
1089  text.insert (pos, openTag);
1090  i = text.c_str() + pos + len + openTagLen + closeTagLen - 2;
1091  break;
1092  }
1093  }
1094  if (it == keywords.end())
1095  i++;
1096  }
1097 }
1098 
1099 void highlightMarkupSpan (std::string &text, const std::list <std::string> &keywords)
1100 {
1101  static const char openTag[] = "<span fgcolor=\"#000000\" bgcolor=\"#ffff00\">";
1102  static const char closeTag[] = "</span>";
1103  highlightMarkup (text, keywords, openTag, closeTag, sizeof (openTag), sizeof (closeTag));
1104 }
1105