/ src / gtk-sat-tree.c
gtk-sat-tree.c
  1  /*
  2    Gpredict: Real-time satellite tracking and orbit prediction program
  3  
  4    Copyright (C)  2001-2010  Alexandru Csete, OZ9AEC.
  5  
  6    Authors: Alexandru Csete <oz9aec@gmail.com>
  7  
  8    Comments, questions and bugreports should be submitted via
  9    http://sourceforge.net/projects/gpredict/
 10    More details can be found at the project home page:
 11  
 12    http://gpredict.oz9aec.net/
 13  
 14    This program is free software; you can redistribute it and/or modify
 15    it under the terms of the GNU General Public License as published by
 16    the Free Software Foundation; either version 2 of the License, or
 17    (at your option) any later version.
 18    
 19    This program is distributed in the hope that it will be useful,
 20    but WITHOUT ANY WARRANTY; without even the implied warranty of
 21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 22    GNU General Public License for more details.
 23    
 24    You should have received a copy of the GNU General Public License
 25    along with this program; if not, visit http://www.fsf.org/
 26  */
 27  
 28  #ifdef HAVE_CONFIG_H
 29  #include <build-config.h>
 30  #endif
 31  
 32  #include <glib/gi18n.h>
 33  #include <gtk/gtk.h>
 34  #include "compat.h"
 35  #include "gtk-sat-tree.h"
 36  #include "sat-log.h"
 37  
 38  
 39  static void     gtk_sat_tree_class_init(GtkSatTreeClass * class);
 40  static void     gtk_sat_tree_init(GtkSatTree * sat_tree);
 41  static void     gtk_sat_tree_destroy(GtkObject * object);
 42  static GtkTreeModel *create_and_fill_model(guint flags);
 43  static void     column_toggled(GtkCellRendererToggle * cell,
 44                                 gchar * path_str, gpointer data);
 45  static gint     scan_tle_file(const gchar * path,
 46                                GtkTreeStore * store, GtkTreeIter * node);
 47  static gboolean check_and_select_sat(GtkTreeModel * model,
 48                                       GtkTreePath * path,
 49                                       GtkTreeIter * iter, gpointer data);
 50  static gboolean uncheck_sat(GtkTreeModel * model,
 51                              GtkTreePath * path,
 52                              GtkTreeIter * iter, gpointer data);
 53  static gint     compare_func(GtkTreeModel * model,
 54                               GtkTreeIter * a,
 55                               GtkTreeIter * b, gpointer userdata);
 56  static void     expand_cb(GtkWidget * button, gpointer tree);
 57  static void     collapse_cb(GtkWidget * button, gpointer tree);
 58  
 59  
 60  static GtkVBoxClass *parent_class = NULL;
 61  
 62  
 63  GType gtk_sat_tree_get_type()
 64  {
 65      static GType    gtk_sat_tree_type = 0;
 66  
 67      if (!gtk_sat_tree_type)
 68      {
 69          static const GTypeInfo gtk_sat_tree_info = {
 70              sizeof(GtkSatTreeClass),
 71              NULL,               /* base_init */
 72              NULL,               /* base_finalize */
 73              (GClassInitFunc) gtk_sat_tree_class_init,
 74              NULL,               /* class_finalize */
 75              NULL,               /* class_data */
 76              sizeof(GtkSatTree),
 77              1,                  /* n_preallocs */
 78              (GInstanceInitFunc) gtk_sat_tree_init,
 79              NULL
 80          };
 81  
 82          gtk_sat_tree_type = g_type_register_static(GTK_TYPE_VBOX,
 83                                                     "GtkSatTree",
 84                                                     &gtk_sat_tree_info, 0);
 85      }
 86  
 87      return gtk_sat_tree_type;
 88  }
 89  
 90  static void gtk_sat_tree_class_init(GtkSatTreeClass * class)
 91  {
 92      GObjectClass   *gobject_class;
 93      GtkObjectClass *object_class;
 94      GtkWidgetClass *widget_class;
 95      GtkContainerClass *container_class;
 96  
 97      gobject_class = G_OBJECT_CLASS(class);
 98      object_class = (GtkObjectClass *) class;
 99      widget_class = (GtkWidgetClass *) class;
100      container_class = (GtkContainerClass *) class;
101  
102      parent_class = g_type_class_peek_parent(class);
103  
104      object_class->destroy = gtk_sat_tree_destroy;
105  }
106  
107  static void gtk_sat_tree_init(GtkSatTree * sat_tree)
108  {
109      (void)sat_tree;
110  }
111  
112  static void gtk_sat_tree_destroy(GtkObject * object)
113  {
114      GtkSatTree     *sat_tree = GTK_SAT_TREE(object);
115  
116      /* clear list of selected satellites */
117      /* crashes on 2. instance: g_slist_free (sat_tree->selection); */
118      guint           n, i;
119      gpointer        data;
120  
121      n = g_slist_length(sat_tree->selection);
122  
123      for (i = 0; i < n; i++)
124      {
125          /* get the first element and delete it */
126          data = g_slist_nth_data(sat_tree->selection, 0);
127          sat_tree->selection = g_slist_remove(sat_tree->selection, data);
128      }
129  
130      (*GTK_OBJECT_CLASS(parent_class)->destroy) (object);
131  }
132  
133  /**
134   * Create a new GtkSatTree widget
135   *
136   * @param flags Flags indicating which columns should be visible
137   *              (see gtk_sat_tree_flag_t)
138   * @return A GtkSatTree widget.
139   */
140  GtkWidget      *gtk_sat_tree_new(guint flags)
141  {
142      GtkWidget      *widget;
143      GtkSatTree     *sat_tree;
144      GtkTreeModel   *model;
145      GtkCellRenderer *renderer;
146      GtkTreeViewColumn *column;
147      GtkWidget      *hbox;
148      GtkWidget      *expbut;
149      GtkWidget      *colbut;
150  
151      if (!flags)
152          flags = GTK_SAT_TREE_DEFAULT_FLAGS;
153  
154      widget = g_object_new(GTK_TYPE_SAT_TREE, NULL);
155      sat_tree = GTK_SAT_TREE(widget);
156  
157      sat_tree->flags = flags;
158  
159      /* create list and model */
160      sat_tree->tree = gtk_tree_view_new();
161      gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(sat_tree->tree), TRUE);
162      model = create_and_fill_model(flags);
163      gtk_tree_view_set_model(GTK_TREE_VIEW(sat_tree->tree), model);
164      g_object_unref(model);
165  
166      /* sort the tree by name */
167      gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model),
168                                      GTK_SAT_TREE_COL_NAME,
169                                      compare_func, NULL, NULL);
170      gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
171                                           GTK_SAT_TREE_COL_NAME,
172                                           GTK_SORT_ASCENDING);
173  
174      /* create tree view columns */
175      /* label column */
176      renderer = gtk_cell_renderer_text_new();
177      column = gtk_tree_view_column_new_with_attributes(_("Satellite"), renderer,
178                                                        "text",
179                                                        GTK_SAT_TREE_COL_NAME,
180                                                        NULL);
181      gtk_tree_view_insert_column(GTK_TREE_VIEW(sat_tree->tree), column, -1);
182      if (!(flags & GTK_SAT_TREE_FLAG_NAME))
183          gtk_tree_view_column_set_visible(column, FALSE);
184  
185      /* catalogue number */
186      renderer = gtk_cell_renderer_text_new();
187      column = gtk_tree_view_column_new_with_attributes(_("Catnum"), renderer,
188                                                        "text",
189                                                        GTK_SAT_TREE_COL_CATNUM,
190                                                        "visible",
191                                                        GTK_SAT_TREE_COL_VIS,
192                                                        NULL);
193      gtk_tree_view_insert_column(GTK_TREE_VIEW(sat_tree->tree), column, -1);
194      if (!(flags & GTK_SAT_TREE_FLAG_CATNUM))
195          gtk_tree_view_column_set_visible(column, FALSE);
196  
197      /* epoch */
198      renderer = gtk_cell_renderer_text_new();
199      column = gtk_tree_view_column_new_with_attributes(_("Epoch"), renderer,
200                                                        "text",
201                                                        GTK_SAT_TREE_COL_EPOCH,
202                                                        "visible",
203                                                        GTK_SAT_TREE_COL_VIS,
204                                                        NULL);
205      gtk_tree_view_insert_column(GTK_TREE_VIEW(sat_tree->tree), column, -1);
206      if (!(flags & GTK_SAT_TREE_FLAG_EPOCH))
207          gtk_tree_view_column_set_visible(column, FALSE);
208  
209      /* checkbox column */
210      renderer = gtk_cell_renderer_toggle_new();
211      sat_tree->handler_id = g_signal_connect(renderer, "toggled",
212                                              G_CALLBACK(column_toggled),
213                                              widget);
214  
215      column = gtk_tree_view_column_new_with_attributes(_("Selected"), renderer,
216                                                        "active",
217                                                        GTK_SAT_TREE_COL_SEL,
218                                                        "visible",
219                                                        GTK_SAT_TREE_COL_VIS,
220                                                        NULL);
221      gtk_tree_view_append_column(GTK_TREE_VIEW(sat_tree->tree), column);
222      gtk_tree_view_column_set_alignment(column, 0.5);
223      if (!(flags & GTK_SAT_TREE_FLAG_SEL))
224          gtk_tree_view_column_set_visible(column, FALSE);
225  
226  
227      /* scrolled window */
228      GTK_SAT_TREE(widget)->swin = gtk_scrolled_window_new(NULL, NULL);
229      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW
230                                     (GTK_SAT_TREE(widget)->swin),
231                                     GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
232  
233      gtk_container_add(GTK_CONTAINER(GTK_SAT_TREE(widget)->swin),
234                        GTK_SAT_TREE(widget)->tree);
235  
236      //gtk_container_add (GTK_CONTAINER (widget), GTK_SAT_TREE (widget)->swin);
237      gtk_box_pack_start(GTK_BOX(widget), GTK_SAT_TREE(widget)->swin, TRUE, TRUE,
238                         0);
239  
240      /* expand and collabse buttons */
241      expbut = gtk_button_new_with_label(_("Expand"));
242      gtk_widget_set_tooltip_text(expbut,
243                                  _
244                                  ("Expand all nodes in the tree to make it searchable"));
245      g_signal_connect(expbut, "clicked", G_CALLBACK(expand_cb), sat_tree);
246  
247      colbut = gtk_button_new_with_label(_("Collapse"));
248      gtk_widget_set_tooltip_text(colbut, _("Collapse all nodes in the tree"));
249      g_signal_connect(colbut, "clicked", G_CALLBACK(collapse_cb), sat_tree);
250  
251      hbox = gtk_hbutton_box_new();
252      gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_START);
253      gtk_box_pack_start(GTK_BOX(hbox), expbut, FALSE, TRUE, 0);
254      gtk_box_pack_start(GTK_BOX(hbox), colbut, FALSE, TRUE, 0);
255  
256      gtk_box_pack_start(GTK_BOX(widget), hbox, FALSE, FALSE, 5);
257  
258      gtk_widget_show_all(widget);
259  
260      /* initialise selection */
261      GTK_SAT_TREE(widget)->selection = NULL;
262  
263      return widget;
264  }
265  
266  /** FIXME: flags not needed here */
267  static GtkTreeModel *create_and_fill_model(guint flags)
268  {
269      GtkTreeStore   *store;      /* the list store data structure */
270      GtkTreeIter     node;       /* new top level node added to the tree store */
271      GDir           *dir;
272      gchar          *dirname;
273      gchar          *path;
274      gchar          *nodename;
275      gchar         **buffv;
276      const gchar    *fname;
277      guint           num = 0;
278  
279      (void)flags;
280  
281      /* create a new tree store */
282      store = gtk_tree_store_new(GTK_SAT_TREE_COL_NUM, G_TYPE_STRING,     // name
283                                 G_TYPE_INT,      // catnum
284                                 G_TYPE_STRING,   // epoch
285                                 G_TYPE_BOOLEAN,  // selected
286                                 G_TYPE_BOOLEAN   // visible
287          );
288  
289      dirname = g_strconcat(g_get_home_dir(),
290                            G_DIR_SEPARATOR_S, ".gpredict2",
291                            G_DIR_SEPARATOR_S, "tle", NULL);
292  
293      sat_log_log(SAT_LOG_LEVEL_DEBUG,
294                  _("%s:%d: Directory is: %s"), __FILE__, __LINE__, dirname);
295  
296      dir = g_dir_open(dirname, 0, NULL);
297  
298      /* no tle files */
299      if (!dir)
300      {
301          sat_log_log(SAT_LOG_LEVEL_ERROR,
302                      _("%s:%d: No .tle files found in %s."),
303                      __FILE__, __LINE__, dirname);
304  
305          g_free(dirname);
306  
307          return GTK_TREE_MODEL(store);;
308      }
309  
310      /* Scan data directory for .tle files.
311         For each file scan through the file and
312         add entry to the tree.
313       */
314      while ((fname = g_dir_read_name(dir)))
315      {
316  
317          if (g_str_has_suffix(fname, ".tle"))
318          {
319  
320              buffv = g_strsplit(fname, ".tle", 0);
321              nodename = g_strdup(buffv[0]);
322              nodename[0] = g_ascii_toupper(nodename[0]);
323  
324              /* create a new top level node in the tree */
325              gtk_tree_store_append(store, &node, NULL);
326              gtk_tree_store_set(store, &node,
327                                 GTK_SAT_TREE_COL_NAME, nodename,
328                                 GTK_SAT_TREE_COL_VIS, FALSE, -1);
329  
330              /* build full path til file and sweep it for sats */
331              path = g_strconcat(dirname, G_DIR_SEPARATOR_S, fname, NULL);
332  
333              num = scan_tle_file(path, store, &node);
334  
335              g_free(path);
336              g_free(nodename);
337              g_strfreev(buffv);
338  
339              sat_log_log(SAT_LOG_LEVEL_INFO,
340                          _("%s:%d: Read %d sats from %s "),
341                          __FILE__, __LINE__, num, fname);
342          }
343      }
344  
345      g_dir_close(dir);
346      g_free(dirname);
347  
348      return GTK_TREE_MODEL(store);
349  }
350  
351  /**
352   * Scan .tle file and add satellites to GtkTreeStore.
353   *
354   * @param path Full path of the tle file.
355   * @param store The GtkTreeStore to store the satellites into.
356   * @param node The parent node for the satellites
357   * @return The number of satellites that have been read into the tree.
358   */
359  static gint scan_tle_file(const gchar * path, GtkTreeStore * store,
360                            GtkTreeIter * node)
361  {
362      guint           i = 0;
363      guint           j;
364      GIOChannel     *tlefile;
365      GError         *error = NULL;
366      GtkTreeIter     sat_iter;
367      gchar          *line;
368      gsize           length;
369      gchar           catstr[6];
370  
371      gchar          *satnam;
372      guint           catnum;
373  
374      /* open IO channel and read 3 lines at a time */
375      tlefile = g_io_channel_new_file(path, "r", &error);
376  
377      if (error != NULL)
378      {
379          sat_log_log(SAT_LOG_LEVEL_ERROR,
380                      _("%s:%d: Failed to open %s (%s)"),
381                      __FILE__, __LINE__, path, error->message);
382          g_clear_error(&error);
383      }
384      else if (tlefile)
385      {
386  
387          /*** FIXME: More error handling please */
388  
389          while (g_io_channel_read_line(tlefile, &line, &length, NULL, NULL) !=
390                 G_IO_STATUS_EOF)
391          {
392  
393              /* satellite name can be found in the first line */
394              satnam = g_strdup(line);
395              g_strchomp(satnam);
396  
397              /* free allocated line */
398              g_free(line);
399  
400              /* extract catnum from second line; index 2..6 */
401              g_io_channel_read_line(tlefile, &line, &length, NULL, NULL);
402  
403              for (j = 2; j < 7; j++)
404              {
405                  catstr[j - 2] = line[j];
406              }
407              catstr[5] = '\0';
408  
409              catnum = (guint) g_ascii_strtod(catstr, NULL);
410  
411              /* insert satnam and catnum */
412              gtk_tree_store_append(store, &sat_iter, node);
413              gtk_tree_store_set(store, &sat_iter,
414                                 GTK_SAT_TREE_COL_NAME, satnam,
415                                 GTK_SAT_TREE_COL_CATNUM, catnum,
416                                 GTK_SAT_TREE_COL_SEL, FALSE,
417                                 GTK_SAT_TREE_COL_VIS, TRUE, -1);
418  
419              g_free(satnam);
420              g_free(line);
421  
422              /* read the third line */
423              g_io_channel_read_line(tlefile, &line, &length, NULL, NULL);
424  
425              g_free(line);
426              i++;
427          }
428  
429          /* close IO channel; don't care about status */
430          g_io_channel_shutdown(tlefile, TRUE, NULL);
431          g_io_channel_unref(tlefile);
432      }
433  
434      return i;
435  }
436  
437  /**
438   * Manage toggle signals.
439   *
440   * @param cell cell.
441   * @param path_str Path string.
442   * @param data Pointer to the GtkSatTree widget.
443   *
444   * This function is called when the user toggles the visibility for a column.
445   * It will add or remove the toggled satellite from the list of selected sats.
446   */
447  static void column_toggled(GtkCellRendererToggle * cell,
448                             gchar * path_str, gpointer data)
449  {
450      GtkSatTree     *sat_tree = GTK_SAT_TREE(data);
451      GtkTreeModel   *model =
452          gtk_tree_view_get_model(GTK_TREE_VIEW(sat_tree->tree));
453      GtkTreePath    *path = gtk_tree_path_new_from_string(path_str);
454      GtkTreeIter     iter;
455      gboolean        toggle_item;
456      guint           catnum;
457  
458      (void)cell;
459  
460      /* get toggled iter */
461      gtk_tree_model_get_iter(model, &iter, path);
462      gtk_tree_model_get(model, &iter,
463                         GTK_SAT_TREE_COL_CATNUM, &catnum,
464                         GTK_SAT_TREE_COL_SEL, &toggle_item, -1);
465  
466      /* do something with the value */
467      toggle_item ^= 1;
468  
469      if (toggle_item)
470      {
471  
472          /* only append if sat not already in list */
473          if (!g_slist_find(sat_tree->selection, GUINT_TO_POINTER(catnum)))
474          {
475              sat_tree->selection = g_slist_append(sat_tree->selection,
476                                                   GUINT_TO_POINTER(catnum));
477              sat_log_log(SAT_LOG_LEVEL_DEBUG,
478                          _("%s:%d: Satellite %d selected."),
479                          __FILE__, __LINE__, catnum);
480  
481              /* Scan the tree for other instances of this sat. For example is
482                 CUTE-1.7 present in both AMATEUR and CUBESAT.
483                 We will need access to both the sat_tree and the catnum in the
484                 foreach callback, so we attach catnum as data to the sat_tree
485               */
486              g_object_set_data(G_OBJECT(sat_tree), "tmp",
487                                GUINT_TO_POINTER(catnum));
488  
489              /* find the satellite in the tree */
490              gtk_tree_model_foreach(model, check_and_select_sat, sat_tree);
491  
492          }
493          else
494          {
495              sat_log_log(SAT_LOG_LEVEL_INFO,
496                          _("%s:%d: Satellite %d already selected; skip..."),
497                          __FILE__, __LINE__, catnum);
498          }
499      }
500      else
501      {
502          sat_tree->selection = g_slist_remove(sat_tree->selection,
503                                               GUINT_TO_POINTER(catnum));
504          sat_log_log(SAT_LOG_LEVEL_DEBUG,
505                      _("%s:%d: Satellite %d de-selected."),
506                      __FILE__, __LINE__, catnum);
507  
508          /* Scan the tree for other instances of this sat. For example is
509             CUTE-1.7 present in both AMATEUR and CUBESAT.
510             We will need access to both the sat_tree and the catnum in the
511             foreach callback, so we attach catnum as data to the sat_tree
512           */
513          g_object_set_data(G_OBJECT(sat_tree), "tmp", GUINT_TO_POINTER(catnum));
514  
515          /* find the satellite in the tree */
516          gtk_tree_model_foreach(model, uncheck_sat, sat_tree);
517      }
518  
519      /* set new value */
520      gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
521                         GTK_SAT_TREE_COL_SEL, toggle_item, -1);
522  
523      gtk_tree_path_free(path);
524  }
525  
526  /**
527   * Select a satellite in the GtkSatTree.
528   *
529   * @param sat_tree The GtkSatTree widget.
530   * @param catnum Catalogue number of satellite to be selected.
531   */
532  void gtk_sat_tree_select(GtkSatTree * sat_tree, guint catnum)
533  {
534      /* sanity check */
535      if ((sat_tree == NULL) || !IS_GTK_SAT_TREE(sat_tree))
536      {
537  
538          sat_log_log(SAT_LOG_LEVEL_ERROR,
539                      _("%s: Invalid GtkSatTree!"), __func__);
540  
541          return;
542      }
543  
544      if (!g_slist_find(sat_tree->selection, GUINT_TO_POINTER(catnum)))
545      {
546  
547          GtkTreeModel   *model =
548              gtk_tree_view_get_model(GTK_TREE_VIEW(sat_tree->tree));
549  
550          /* we will need access to both the sat_tree and the catnum in the
551             foreach callback, so we attach catnum as data to the sat_tree
552           */
553          g_object_set_data(G_OBJECT(sat_tree), "tmp", GUINT_TO_POINTER(catnum));
554  
555          /* find the satellite in the tree */
556          gtk_tree_model_foreach(model, check_and_select_sat, sat_tree);
557  
558      }
559      else
560      {
561          /* else do nothing since the sat is already selected */
562          sat_log_log(SAT_LOG_LEVEL_INFO,
563                      _("%s: Satellite %d already selected; skip..."),
564                      __func__, catnum);
565      }
566  }
567  
568  /**
569   * Foreach callback for checking and selecting a satellite.
570   *
571   * @param model The GtkTreeModel.
572   * @param path The GtkTreePath of the current item.
573   * @param iter The GtkTreeIter of the current item.
574   * @param data Pointer to the GtkSatTree structure.
575   * @return Always FALSE to let the for-each run to till end.
576   *
577   * This function is used as foreach-callback in the gtk_sat_tree_select function.
578   * The purpoise of the function is to set the check box to chacked state and add
579   * the satellite in question to the selection list. The catalogue number of the
580   * satellite to be selected is attached as data to the GtkSatTree (key = tmp).
581   *
582   * The function is also used in the column_toggled callback function with the
583   * purpose of locating and selecting other instances of the satellite than the
584   * one, on which the user clicked on (meaning: some sats can be found in several
585   * TLE file and we want to chak them all, not just the clicked instance).
586   */
587  static gboolean check_and_select_sat(GtkTreeModel * model,
588                                       GtkTreePath * path,
589                                       GtkTreeIter * iter, gpointer data)
590  {
591      GtkSatTree     *sat_tree = GTK_SAT_TREE(data);
592      guint           cat1, cat2;
593  
594      (void)path;
595  
596      cat1 = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(data), "tmp"));
597      gtk_tree_model_get(model, iter, GTK_SAT_TREE_COL_CATNUM, &cat2, -1);
598  
599      if (cat1 == cat2)
600      {
601          /* we have a match */
602          gtk_tree_store_set(GTK_TREE_STORE(model), iter,
603                             GTK_SAT_TREE_COL_SEL, TRUE, -1);
604  
605          /* only append if sat not already in list */
606          if (!g_slist_find(sat_tree->selection, GUINT_TO_POINTER(cat1)))
607          {
608              sat_tree->selection = g_slist_append(sat_tree->selection,
609                                                   GUINT_TO_POINTER(cat1));
610              sat_log_log(SAT_LOG_LEVEL_DEBUG,
611                          _("%s:%d: Satellite %d selected."),
612                          __FILE__, __LINE__, cat1);
613          }
614          else
615          {
616              sat_log_log(SAT_LOG_LEVEL_INFO,
617                          _("%s:%d: Satellite %d already selected; skip..."),
618                          __FILE__, __LINE__, cat1);
619          }
620  
621          /* If we return TRUE here, the foreach would terminate.
622             We let it run to allow GtkSatTree to mark all instances
623             of sat the satellite (some sats may be present in two or
624             more .tle files.
625           */
626          //return TRUE;
627      }
628  
629      /* continue in order to catch ALL instances of sat */
630      return FALSE;
631  }
632  
633  /**
634   * Foreach callback for unchecking a satellite.
635   *
636   * @param model The GtkTreeModel.
637   * @param path The GtkTreePath of the current item.
638   * @param iter The GtkTreeIter of the current item.
639   * @param data Pointer to the GtkSatTree structure.
640   * @return Always FALSE to let the for-each run to till end.
641   *
642   * This function is very similar to the check_and_select callback except that it
643   * is used only to uncheck a deselected satellite.
644   */
645  static gboolean uncheck_sat(GtkTreeModel * model,
646                              GtkTreePath * path,
647                              GtkTreeIter * iter, gpointer data)
648  {
649      guint           cat1, cat2;
650  
651      (void)path;
652  
653      cat1 = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(data), "tmp"));
654      gtk_tree_model_get(model, iter, GTK_SAT_TREE_COL_CATNUM, &cat2, -1);
655  
656      if (cat1 == cat2)
657      {
658          /* we have a match */
659          gtk_tree_store_set(GTK_TREE_STORE(model), iter,
660                             GTK_SAT_TREE_COL_SEL, FALSE, -1);
661      }
662  
663      /* continue in order to catch ALL instances of sat */
664      return FALSE;
665  }
666  
667  /**
668   * Get list of selected satellites.
669   *
670   * @param sat_tree The GtkSatTree
671   * @param size Return location for number of selected sats.
672   * @return A newly allocated array containing the selected satellites or
673   *         NULL if no satellites are selected.
674   *
675   * The returned array should be g_freed when no longer needed.
676   */
677  guint          *gtk_sat_tree_get_selected(GtkSatTree * sat_tree, gsize * size)
678  {
679      guint           i;
680      gsize           s;
681      guint          *ret;
682  
683      /* sanity check */
684      if ((sat_tree == NULL) || !IS_GTK_SAT_TREE(sat_tree))
685      {
686  
687          sat_log_log(SAT_LOG_LEVEL_ERROR,
688                      _("%s: Invalid GtkSatTree!"), __func__);
689  
690          return NULL;
691      }
692  
693      /* parameter are ok */
694      s = g_slist_length(sat_tree->selection);
695  
696      if (s < 1)
697      {
698          sat_log_log(SAT_LOG_LEVEL_DEBUG,
699                      _("%s: There are no satellites selected => NULL."),
700                      __func__);
701  
702          *size = 0;
703  
704          return NULL;
705      }
706  
707      ret = (guint *) g_try_malloc(s * sizeof(guint));
708  
709      for (i = 0; i < s; i++)
710      {
711          ret[i] = GPOINTER_TO_UINT(g_slist_nth_data(sat_tree->selection, i));
712      }
713  
714      if (size != NULL)
715          *size = s;
716  
717      return ret;
718  }
719  
720  /**
721   * Compare two rows of the GtkSatTree.
722   *
723   * @param model The tree model of the GtkSatTree.
724   * @param a The first row.
725   * @param b The second row.
726   * @param userdata Not used.
727   *
728   * This function is used by the sorting algorithm to compare two rows of the
729   * GtkSatTree widget. The unctions works by comparing the character strings
730   * in the name column.
731   */
732  static gint compare_func(GtkTreeModel * model,
733                           GtkTreeIter * a, GtkTreeIter * b, gpointer userdata)
734  {
735      gchar          *sat1, *sat2;
736      gint            ret = 0;
737  
738      (void)userdata;
739  
740      gtk_tree_model_get(model, a, GTK_SAT_TREE_COL_NAME, &sat1, -1);
741      gtk_tree_model_get(model, b, GTK_SAT_TREE_COL_NAME, &sat2, -1);
742  
743      ret = g_ascii_strcasecmp(sat1, sat2);
744  
745      g_free(sat1);
746      g_free(sat2);
747  
748      return ret;
749  }
750  
751  /**
752   * Expand all nodes in the GtkSatTree.
753   *
754   * @param button The GtkButton that received the signal.
755   * @param tree Pointer to the GtkSatTree widget.
756   *
757   * This function expands all rows in the tree view in order to make it
758   * searchable.
759   */
760  static void expand_cb(GtkWidget * button, gpointer tree)
761  {
762      (void)button;
763  
764      gtk_tree_view_expand_all(GTK_TREE_VIEW(GTK_SAT_TREE(tree)->tree));
765  }
766  
767  /**
768   * Collapse all nodes in the GtkSatTree.
769   *
770   * @param button The GtkButton that received the signal.
771   * @param tree Pointer to the GtkSatTree widget.
772   *
773   * This function collapses all rows in the tree view.
774   */
775  static void collapse_cb(GtkWidget * button, gpointer tree)
776  {
777      (void)button;
778  
779      gtk_tree_view_collapse_all(GTK_TREE_VIEW(GTK_SAT_TREE(tree)->tree));
780  }