/* $Id: prefs_scope_tree.c,v 1.17 2007-03-01 22:58:29 jan Exp $
 *
 * Copyright (C) 2004 by Intevation GmbH
 * Author(s):
 * Thomas Arendsen Hein <thomas@intevation.de>
 *
 * This program is free software under the GNU GPL (>=v2)
 * Read the file COPYING coming with the software for details.
 *
 * In addition, as a special exception, Intevation GmbH gives
 * permission to link the code of this program with the OpenSSL
 * library (or with modified versions of OpenSSL that use the same
 * license as OpenSSL), and distribute linked combinations including
 * the two. You must obey the GNU General Public License in all
 * respects for all of the code used other than OpenSSL. If you
 * modify this file, you may extend this exception to your version
 * of the file, but you are not obligated to do so. If you do not
 * wish to do so, delete this exception statement from your version.
 */

#include <includes.h>
#include "nessus_i18n.h"

#ifdef USE_GTK
#include <gtk/gtk.h>
#include "globals.h"
#include "nessus.h"
#include "context.h"
#include "preferences.h"
#include "prefs_context.h"
#include "prefs_dialog.h"
#include "prefs_dialog_auth.h"
#include "prefs_report.h"
#include "prefs_scope_tree.h"
#include "severity_filter.h"
#include "error_dlg.h"
#include "report.h"
#include "report_utils.h"
#include "backend.h"
#include "treeview_support.h"

/**
 * @file
 * Handle the scope- tree (in GUI the area to the left where Global Settings,
 * taks and scopes are organized in a tree).
 * Scopes and Global settings have an icon, showing its connection state.
 * The scope- tree also acts as a table, allowing an overview over how many
 * security notes, warnings, holes and false positives were reported in a
 * "report"- scope.
 * 
 * The counted numbers for warnings, holes etc are stored in a separate .cnt
 * file to avoid recounting.
 * The cnt file is placed next to the location of the .nbe backend file (usually
 * in the contexts directory).
 */

/* temporary, should be part of the global context (from severity_filter.c) */
extern severity_filter_t * global_filter;

/**
 * @brief Indices of the Scope- treeview.
 *
 * Will be shown in the tree of scopes.
 */
enum
{
  COL_CONTEXT = 0,
  COL_NAME,  /**< Name of the task/scope/report. */
  COL_NOTE,  /**< Nr. of "security notes" reported. */
  COL_WARN,  /**< Nr. of warnings reported. */
  COL_HOLE,  /**< Nr. of holes reported. */
  COL_FALSE, /**< Nr. of false positives reported. */
  COL_LOG,   /**< Nr. of log messages received. */
  COL_EDITABLE,
  NUM_COLS
};

void
scopetree_save_treerowref (struct context* context, GtkTreeModel *model,
                           GtkTreeIter iter)
{
  GtkTreePath *path;

  gtk_tree_row_reference_free(context->treerowref);
  path = gtk_tree_model_get_path(model, &iter);
  context->treerowref = gtk_tree_row_reference_new(model, path);
}


void
scope_move_menuitem_enable (struct context *context, gboolean enable)
{
  context = context_by_type(context, CONTEXT_TASK);
  if (context && context->move_menuitem && GTK_IS_WIDGET(context->move_menuitem))
      gtk_widget_set_sensitive(context->move_menuitem, enable);
}

/**
 * @brief Moves a context under a new parent.
 */
void
scopetree_move (struct context* context, struct context* new_parent)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(scopetree));
  GtkTreeRowReference *old_treerowref;
  GtkTreePath *path;
  GtkTreeIter iter, parent;
  struct context *next;
  const char *copy_name;
  int copy_note, copy_warn, copy_hole, copy_false, copy_log;
  gboolean copy_editable;

  /* Read old context */
  old_treerowref = context->treerowref;
  context->treerowref = NULL;
  path = gtk_tree_row_reference_get_path(old_treerowref);
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_path_free(path);
  gtk_tree_model_get (model, &iter,
                      COL_NAME, &copy_name,
                      COL_HOLE, &copy_hole,
                      COL_WARN, &copy_warn,
                      COL_NOTE, &copy_note,
                      COL_FALSE, &copy_false,
                      COL_LOG, &copy_log,
                      COL_EDITABLE, &copy_editable,
                      -1);

  /* Create the target context */
  path = gtk_tree_row_reference_get_path(new_parent->treerowref);
  gtk_tree_model_get_iter(model, &parent, path);
  gtk_tree_path_free(path);
  gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &parent);
  gtk_tree_store_set (GTK_TREE_STORE(model), &iter,
                      COL_CONTEXT, context,
                      COL_NAME, copy_name,
                      COL_HOLE, copy_hole,
                      COL_WARN, copy_warn,
                      COL_NOTE, copy_note,
                      COL_FALSE, copy_false,
                      COL_LOG, &copy_log,
                      COL_EDITABLE, copy_editable,
                      -1);
  scopetree_save_treerowref(context, model, iter);

  /* Move children */
  next = context->children;
  while(next)
  {
    scopetree_move(next, context);
    next = next->next;
  }

  /* Remove old context */
  path = gtk_tree_row_reference_get_path(old_treerowref);
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_path_free(path);
  gtk_tree_row_reference_free(old_treerowref);
  gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
}

void
scope_menu_moveto (GtkMenuItem *menuitem, struct context *context)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  struct context *scope = context_by_type(Context, CONTEXT_SCOPE);
  GtkTreePath *parentpath;

  prefs_context_update(scope);
  scope_move_menuitem_enable(scope, TRUE);

  if(context_move(scope, context))
    scopetree_move(scope, context);

  parentpath = gtk_tree_row_reference_get_path(context->treerowref);
  gtk_tree_view_expand_row(GTK_TREE_VIEW(scopetree), parentpath, FALSE);
  gtk_tree_path_free(parentpath);
  prefs_context_update(scope);
}

void
scopetree_move_menuitem_add (struct context *context)
{
  GtkWidget *menu = arg_get_value(MainDialog, "MOVESCOPE_SUBMENU");
  GtkWidget *menuitem;

  menuitem = gtk_menu_item_new_with_label(prefs_get_string(context, "name"));
  gtk_widget_show(menuitem);
  gtk_container_add(GTK_CONTAINER(menu), menuitem);
  g_signal_connect(G_OBJECT(menuitem), "activate",
      G_CALLBACK(scope_menu_moveto), context);

  context->move_menuitem = menuitem;
}

void
scopetree_rename (context_type type)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreePath *path;
  GtkTreeViewColumn *column;
  struct context *context = context_by_type(Context, type);

  if(!context)
  {
    show_error(_("scopetree_rename() called with illegal type"));
    return;
  }
  prefs_context_update(context);

  column = gtk_tree_view_get_column(GTK_TREE_VIEW(scopetree), 0);
  if(!column->editable_widget) /* avoid a bug in GTK+ 2.0.2 */
  {
    gtk_tree_view_get_cursor(GTK_TREE_VIEW(scopetree), &path, NULL);
    gtk_tree_view_set_cursor(GTK_TREE_VIEW(scopetree), path, column, TRUE);
    gtk_widget_grab_focus(scopetree);
    gtk_tree_path_free(path);
  }
}

void
task_menu_rename (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_rename(CONTEXT_TASK);
}

void
scope_menu_rename (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_rename(CONTEXT_SCOPE);
}

void
report_menu_rename (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_rename(CONTEXT_REPORT);
}


struct context *
scopetree_new (context_type type, const char* name, const char* filename)
{
  return scopetree_new_with_parent(NULL, type, name, filename);
}


struct context *
scopetree_new_with_parent (struct context* parentscope, context_type type,
                           const char *name, const char *filename)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(scopetree));
  GtkTreePath *path, *parentpath = NULL;
  GtkTreeViewColumn *column;
  GtkTreeIter iter, parent;
  struct context *context;

  switch(type)
  {
    case CONTEXT_TASK:
      context = Global;
      break;
    case CONTEXT_SCOPE:
    case CONTEXT_REPORT:
      if (parentscope != NULL)
      {
	if (parentscope->type >= type)
	{
	  show_error(_("scopetree_new_with_parent(): parent type >= child type"));
	  return NULL;
	}
	context = parentscope;
	parentpath = gtk_tree_row_reference_get_path(context->treerowref);
	gtk_tree_model_get_iter(model, &parent, parentpath);
      }
      else
      {
	gtk_tree_view_get_cursor(GTK_TREE_VIEW(scopetree), &parentpath,
	    &column);
	while(gtk_tree_path_get_depth(parentpath) >= type)
	  gtk_tree_path_up(parentpath);

	gtk_tree_model_get_iter(model, &parent, parentpath);
	gtk_tree_model_get(model, &parent, COL_CONTEXT, &context, -1);
      }
      break;
    default:
      show_error(_("context_rename() called with illegal type"));
      return NULL;
  }

  context = context_new(context, name, filename);
  if(context)
  {
    if(type == CONTEXT_TASK)
    {
      gtk_tree_store_append(GTK_TREE_STORE(model), &iter, NULL);
      scopetree_move_menuitem_add(context);
    }
    else
      gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &parent);
    gtk_tree_store_set (GTK_TREE_STORE(model), &iter,
                        COL_CONTEXT, context,
                        COL_NAME, prefs_get_string(context, "name"),
                        COL_HOLE, -1, COL_WARN, -1, COL_NOTE, -1, COL_FALSE, -1,
                        COL_LOG, -1,
                        COL_EDITABLE, TRUE, -1);
    scopetree_save_treerowref(context, model, iter);

    if(parentpath)
      gtk_tree_view_expand_row(GTK_TREE_VIEW(scopetree), parentpath, FALSE);

    path = gtk_tree_model_get_path(model, &iter);
    column = gtk_tree_view_get_column(GTK_TREE_VIEW(scopetree), 0);

    if(!column->editable_widget) /* avoid a bug in GTK+ 2.0.2 */
    {
      gtk_tree_view_set_cursor(GTK_TREE_VIEW(scopetree), path, column, !name);
      gtk_widget_grab_focus(scopetree);
    }
    gtk_tree_path_free(path);
  }
  if(parentpath)
    gtk_tree_path_free(parentpath);

  return context;
}

void
task_menu_new(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_new(CONTEXT_TASK, NULL, NULL);
}

void
scope_menu_new(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_new(CONTEXT_SCOPE, NULL, NULL);
}

void
scopetree_context_new(button, user_data)
  GtkWidget *button;
  gpointer user_data;
{
  if(Context->type >= CONTEXT_TASK)
    scopetree_new(CONTEXT_SCOPE, NULL, NULL);
  else
    scopetree_new(CONTEXT_TASK, NULL, NULL);
}


/**
 * Delete menu widgets, tree row references and tree rows for a subtree.
 */
static void
scopetree_delete_recurse (struct context *context)
{
  struct context *child = context->children;
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(scopetree));
  GtkTreePath *path;
  GtkTreeIter iter;

  while(child)
  {
    scopetree_delete_recurse(child);
    child = child->next;
  }
  if(context->move_menuitem)
    gtk_widget_destroy(context->move_menuitem);
  path = gtk_tree_row_reference_get_path(context->treerowref);
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_path_free(path);
  gtk_tree_row_reference_free(context->treerowref);
  gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
}

static void
scopetree_delete (context_type type)
{
  GtkWindow *window = GTK_WINDOW(arg_get_value(MainDialog, "WINDOW"));
  GtkWidget *dialog;
  const char *question;
  struct context *context = context_by_type(Context, type);

  prefs_context_update(context);

  switch(type)
  {
    case CONTEXT_TASK:
      question = _("Really delete task\n `%s'\nand all scopes and reports in it?");
      break;
    case CONTEXT_SCOPE:
      question = _("Really delete scope\n `%s'\nall reports in it?");
      break;
    case CONTEXT_REPORT:
      question = _("Really delete report\n `%s'?");
      break;
    default:
      show_error(_("scopetree_delete() called with illegal type"));
      return;
  }

  dialog = gtk_message_dialog_new(window,
      GTK_DIALOG_DESTROY_WITH_PARENT,
      GTK_MESSAGE_QUESTION,
      GTK_BUTTONS_OK_CANCEL,
      question, prefs_get_string(context, "name"));

  arg_set_value(MainDialog, "CONTEXT", -1, dialog);
  switch (gtk_dialog_run(GTK_DIALOG(dialog)))
  {
    case GTK_RESPONSE_OK:
      scopetree_delete_recurse(context);
      if(context->next)
	prefs_context_update(context->next);
      else if(context->parent->children == context)
	prefs_context_update(context->parent);
      else
      {
	struct context *sibling = context->parent->children;

	while(sibling->next != context)
	  sibling = sibling->next;
	prefs_context_update(sibling);
      }
      context_delete(context);
      break;
    default:
      break;
  }
  gtk_widget_destroy(dialog);
  arg_set_value(MainDialog, "CONTEXT", -1, window);
}


/**
 * @param user_data ignored (callback).
 */
void
task_menu_delete (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_delete(CONTEXT_TASK);
}

/**
 * @param user_data ignored (callback).
 */
void
scope_menu_delete (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_delete(CONTEXT_SCOPE);
}

/**
 * @param user_data ignored (callback).
 */
void
report_menu_delete (GtkMenuItem *menuitem, gpointer user_data)
{
  scopetree_delete(CONTEXT_REPORT);
}

/**
 * @param user_data ignored (callback).
 */
void
scopetree_context_delete (GtkWidget *button, gpointer user_data)
{
  if(Context->type == CONTEXT_TASK)
    scopetree_delete(CONTEXT_TASK);
  else if(Context->type == CONTEXT_SCOPE)
    scopetree_delete(CONTEXT_SCOPE);
  else if(Context->type >= CONTEXT_REPORT)
    scopetree_delete(CONTEXT_REPORT);
  else
    show_error(_("scopetree_context_delete() called with illegal type"));
}

/**
 * @param user_data ignored (callback).
 */
static void
scope_menu_open_ok (GtkWidget *widget, gpointer user_data)
{
  GtkWidget *dialog = GTK_WIDGET(user_data);
  gchar *filename;

  filename = g_strdup(
      gtk_file_selection_get_filename(GTK_FILE_SELECTION(dialog)));
  gtk_widget_destroy(dialog);

  if(check_is_dir(filename))
    show_error(_("Please choose a target filename."));
  else if(!check_is_file(filename))
    show_error(_("File \"%s\" doesn't exist."), filename);
  else
    scopetree_new(CONTEXT_SCOPE, NULL, filename);

  g_free(filename);
}

/**
 * @param user_data ignored (callback).
 */
void
scope_menu_open (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWindow *parent = arg_get_value(MainDialog, "CONTEXT");
  GtkWidget *dialog = gtk_file_selection_new(
      _("Open configuration for new scope"));

  if(parent)
    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
  prefs_context_update(context_by_type(Context, CONTEXT_TASK));
  g_signal_connect(GTK_FILE_SELECTION(dialog)->ok_button,
      "clicked", G_CALLBACK(scope_menu_open_ok), dialog);
  g_signal_connect_swapped(GTK_FILE_SELECTION(dialog)->cancel_button,
      "clicked", G_CALLBACK(gtk_widget_destroy), dialog);
  gtk_widget_show(dialog);
}


static void
scope_menu_save_ok (GtkWidget *widget, gpointer user_data)
{
  GtkWidget *dialog = GTK_WIDGET(user_data);
  gchar *filename;

  filename = g_strdup(
      gtk_file_selection_get_filename(GTK_FILE_SELECTION(dialog)));
  gtk_widget_destroy(dialog);

  if(check_is_dir(filename))
    show_error(_("Please choose a target filename."));
  else if(check_exists(filename))
    show_error(_("File \"%s\" already exists."), filename);
  else
    preferences_save_as(Context, filename);

  g_free(filename);
}

void
scope_menu_save (GtkMenuItem* menuitem, gpointer user_data)
{
  GtkWindow *parent = arg_get_value(MainDialog, "CONTEXT");
  GtkWidget *dialog = gtk_file_selection_new(_("Save scope configuration"));

  if(parent)
    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
  prefs_context_update(context_by_type(Context, CONTEXT_SCOPE));
  gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog), "nessusrc");
  g_signal_connect(GTK_FILE_SELECTION(dialog)->ok_button,
      "clicked", G_CALLBACK(scope_menu_save_ok), dialog);
  g_signal_connect_swapped(GTK_FILE_SELECTION(dialog)->cancel_button,
      "clicked", G_CALLBACK(gtk_widget_destroy), dialog);
  gtk_widget_show(dialog);
}


/**
 * @brief Set table values for statistics (e.g. nr. of warnings) in the
 * @brief treestore. Values are read from or written to a .cnt file.
 * 
 * @param treestore Treestore to manipulate.
 * @param be        Backend index, will be converted (backend_convert) if file
 *                  does not exist.
 * @param fname     Path to .cnt file.
 */
void
scopetreestore_counters_update (GtkTreeStore* treestore, GtkTreeIter iter,
                                int be, char * fname)
{
  struct arglist *hosts;
  gchar* cached_counters = NULL;
  FILE * fp;
  int    highs = 0, warns = 0, notes = 0, falsepos = 0, logs = 0;

  /* Cache the results in a '.cnt' file to avoid recounting 
   * each .nbe file on startup */
  if (fname != NULL)
    {
      cached_counters = g_strdup_printf ("%s.cnt", fname);
      fp = fopen (cached_counters, "r");
    }
  else
    fp = NULL;

  if (fp == NULL)
  {
     hosts = backend_convert(be);
     highs = number_of_holes(hosts);
     warns = number_of_warnings(hosts);
     notes = number_of_notes(hosts);
     falsepos = number_of_false_positives(hosts);
     logs = number_of_log_messages(hosts);
     if(hosts) arg_free_all(hosts);
     if ( fname != NULL )
       fp = fopen (cached_counters, "w");
     else
	fp = NULL;

     if ( fp != NULL )
      {
       fprintf(fp, "%d %d %d %d %d\n", highs, warns, notes, falsepos, logs);
       fclose(fp);
      }
      else if ( fname != NULL ) perror("open ");
  }
  else
  {
    fscanf(fp, "%d %d %d %d %d", &highs, &warns, &notes, &falsepos, &logs);
    fclose(fp);
  }


  /* XXX: add these to scopes and tasks? */
  gtk_tree_store_set (treestore, &iter,
                      COL_NOTE, notes,
                      COL_WARN, warns,
                      COL_HOLE, highs,
                      COL_FALSE, falsepos,
                      COL_LOG, logs,
                      -1);
  if (cached_counters) g_free (cached_counters);
}


void
scopetreeview_counters_update (struct context *context, int be, char * fname)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(scopetree));
  GtkTreePath *path;
  GtkTreeIter iter;

  gtk_tree_view_get_cursor(GTK_TREE_VIEW(scopetree), &path, NULL);
  gtk_tree_model_get_iter(model, &iter, path);
  scopetreestore_counters_update(GTK_TREE_STORE(model), iter, be, fname);

  gtk_tree_path_free(path);
}


void
scopetreeview_connected_update (struct context *context)
{
  GtkWidget *scopetree = GTK_WIDGET(
      arg_get_value(arg_get_value(MainDialog, "SCOPETREE"), "TREEVIEW"));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(scopetree));
  GtkTreePath *path = gtk_tree_row_reference_get_path(context->treerowref);
  GtkTreeIter iter;

  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_model_row_changed(model, path, &iter);
  gtk_tree_path_free(path);
}


/**
 * @brief Set the pixbuf of the cell according to the state of the context or
 *        the row.
 *
 * @param data The tree-view widget.
 */
static void
connected_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
                     GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
  GtkWidget *widget = GTK_WIDGET(data);
  GdkPixbuf *icon = NULL;
  struct context *context;

  gtk_tree_model_get(model, iter, COL_CONTEXT, &context, -1);

  /* if the row is for a context that can have a connection, choose an
   * icon showing the connection status.  Otherwise icon stays NULL,
   * i.e. no icon at all.
   */
  if (context->type == CONTEXT_SCOPE || context->type == CONTEXT_GLOBAL)
    {
      icon = gtk_widget_render_icon (widget,
             context->socket < 0 ? "openvas-disconnect" : "openvas-connect",
             GTK_ICON_SIZE_MENU, NULL);
    }
  else if (context->type == CONTEXT_REPORT
           && context->is_severity_mapped == TRUE)
    {
      icon = gtk_widget_render_icon (widget, "gtk-convert", GTK_ICON_SIZE_MENU,
                                     NULL);
    }

  g_object_set (G_OBJECT(cell), "pixbuf", icon, NULL);

  if (icon != NULL)
    g_object_unref(icon);
}

static void
on_scope_edited(cellrenderertext, path_string, new_text, data)
  GtkCellRendererText *cellrenderertext;
  gchar *path_string;
  gchar *new_text;
  gpointer data;
{
  struct context *context;
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(data));
  GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
  GtkTreeIter iter;

  if(new_text && new_text[0])
  {
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_NAME, new_text, -1);
    gtk_tree_model_get(model, &iter, COL_CONTEXT, &context, -1);
    context_rename(context, new_text);
    if(context->move_menuitem)
    {
      GtkWidget *label = gtk_bin_get_child(GTK_BIN(context->move_menuitem));

      if(GTK_IS_LABEL(label))
	gtk_label_set_text(GTK_LABEL(label), prefs_get_string(context, "name"));
      else
	show_error(_("on_scope_edited(): menuitem has no label."));
    }
    prefs_context_update(context);
  }

  gtk_tree_path_free(path);
}

/**
 * @brief Implementation of a GtkTreeSelectionFunc().
 * 
 * Does autoconnect and auto-expand if the Client was configured to do so.
 * Sets the filter- state (is_severity_mapped) of the selected context according
 * to existance of global severity filter and the glabal_filter_active flag.
 * 
 * @return Always TRUE, because all rows are always selectable.
 */
static gboolean
scopetreeview_selection_func (GtkTreeSelection *selection, GtkTreeModel *model,
                              GtkTreePath *path, gboolean path_currently_selected,
                              gpointer userdata)
{
  GtkTreeIter iter;
  struct context *context;

  if (!path_currently_selected &&
      !gtk_tree_selection_get_selected(selection, NULL, NULL))
  {
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, COL_CONTEXT, &context, -1);
    if(prefs_get_int(Global, "tree_autoexpand"))
      gtk_tree_view_expand_row(gtk_tree_selection_get_tree_view(selection),
	  path, FALSE);
    prefs_context_update(context);
    if (prefs_get_int(Global, "nessusd_autoconnect") &&
	(context->type == CONTEXT_SCOPE))
    {
      prefs_dialog_auth_connection(context);
    }
  }

  return TRUE;			/* allow selection state to change */
}

/**
 * @brief Writes a number to a cell, number is fetched from tree_model.
 * 
 * Acts as cell_data_func for the columns that involve counting 
 * (e.g. COL_HOLES shows number of reported holes).
 * 
 * @param data In practice one of COL_HOLE, COL_WARN, COL_NOTE, COL_FALSE,
 *             used as column- index in  tree_model to receive the value.
 */
static void
cell_counts (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
             GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
{
  gint value;
  gchar *text;

  /* Get the int value from the model. */
  gtk_tree_model_get(tree_model, iter, GPOINTER_TO_SIZE(data), &value, -1);
  /* Now we can format the value ourselves. */
  if(value < 0)
    text = g_strdup("");
  else
    text = g_strdup_printf("%d", value);
  g_object_set(cell, "text", text, NULL);
  g_free(text);
}

/**
 * @brief Behave as if the currently selected report would have been freshly
 * @brief selected, update counters, icons etc.
 * 
 * Motivation for this function is to apply filters "on the fly" on the current
 * selected report.
 */
void
prefs_scope_tree_update_report ()
{
  /* If currently a Report is selected in the GUI,
    Update the scopeview, as we want the values and icon change immediately
    according to the severity_filter.
    Ideally that should work by just sending an update() signal to the
    scopetreeview. That did not work in practice, however.
    @TODO make this meachanism clean, document flow of events in real life GUI use.  */
  if (Context->type == CONTEXT_REPORT)
    {
      GtkWidget *scopetree = GTK_WIDGET (arg_get_value(arg_get_value(MainDialog,
                                                        "SCOPETREE"), "TREEVIEW"));
      GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW (scopetree));
      GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (scopetree));
      GtkTreeIter iter;

      // Get the selector, and the path to the selected node.
      gtk_tree_selection_get_selected (selection, NULL, &iter);
      GtkTreePath * path = gtk_tree_model_get_path (model, &iter);
      gtk_tree_selection_select_path (selection, path);
      // Manually call the selection function to update icon that indicates filtering
      scopetreeview_selection_func (selection, model, path, FALSE, NULL);

      char* fname = report_get_filename (Context);
      int be = backend_import_report (fname);
      // Manually call counter update
      scopetreestore_counters_update (GTK_TREE_STORE(model), iter, be, NULL);
      prefs_report_update (arg_get_value (MainDialog, "REPORT"), TRUE);
    }
}

/**
 * @brief Creates the columns of the scope- tree view.
 * 
 * @param view The scope-tree view.
 */
void
scopetreeview_create_columns (GtkWidget* view)
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;

  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title(column, _("Name"));
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new();
  g_signal_connect(G_OBJECT(renderer), "edited",
		   G_CALLBACK(on_scope_edited), view);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_NAME);
  gtk_tree_view_column_add_attribute(column, renderer, "editable",
				     COL_EDITABLE);

  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_set_cell_data_func(column, renderer, connected_data_func,
      view, NULL);

  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title(column, _("High"));
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new();
  g_object_set(renderer, "xalign", (gfloat) 1.0, "xpad", (guint) 8, NULL);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_HOLE);
  gtk_tree_view_column_set_cell_data_func(column, renderer,
					  cell_counts,
					  (gpointer) COL_HOLE, NULL);

  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title(column, _("Medium"));
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new();
  g_object_set(renderer, "xalign", (gfloat) 1.0, "xpad", (guint) 8, NULL);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_WARN);
  gtk_tree_view_column_set_cell_data_func(column, renderer,
					  cell_counts,
					  (gpointer) COL_WARN, NULL);

  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title(column, _("Low"));
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new();
  g_object_set(renderer, "xalign", (gfloat) 1.0, "xpad", (guint) 8, NULL);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_NOTE);
  gtk_tree_view_column_set_cell_data_func(column, renderer,
					  cell_counts,
					  (gpointer) COL_NOTE, NULL);

  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title(column, _("FP"));
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new();
  g_object_set(renderer, "xalign", (gfloat) 1.0, "xpad", (guint) 8, NULL);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_FALSE);
  gtk_tree_view_column_set_cell_data_func(column, renderer,
					  cell_counts,
					  (gpointer) COL_FALSE, NULL);
  
  column = gtk_tree_view_column_new ();
  gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN(column), 1);
  gtk_tree_view_column_set_title (column, _("Log"));
  gtk_tree_view_append_column (GTK_TREE_VIEW(view), column);
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (renderer, "xalign", (gfloat) 1.0, "xpad", (guint) 8, NULL);
  gtk_tree_view_column_pack_start (column, renderer, TRUE);
  gtk_tree_view_column_add_attribute (column, renderer, "text", COL_LOG);
  gtk_tree_view_column_set_cell_data_func (column, renderer, cell_counts,
                                           (gpointer) COL_LOG, NULL);
}

GtkTreeStore *
scope_create_treestore (struct context *context)
{
  GtkTreeStore *treestore;
  GtkTreeIter global, task, scope, report;
  /* TODO currently unused:
   * GtkTreeIter hostgroup, host; */
  struct context *tasks, *scopes, *reports;

  treestore = gtk_tree_store_new (NUM_COLS,
                                  G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_INT,
                                  G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
                                  G_TYPE_BOOLEAN);

  gtk_tree_store_append(treestore, &global, NULL);
  gtk_tree_store_set (treestore, &global,
                      COL_CONTEXT, context,
                      COL_NAME, prefs_get_string(context, "name"),
                      COL_HOLE, -1, COL_WARN, -1, COL_NOTE, -1, COL_FALSE, -1,
                      COL_LOG, -1,
                      COL_EDITABLE, FALSE, -1);
  scopetree_save_treerowref(context, GTK_TREE_MODEL(treestore), global);

  tasks = context->children;
  while(tasks)
  {
    gtk_tree_store_append(treestore, &task, NULL);
    gtk_tree_store_set(treestore, &task,
                       COL_CONTEXT, tasks,
                       COL_NAME, prefs_get_string(tasks, "name"),
                       COL_HOLE, -1, COL_WARN, -1, COL_NOTE, -1, COL_FALSE, -1,
                       COL_LOG, -1,
                       COL_EDITABLE, TRUE,
                       -1);
    scopetree_save_treerowref(tasks, GTK_TREE_MODEL(treestore), task);
    scopetree_move_menuitem_add(tasks);

    scopes = tasks->children;
    while(scopes)
    {
      gtk_tree_store_append(treestore, &scope, &task);
      gtk_tree_store_set (treestore, &scope,
                          COL_CONTEXT, scopes,
                          COL_NAME, prefs_get_string(scopes, "name"),
                          COL_HOLE, -1, COL_WARN, -1, COL_NOTE, -1, COL_FALSE, -1,
                          COL_LOG, -1,
                          COL_EDITABLE, TRUE, -1);
      scopetree_save_treerowref(scopes, GTK_TREE_MODEL(treestore), scope);

      reports = scopes->children;
      while(reports)
      {
	gtk_tree_store_append(treestore, &report, &scope);
        gtk_tree_store_set (treestore, &report,
                            COL_CONTEXT, reports,
                            COL_NAME, prefs_get_string(reports, "name"),
                            COL_HOLE, -1, COL_WARN, -1, COL_NOTE, -1, COL_FALSE, -1,
                            COL_LOG, -1,
                            COL_EDITABLE, TRUE, -1);
	scopetree_save_treerowref(reports, GTK_TREE_MODEL(treestore), report);

	{
	  /* TODO: move this to general counter handling */
	  char *fname;
	  int be;

	  fname = report_get_filename(reports);
	  be = backend_import_report(fname);
	  scopetreestore_counters_update(treestore, report, be, fname);
	  efree(&fname);
	  backend_dispose(be);
	}
	reports = reports->next;
      }
      scopes = scopes->next;
    }
    tasks = tasks->next;
  }

  return treestore;
}

/**
 * Returns an arglist with following keys (type, description):
 *  - FRAME (GtkScrolledWindow*, )
 *  - TREEVIEW (GtkTreeView*, )
 */
struct arglist *
prefs_dialog_scope_tree (struct context *context)
{
  GtkTreeModel *model;
  GtkTreeSelection *selection;
  GtkWidget *scrolledwindow;
  GtkWidget *scopetreeview;
  struct arglist *ctrls = emalloc(sizeof(struct arglist));

  scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
  gtk_widget_show(scrolledwindow);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),
				 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(scrolledwindow),
				    GTK_CORNER_TOP_RIGHT);
  arg_add_value(ctrls, "FRAME", ARG_PTR, -1, scrolledwindow);

  scopetreeview = gtk_tree_view_new();
  gtk_widget_show(scopetreeview);
  gtk_container_add(GTK_CONTAINER(scrolledwindow), scopetreeview);
  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(scopetreeview), TRUE);
  arg_add_value(ctrls, "TREEVIEW", ARG_PTR, -1, scopetreeview);

  g_signal_connect(G_OBJECT(scopetreeview), "key-press-event",
                   G_CALLBACK(onKeypressed), NULL);

  scopetreeview_create_columns(scopetreeview);

  model = GTK_TREE_MODEL(scope_create_treestore(context));
  gtk_tree_view_set_model(GTK_TREE_VIEW(scopetreeview), model);
  g_object_unref(model);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(scopetreeview));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
  gtk_tree_selection_set_select_function(selection,
					 scopetreeview_selection_func,
					 NULL, NULL);

  return ctrls;
}

#endif /* USE_GTK */
