/* $Id: prefs_scope_tree.c,v 1.10.2.4 2007/01/02 16:24:02 renaud 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_scope_tree.h"
#include "error_dialog.h"
#include "report.h"
#include "report_utils.h"
#include "backend.h"

enum
{
  COL_CONTEXT = 0,
  COL_NAME,
  COL_NOTE,
  COL_WARN,
  COL_HOLE,
  COL_EDITABLE,
  NUM_COLS
};

void
scopetree_save_treerowref(context, model, iter)
  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(context, 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);
}

void
scopetree_move(context, new_parent)
  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;
  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_NOTE, &copy_note, COL_WARN, &copy_warn, COL_HOLE, &copy_hole,
      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_NOTE, copy_note, COL_WARN, copy_warn, COL_HOLE, copy_hole,
      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(menuitem, context)
  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(context)
  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(type)
  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(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_rename(CONTEXT_TASK);
}

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

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


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


struct context *
scopetree_new_with_parent(parentscope, type, name, filename)
  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_NOTE, -1, COL_WARN, -1, COL_HOLE, -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.
 */
void
scopetree_delete_recurse(context)
  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);
}

void
scopetree_delete(type)
  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);
}


void
task_menu_delete(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_delete(CONTEXT_TASK);
}

void
scope_menu_delete(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_delete(CONTEXT_SCOPE);
}

void
report_menu_delete(menuitem, user_data)
  GtkMenuItem *menuitem;
  gpointer user_data;
{
  scopetree_delete(CONTEXT_REPORT);
}

void
scopetree_context_delete(button, user_data)
  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"));
}


void
scope_menu_open_ok(widget, user_data)
  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);
}

void
scope_menu_open(menuitem, user_data)
  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);
}


void
scope_menu_save_ok(widget, user_data)
  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(menuitem, user_data)
  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);
}


void
scopetreestore_counters_update(treestore, iter, be, fname)
  GtkTreeStore *treestore;
  GtkTreeIter iter;
  int be;
  char * fname;
{
  struct arglist *hosts;
  char cached_counters[1024];
  FILE * fp;
  int    h = 0, w = 0,  n = 0;

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

  if ( fp == NULL )
  {
     hosts = backend_convert(be);
     h = number_of_holes(hosts);
     w = number_of_warnings(hosts);
     n = number_of_notes(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\n", h, w, n);
       fclose(fp);
      }
      else if ( fname != NULL ) perror("open ");
  }
  else
  {
    fscanf(fp, "%d %d %d", &h, &w, &n);
    fclose(fp);
  }
   



  /* XXX: add these to scopes and tasks? */
  gtk_tree_store_set(treestore, &iter,
      COL_NOTE, n,
      COL_WARN, w,
      COL_HOLE, h,
      -1);

}

void
scopetreeview_counters_update(context, be,fname)
  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(context)
  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);
}

/* Set the pixbuf of the cell according to the state of the context or the row
 *
 * The data parameter should be the tree-view widget.
 */
static void
connected_data_func(tree_column, cell, model, iter, data)
  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 ? "nessus-disconnect" : "nessus-connect",
	GTK_ICON_SIZE_MENU, NULL);
  }

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

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

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);
}

gboolean
scopetreeview_selection_func(selection, model, path,
    path_currently_selected, userdata)
  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);
  }
  return TRUE;			/* allow selection state to change */
}

void
cell_counts(tree_column, cell, tree_model, iter, data)
  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, (gint) 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);
}

void
scopetreeview_create_columns(view)
  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, _("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, _("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, _("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);
}

GtkTreeStore *
scope_create_treestore(context)
  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_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_NOTE, -1, COL_WARN, -1, COL_HOLE, -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_NOTE, -1, COL_WARN, -1, COL_HOLE, -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_NOTE, -1, COL_WARN, -1, COL_HOLE, -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_NOTE, -1, COL_WARN, -1, COL_HOLE, -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;
}

struct arglist *
prefs_dialog_scope_tree(context)
  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);

  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 */
