/* NessusClient
 * Copyright (C) 2005 Renaud Deraison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * In addition, as a special exception, Renaud Deraison
 * gives permission to link the code of this program with any
 * version of the OpenSSL library which is distributed under a
 * license identical to that listed in the included COPYING.OpenSSL
 * file, 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.
 */

/**
 * \file
 * This module defines a list notebook, that is a notebook whose pages
 * are controlled by a list box.  The gtk widget that represents such a
 * listnotebook is a paned window with a tree view showing the list of
 * pages and a notebook.
 */

#ifdef USE_GTK
#include <gtk/gtk.h>

#include <includes.h>
#include "listnotebook.h"
#include "readonly.h"

enum {
  COL_PAGENAME,
  COL_PAGENUM,
  COL_ICON,
  NUM_COLS
};


/**
 * @brief When a selection in the tree-view (~left) was done, changes the shown
 *        page (~right) accordingly.
 *
 * Selection func for the tree view.  Change the notebook page according
 * to the new selection in the tree view
 */
static gboolean
selection_func (GtkTreeSelection *selection, GtkTreeModel *model,
                GtkTreePath *path, gboolean path_currently_selected,
                gpointer userdata)
{
  if (!path_currently_selected) {
    GtkTreeIter iter;
    GtkWidget *optionsnb = GTK_WIDGET(userdata);
    gint pagenr;

    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, COL_PAGENUM, &pagenr, -1);

    gtk_notebook_set_current_page(GTK_NOTEBOOK(optionsnb), pagenr);
  }
  return TRUE; /* allow selection state to change */
}


/**
 * @brief Change the selection of the list to match the notebook's current page
 */
static void
notebook_page_switched (GtkNotebook *notebook, GtkNotebookPage *page,
                        guint page_num, gpointer user_data)
{
  GtkTreeView *list = GTK_TREE_VIEW(user_data);
  GtkTreeModel *model = gtk_tree_view_get_model(list);
  GtkTreePath *store_path = gtk_tree_path_new();
  GtkTreePath *sorted_path = NULL;
  GtkTreePath *current_path = NULL;

  /* Convert the page number to a path in the sorted model.  We can
   * derive a path pointing to the row in the list store directly from
   * the page number because the page number is the same as the index of
   * the row.  This assumes that all pages are added with
   * listnotebook_add_page. */
  gtk_tree_path_append_index(store_path, page_num);
  sorted_path = gtk_tree_model_sort_convert_child_path_to_path(
    GTK_TREE_MODEL_SORT(model), store_path);

  /* If the list is empty, sorted_path will be NULL because the
   * store_path we created above is invalid */
  if (sorted_path != NULL)
  {
    /* To avoid recursion we only change the selection if either no row is
     * currently selected or the current selection doesn't match the
     * current notebook page. */
    gtk_tree_view_get_cursor(list, &current_path, NULL);
    if (current_path == NULL ||
	gtk_tree_path_compare(sorted_path, current_path) != 0)
      gtk_tree_view_set_cursor(list, sorted_path, NULL, FALSE);
  }

  if (sorted_path != NULL)
    gtk_tree_path_free(sorted_path);
  if (current_path != NULL)
    gtk_tree_path_free(current_path);
}


/**
 * @brief Creates a listnotebook.
 *
 * If horizontal is TRUE, use a horizontal paned window, otherwise a
 * vertical one.
 * 
 * @param horizontal If TRUE, window is horizonzal paned, otherwise vertical.
 * @param sorted if TRUE, entries will be sorted.
 * @return A custom listnotebook GtkWidget.
 */
GtkWidget *
listnotebook_new (gboolean horizontal, gboolean sorted)
{
  GtkWidget *paned;
  GtkWidget *notebook;
  GtkWidget *list;
  GtkWidget *scrolled;
  GtkListStore *store;
  GtkTreeModel *model;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GtkTreeSelection *selection;

  if (horizontal)
    paned = gtk_hpaned_new();
  else
    paned = gtk_vpaned_new();

  /* The list that will be used to select the notebook page. */
  store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_INT,
      GDK_TYPE_PIXBUF);

  /* Wrap store in a sorted model and optionally sort it. */
  model = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(store));
  if (sorted)
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
	COL_PAGENAME, GTK_SORT_ASCENDING);

  scrolled = gtk_scrolled_window_new(NULL, NULL);
  read_only_set_active(scrolled);
  /* In a horizontal listnotebook, we don't want horizontal scrollbars
   * in the list of pages.  If we allow horizontal scrollbars the
   * initial size is much too narrow */
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
      horizontal ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC,
      GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
      GTK_SHADOW_IN);
  gtk_paned_pack1 (GTK_PANED(paned), scrolled, TRUE, TRUE);
  gtk_widget_show(scrolled);

  list = gtk_tree_view_new_with_model(model);
  gtk_widget_show(list);
  gtk_container_add(GTK_CONTAINER(scrolled), list);

  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
  column = gtk_tree_view_column_new();
  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(column, renderer, FALSE);
  gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", COL_ICON);
  renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, renderer, "text", COL_PAGENAME);

  /* Set up a notebook in the box */
  notebook = gtk_notebook_new();
  read_only_set_recurse(notebook);
  gtk_paned_pack2 (GTK_PANED(paned), notebook, TRUE, TRUE);
  gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
  gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
  gtk_widget_show(notebook);

  /* Hook up the list and notebook so that clicking into the list
   * changes the notebook page and that the selection in the list always
   * matches the notebook even if the notebook page is switched by other
   * means  */
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
  gtk_tree_selection_set_select_function(selection, selection_func, notebook,
      NULL);
  g_signal_connect (G_OBJECT(notebook), "switch-page",
                    G_CALLBACK(notebook_page_switched), list);

  /* Add the notebook, tree view and store as data to the box so that we
   * can easily access them later */
  g_object_set_data(G_OBJECT(paned), "notebook", notebook);
  g_object_set_data(G_OBJECT(paned), "treeview", list);
  g_object_set_data(G_OBJECT(paned), "liststore", store);

  return paned;
}

/**
 * @brief Removes a page from the listnotebook.
 * 
 * @param listnotebook Affected listnotebook.
 * @param page_name    Name of the page.
 */
void
listnotebook_remove_page (GtkWidget* listnotebook, char* page_name)
{
  GtkTreeModel *model = NULL;
  GtkNotebook *notebook = NULL;
  GtkTreeIter iter;
  gboolean valid = FALSE;
  int counter = 0;

  model = GTK_TREE_MODEL (g_object_get_data (G_OBJECT(listnotebook), "liststore") );
  notebook = GTK_NOTEBOOK (g_object_get_data (G_OBJECT(listnotebook), "notebook"));
  valid = gtk_tree_model_get_iter_first (model, &iter);

  // Find item in the liststore with name page_name, remove page from notebook
  // and the item from the list.
  while (valid)
    {
      gchar *str_data;
      gtk_tree_model_get (model, &iter, 0, &str_data, -1);
      if (str_data && !strcmp(str_data, page_name) )
        {
          gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
          gtk_notebook_remove_page (notebook, counter);
          g_free (str_data);
          break;
        }
      g_free (str_data);
      counter++;
      valid = gtk_tree_model_iter_next (model, &iter);
    }
}

/**
 * @brief Add a page to the listnotebook in the box.
 *
 * The parameter page should be the widget that makes up the new page.
 * It will be added to the notebook and the list store.  The parameter
 * title is the title of the page used in the list.  The stock-id, if
 * not NULL, should be the name of the icon to use.
 * 
 * @param listnotebook The listnotebook to add a page to.
 * @param page         GtkWidget that makes up the new page.
 * @param title        The pages title.
 * @param stock_id     Name of the icon to use or NULL.
 */
void
listnotebook_add_page (GtkWidget *listnotebook, GtkWidget *page,
                       const char *title, const char *stock_id)
{
  GtkWidget *notebook = g_object_get_data(G_OBJECT(listnotebook), "notebook");
  GtkListStore *store = g_object_get_data(G_OBJECT(listnotebook), "liststore");
  int pagenum = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL);
  GtkTreeIter iter;
  GdkPixbuf *icon = NULL;

  if (stock_id != NULL)
    icon = gtk_widget_render_icon (notebook, stock_id,
                                   GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);

  gtk_widget_show(page);
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, NULL);

  gtk_list_store_append(store, &iter);
  gtk_list_store_set (store, &iter, COL_ICON, icon,
                      COL_PAGENAME, title, COL_PAGENUM, pagenum, -1);
  
  if (icon != NULL)
    g_object_unref(G_OBJECT(icon));
}


/**
 * @brief Select a specific (named) notebook page.
 * 
 * @param page_name Name of the page in the list- view (~left). If no page with
 *                  that name is found (or it is NULL) the first page will be
 *                  selected.
 */
void
listnotebook_select_page_named (GtkWidget* listnotebook, char* page_name)
{
  GtkTreeView *list =  g_object_get_data(G_OBJECT(listnotebook), "treeview");
  GtkTreeModel *model = gtk_tree_view_get_model(list);
  GtkTreeIter iter;
  gboolean done = FALSE;

  if (gtk_tree_model_get_iter_first(model, &iter))
  {
    do
      {
        gchar* str_data;
        gtk_tree_model_get (model, &iter, 0, &str_data, -1);
        if (!strcmp(str_data, page_name))
          {
            GtkTreeSelection *selection = gtk_tree_view_get_selection(list);
            gtk_tree_selection_select_iter(selection, &iter);
            done = TRUE;
          }
      } while (gtk_tree_model_iter_next (model, &iter) && done == FALSE);
  }
}

/**
 * @brief Returns the string of the item selected in the list (~left).
 * 
 * Returns NULL if none is selected.
 * 
 * @param listnotebook The listnotebook in whose selection you have interest.
 * 
 * @return The selected text entry in the list (~left) or NULL if none is
 *         selected or an error occurred (has to be freed).
 */
char*
listnotebook_get_selected (GtkWidget* listnotebook)
{
  GtkTreeView* list   = NULL;
  GtkTreeModel* model = NULL;

  if (listnotebook == NULL)
    return NULL;

  GtkTreeIter iter;
  list = g_object_get_data (G_OBJECT(listnotebook), "treeview");
  GtkTreeSelection *selection = gtk_tree_view_get_selection(list);
  model = gtk_tree_view_get_model(list);

  // If a selection could be found, return its string data
  if (gtk_tree_selection_get_selected (selection, &model, &iter) == TRUE)
    {
      gchar* str_data;
      gtk_tree_model_get (model, &iter, 0, &str_data, -1);
      return str_data;
    }

  return NULL;
}


/**
 * @brief Select a specific (the nth) notebook page.
 * 
 * @param page_num The index in the (optionallly sorted) list (0 is the page 
 *                 listed first).
 */
void
listnotebook_select_page (GtkWidget*listnotebook, int page_num)
{
  GtkTreeView *list   = g_object_get_data(G_OBJECT(listnotebook), "treeview");
  GtkTreeModel *model = gtk_tree_view_get_model(list);
  GtkTreeIter iter;

  if (gtk_tree_model_iter_nth_child(model, &iter, NULL, page_num))
  {
    GtkTreeSelection *selection = gtk_tree_view_get_selection(list);
    gtk_tree_selection_select_iter(selection, &iter);
  }
}

#endif /* USE_GTK */
