/*
    This file is part of darktable,
    copyright (c) 2012 aldric renaudin.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable 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 darktable.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "bauhaus/bauhaus.h"
#include "common/darktable.h"
#include "common/debug.h"
#include "control/control.h"
#include "control/conf.h"
#include "common/styles.h"
#include "develop/develop.h"
#include "develop/masks.h"
#include "develop/imageop.h"
#include "libs/lib.h"
#include "gui/draw.h"
#include "gui/gtk.h"
#include "gui/styles.h"
#include "dtgtk/button.h"

DT_MODULE(1)

static void _lib_masks_recreate_list(dt_lib_module_t *self);
static void _lib_masks_update_list(dt_lib_module_t *self);

typedef struct dt_lib_masks_t
{
  /* vbox with managed history items */
  GtkWidget *hbox;
  GtkWidget *bt_circle, *bt_path, *bt_gradient, *bt_ellipse, *bt_brush;
  GtkWidget *treeview;
  GtkWidget *scroll_window;

  GdkPixbuf *ic_inverse, *ic_union, *ic_intersection, *ic_difference, *ic_exclusion, *ic_used;
  int gui_reset;
}
dt_lib_masks_t;


const char* name()
{
  return _("mask manager");
}

uint32_t views()
{
  return DT_VIEW_DARKROOM;
}

uint32_t container()
{
  return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
}

int position()
{
  return 10;
}

typedef enum dt_masks_tree_cols_t
{
  TREE_TEXT = 0,
  TREE_MODULE,
  TREE_GROUPID,
  TREE_FORMID,
  TREE_EDITABLE,
  TREE_IC_OP,
  TREE_IC_OP_VISIBLE,
  TREE_IC_INVERSE,
  TREE_IC_INVERSE_VISIBLE,
  TREE_IC_USED,
  TREE_IC_USED_VISIBLE,
  TREE_USED_TEXT,
  TREE_COUNT
}
dt_masks_tree_cols_t;

static void _tree_add_circle(GtkButton *button, dt_iop_module_t *module)
{
  //we create the new form
  dt_masks_form_t *spot = dt_masks_create(DT_MASKS_CIRCLE);
  dt_masks_change_form_gui(spot);
  darktable.develop->form_gui->creation = TRUE;
  darktable.develop->form_gui->creation_module = module;
  darktable.develop->form_gui->group_selected = 0;
  dt_control_queue_redraw_center();
}
static void _bt_add_circle (GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *module)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
  {
    //we unset the creation mode
    dt_masks_form_t *form = darktable.develop->form_visible;
    if (form) dt_masks_free_form(form);
    dt_masks_change_form_gui(NULL);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else _tree_add_circle(NULL,NULL);
}
static void _tree_add_ellipse(GtkButton *button, dt_iop_module_t *module)
{
  //we create the new form
  dt_masks_form_t *spot = dt_masks_create(DT_MASKS_ELLIPSE);
  dt_masks_change_form_gui(spot);
  darktable.develop->form_gui->creation = TRUE;
  darktable.develop->form_gui->creation_module = module;
  darktable.develop->form_gui->group_selected = 0;
  dt_control_queue_redraw_center();
}
static void _bt_add_ellipse (GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *module)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
  {
    //we unset the creation mode
    dt_masks_form_t *form = darktable.develop->form_visible;
    if (form) dt_masks_free_form(form);
    dt_masks_change_form_gui(NULL);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else _tree_add_ellipse(NULL,NULL);
}

static void _tree_add_path(GtkButton *button, dt_iop_module_t *module)
{
  //we create the new form
  dt_masks_form_t *spot = dt_masks_create(DT_MASKS_PATH);
  dt_masks_change_form_gui(spot);
  darktable.develop->form_gui->creation = TRUE;
  darktable.develop->form_gui->creation_module = module;
  darktable.develop->form_gui->group_selected = 0;
  dt_control_queue_redraw_center();
}
static void _bt_add_path (GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *module)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
  {
    //we unset the creation mode
    dt_masks_form_t *form = darktable.develop->form_visible;
    if (form) dt_masks_free_form(form);
    dt_masks_change_form_gui(NULL);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else _tree_add_path(NULL,NULL);
}
static void _tree_add_gradient(GtkButton *button, dt_iop_module_t *module)
{
  //we create the new form
  dt_masks_form_t *spot = dt_masks_create(DT_MASKS_GRADIENT);
  dt_masks_change_form_gui(spot);
  darktable.develop->form_gui->creation = TRUE;
  darktable.develop->form_gui->creation_module = module;
  darktable.develop->form_gui->group_selected = 0;
  dt_control_queue_redraw_center();
}
static void _bt_add_gradient (GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *module)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
  {
    //we unset the creation mode
    dt_masks_form_t *form = darktable.develop->form_visible;
    if (form) dt_masks_free_form(form);
    dt_masks_change_form_gui(NULL);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else _tree_add_gradient(NULL,NULL);
}
static void _tree_add_brush(GtkButton *button, dt_iop_module_t *module)
{
  // enable pressure readings
  dt_gui_enable_extended_input_devices();
  //we create the new form
  dt_masks_form_t *spot = dt_masks_create(DT_MASKS_BRUSH);
  dt_masks_change_form_gui(spot);
  darktable.develop->form_gui->creation = TRUE;
  darktable.develop->form_gui->creation_module = module;
  darktable.develop->form_gui->group_selected = 0;
  dt_control_queue_redraw_center();
}
static void _bt_add_brush (GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *module)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
  {
    //we unset the creation mode
    dt_masks_form_t *form = darktable.develop->form_visible;
    if (form) dt_masks_free_form(form);
    dt_masks_change_form_gui(NULL);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else _tree_add_brush(NULL,NULL);
}


static void _tree_add_exist(GtkButton *button, dt_masks_form_t *grp)
{
  if (!grp || !(grp->type & DT_MASKS_GROUP)) return;
  //we get the new formid
  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button),"formid"));

  //we add the form in this group
  dt_masks_point_group_t *grpt = malloc(sizeof(dt_masks_point_group_t));
  grpt->formid = id;
  grpt->parentid = grp->formid;
  grpt->state = DT_MASKS_STATE_SHOW | DT_MASKS_STATE_USE;
  if (g_list_length(grp->points)>0) grpt->state |= DT_MASKS_STATE_UNION;
  grpt->opacity = 1.0f;
  grp->points = g_list_append(grp->points,grpt);
  //we save the group
  dt_masks_write_form(grp,darktable.develop);

  //and we apply the change
  dt_dev_masks_list_change(darktable.develop);
  dt_masks_update_image(darktable.develop);
  dt_dev_masks_selection_change(darktable.develop,grp->formid,TRUE);
}

static void _tree_group(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  //we create the new group
  dt_masks_form_t *grp = dt_masks_create(DT_MASKS_GROUP);
  snprintf(grp->name,128,_("group #%d"),g_list_length(darktable.develop->forms));

  //we add all selected forms to this group
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));

  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  int pos = 0;
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv);
      int id = g_value_get_int(&gv);
      if (id > 0)
      {
        dt_masks_point_group_t *fpt = (dt_masks_point_group_t *) malloc(sizeof(dt_masks_point_group_t));
        fpt->formid = id;
        fpt->parentid = grp->formid;
        fpt->opacity = 1.0f;
        fpt->state = DT_MASKS_STATE_USE;
        if (pos>0) fpt->state |= DT_MASKS_STATE_UNION;
        grp->points = g_list_append(grp->points,fpt);
        pos++;
      }
    }
    items = g_list_next(items);
  }

  //we add this group to the general list
  darktable.develop->forms = g_list_append(darktable.develop->forms,grp);

  //add we save
  dt_masks_write_forms(darktable.develop);
  _lib_masks_recreate_list(self);
  //dt_masks_change_form_gui(grp);
}

static void _set_iter_name(dt_lib_masks_t *lm, dt_masks_form_t *form, int state, float opacity, GtkTreeModel *model, GtkTreeIter *iter)
{
  if (!form) return;

  char str[256] = "";
  strcat(str,form->name);

  if (opacity != 1.0f)
  {
    char str2[256] = "";
    strcpy(str2,str);
    snprintf(str,256,"%s %d%%",str2,(int)(opacity*100));
  }

  GdkPixbuf *icop = NULL;
  GdkPixbuf *icinv = NULL;
  if (state & DT_MASKS_STATE_UNION) icop = lm->ic_union;
  else if (state & DT_MASKS_STATE_INTERSECTION) icop = lm->ic_intersection;
  else if (state & DT_MASKS_STATE_DIFFERENCE) icop = lm->ic_difference;
  else if (state & DT_MASKS_STATE_EXCLUSION) icop = lm->ic_exclusion;
  if (state & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;

  gtk_tree_store_set(GTK_TREE_STORE(model), iter, TREE_TEXT, str, TREE_IC_OP, icop, TREE_IC_OP_VISIBLE, (icop != NULL), TREE_IC_INVERSE, icinv,
                     TREE_IC_INVERSE_VISIBLE, (icinv!=NULL), -1);

}

static void _tree_cleanup(GtkButton *button, dt_lib_module_t *self)
{
  dt_masks_cleanup_unused(darktable.develop);
  _lib_masks_recreate_list(self);
}

static void _tree_inverse(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  int change = 0;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        //we search the entry to inverse
        GList *pts = g_list_first(grp->points);
        while(pts)
        {
          dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
          if (pt->formid == id)
          {
            if (pt->state & DT_MASKS_STATE_INVERSE) pt->state &= ~DT_MASKS_STATE_INVERSE;
            else pt->state |= DT_MASKS_STATE_INVERSE;
            _set_iter_name(lm,dt_masks_get_from_id(darktable.develop,id),pt->state,pt->opacity,model,&iter);
            change = 1;
            break;
          }
          pts = g_list_next(pts);
        }
      }
    }
    items = g_list_next(items);
  }

  if (change)
  {
    dt_masks_write_forms(darktable.develop);
    dt_masks_update_image(darktable.develop);
    dt_control_queue_redraw_center();
  }
}

static void _tree_intersection(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  int change = 0;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        //we search the entry to inverse
        GList *pts = g_list_first(grp->points);
        while(pts)
        {
          dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
          if (pt->formid == id)
          {
            if (!(pt->state & DT_MASKS_STATE_INTERSECTION))
            {
              if (pt->state & DT_MASKS_STATE_DIFFERENCE) pt->state &= ~DT_MASKS_STATE_DIFFERENCE;
              else if (pt->state & DT_MASKS_STATE_UNION) pt->state &= ~DT_MASKS_STATE_UNION;
              else if (pt->state & DT_MASKS_STATE_EXCLUSION) pt->state &= ~DT_MASKS_STATE_EXCLUSION;
              pt->state |= DT_MASKS_STATE_INTERSECTION;
              _set_iter_name(lm,dt_masks_get_from_id(darktable.develop,id),pt->state,pt->opacity,model,&iter);
              change = 1;
            }
            break;
          }
          pts = g_list_next(pts);
        }
      }
    }
    items = g_list_next(items);
  }

  if (change)
  {
    dt_masks_write_forms(darktable.develop);
    dt_masks_update_image(darktable.develop);
    dt_control_queue_redraw_center();
  }
}

static void _tree_difference(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  int change = 0;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        //we search the entry to inverse
        GList *pts = g_list_first(grp->points);
        while(pts)
        {
          dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
          if (pt->formid == id)
          {
            if (!(pt->state & DT_MASKS_STATE_DIFFERENCE))
            {
              if (pt->state & DT_MASKS_STATE_UNION) pt->state &= ~DT_MASKS_STATE_UNION;
              else if (pt->state & DT_MASKS_STATE_INTERSECTION) pt->state &= ~DT_MASKS_STATE_INTERSECTION;
              else if (pt->state & DT_MASKS_STATE_EXCLUSION) pt->state &= ~DT_MASKS_STATE_EXCLUSION;
              pt->state |= DT_MASKS_STATE_DIFFERENCE;
              _set_iter_name(lm,dt_masks_get_from_id(darktable.develop,id),pt->state,pt->opacity,model,&iter);
              change = 1;
            }
            break;
          }
          pts = g_list_next(pts);
        }
      }
    }
    items = g_list_next(items);
  }

  if (change)
  {
    dt_masks_write_forms(darktable.develop);
    dt_masks_update_image(darktable.develop);
    dt_control_queue_redraw_center();
  }
}

static void _tree_exclusion(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  int change = 0;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        //we search the entry to inverse
        GList *pts = g_list_first(grp->points);
        while(pts)
        {
          dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
          if (pt->formid == id)
          {
            if (!(pt->state & DT_MASKS_STATE_EXCLUSION))
            {
              if (pt->state & DT_MASKS_STATE_DIFFERENCE) pt->state &= ~DT_MASKS_STATE_DIFFERENCE;
              else if (pt->state & DT_MASKS_STATE_INTERSECTION) pt->state &= ~DT_MASKS_STATE_INTERSECTION;
              else if (pt->state & DT_MASKS_STATE_UNION) pt->state &= ~DT_MASKS_STATE_UNION;
              pt->state |= DT_MASKS_STATE_EXCLUSION;
              _set_iter_name(lm,dt_masks_get_from_id(darktable.develop,id),pt->state,pt->opacity,model,&iter);
              change = 1;
            }
            break;
          }
          pts = g_list_next(pts);
        }
      }
    }
    items = g_list_next(items);
  }

  if (change)
  {
    dt_masks_write_forms(darktable.develop);
    dt_masks_update_image(darktable.develop);
    dt_control_queue_redraw_center();
  }
}

static void _tree_union(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  int change = 0;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        //we search the entry to inverse
        GList *pts = g_list_first(grp->points);
        while(pts)
        {
          dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
          if (pt->formid == id)
          {
            if (!(pt->state & DT_MASKS_STATE_UNION))
            {
              if (pt->state & DT_MASKS_STATE_DIFFERENCE) pt->state &= ~DT_MASKS_STATE_DIFFERENCE;
              else if (pt->state & DT_MASKS_STATE_INTERSECTION) pt->state &= ~DT_MASKS_STATE_INTERSECTION;
              else if (pt->state & DT_MASKS_STATE_EXCLUSION) pt->state &= ~DT_MASKS_STATE_EXCLUSION;
              pt->state |= DT_MASKS_STATE_UNION;
              _set_iter_name(lm,dt_masks_get_from_id(darktable.develop,id),pt->state,pt->opacity,model,&iter);
              change = 1;
            }
            break;
          }
          pts = g_list_next(pts);
        }
      }
    }
    items = g_list_next(items);
  }

  if (change)
  {
    dt_masks_write_forms(darktable.develop);
    dt_masks_update_image(darktable.develop);
    dt_control_queue_redraw_center();
  }
}

static void _tree_moveup(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //we first discard all visible shapes
  dt_masks_clear_form_gui(darktable.develop);
  darktable.develop->form_visible = NULL;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  lm->gui_reset = 1;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);

      dt_masks_form_move(dt_masks_get_from_id(darktable.develop,grid),id,1);
    }
    items = g_list_next(items);
  }
  lm->gui_reset = 0;
  _lib_masks_recreate_list(self);
  dt_masks_update_image(darktable.develop);
}

static void _tree_movedown(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //we first discard all visible shapes
  dt_masks_clear_form_gui(darktable.develop);
  darktable.develop->form_visible = NULL;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  lm->gui_reset = 1;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);

      dt_masks_form_move(dt_masks_get_from_id(darktable.develop,grid),id,0);
    }
    items = g_list_next(items);
  }
  lm->gui_reset = 0;
  _lib_masks_recreate_list(self);
  dt_masks_update_image(darktable.develop);
}
static void _tree_delete_shape(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //we first discard all visible shapes
  dt_masks_clear_form_gui(darktable.develop);
  darktable.develop->form_visible = NULL;

  //now we go through all selected nodes
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  dt_iop_module_t *module = NULL;
  lm->gui_reset = 1;
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv3 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
      int id = g_value_get_int(&gv3);
      GValue gv2 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_MODULE,&gv2);
      module = NULL;
      if (G_VALUE_TYPE(&gv2) == G_TYPE_POINTER) module = (dt_iop_module_t *)g_value_get_pointer(&gv2);

      dt_masks_form_remove(module,dt_masks_get_from_id(darktable.develop,grid),dt_masks_get_from_id(darktable.develop,id));
    }
    items = g_list_next(items);
  }
  lm->gui_reset = 0;
  _lib_masks_recreate_list(self);
}
static void _tree_duplicate_shape(GtkButton *button, dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;

  //we get the selected node
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  if (!items) return;
  GtkTreePath *item = (GtkTreePath *)items->data;
  GtkTreeIter iter;
  if (gtk_tree_model_get_iter (model,&iter,item))
  {
    GValue gv3 = {0,};
    gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
    int id = g_value_get_int(&gv3);

    int nid = dt_masks_form_duplicate(darktable.develop,id);
    if (nid <= 0) return;
    dt_dev_masks_list_change(darktable.develop);
    dt_dev_masks_selection_change(darktable.develop,nid,TRUE);
    //_lib_masks_recreate_list(self);
  }  
}
static void _tree_cell_editing_started (GtkCellRenderer *cell, GtkCellEditable *editable, const gchar *path, gpointer data)
{
  dt_control_key_accelerators_off (darktable.control);
}
static void _tree_cell_edited (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, dt_lib_module_t *self)
{
  dt_control_key_accelerators_on (darktable.control);
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GtkTreeIter iter;
  if (!gtk_tree_model_get_iter_from_string(model,&iter,path_string)) return;
  GValue gv3 = {0,};
  gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv3);
  int id = g_value_get_int(&gv3);
  dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop,id);
  if (!form) return;

  //first, we need to update the mask name

  g_strlcpy(form->name,new_text,sizeof(form->name));
  dt_masks_write_form(form,darktable.develop);

  //and we update the cell text
  _set_iter_name(lm,form,0,1.0f,model,&iter);
}

static void _tree_selection_change (GtkTreeSelection *selection,dt_lib_masks_t *self)
{
  if (self->gui_reset) return;
  //we reset all "show mask" icon of iops
  dt_masks_reset_show_masks_icons();

  //if selection empty, we hide all
  int nb = gtk_tree_selection_count_selected_rows(selection);
  if (nb == 0)
  {
    dt_masks_clear_form_gui(darktable.develop);
    darktable.develop->form_visible = NULL;
    dt_control_queue_redraw_center();
    return;
  }

  //else, we create a new from group with the selection and display it
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeview));
  dt_masks_form_t *grp = dt_masks_create(DT_MASKS_GROUP);
  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter (model,&iter,item))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_GROUPID,&gv);
      int grid = g_value_get_int(&gv);
      GValue gv2 = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv2);
      int id = g_value_get_int(&gv2);
      dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop,id);
      if (form)
      {
        dt_masks_point_group_t *fpt = (dt_masks_point_group_t *) malloc(sizeof(dt_masks_point_group_t));
        fpt->formid = id;
        fpt->parentid = grid;
        fpt->state = DT_MASKS_STATE_USE;
        fpt->opacity = 1.0f;
        grp->points = g_list_append(grp->points,fpt);
        //we eventually set the "show masks" icon of iops
        if (nb==1 && (form->type & DT_MASKS_GROUP))
        {
          GValue gv2 = {0,};
          gtk_tree_model_get_value (model,&iter,TREE_MODULE,&gv2);
          dt_iop_module_t *module = g_value_peek_pointer(&gv2);
          if (module && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(module->flags() & IOP_FLAGS_NO_MASKS))
          {
            dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t*)module->blend_data;
            bd->masks_shown = 1;
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), TRUE);
            gtk_widget_queue_draw (bd->masks_edit);
          }
        }
      }
    }
    items = g_list_next(items);
  }
  dt_masks_form_t *grp2 = dt_masks_create(DT_MASKS_GROUP);
  dt_masks_group_ungroup(grp2,grp);
  free(grp);
  dt_masks_clear_form_gui(darktable.develop);
  darktable.develop->form_visible = grp2;
  darktable.develop->form_gui->edit_mode = DT_MASKS_EDIT_FULL;
  dt_control_queue_redraw_center();
}

static int _tree_button_pressed (GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
{
  //dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  //we first need to adjust selection
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));

  GtkTreePath *mouse_path = NULL;
  GtkTreeIter iter;
  dt_iop_module_t *module = NULL;
  int on_row = 0;
  if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint) event->x, (gint) event->y, &mouse_path, NULL, NULL, NULL))
  {
    on_row = 1;
    //we retrieve the iter and module from path
    if (gtk_tree_model_get_iter (model,&iter,mouse_path))
    {
      GValue gv = {0,};
      gtk_tree_model_get_value (model,&iter,TREE_MODULE,&gv);
      module = g_value_peek_pointer(&gv);
    }
  }
  /* single click with the right mouse button? */
  if (event->type == GDK_BUTTON_PRESS  &&  event->button == 1)
  {
    //if click on a blank space, then deselect all
    if (!on_row)
    {
      gtk_tree_selection_unselect_all(selection);
    }
  }
  else if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
  {
    //if we are already inside the selection, no change
    if (on_row && !gtk_tree_selection_path_is_selected(selection,mouse_path))
    {
      if (!(event->state & GDK_CONTROL_MASK)) gtk_tree_selection_unselect_all(selection);
      gtk_tree_selection_select_path(selection, mouse_path);
      gtk_tree_path_free(mouse_path);
    }

    //and we display the context-menu
    GtkMenuShell *menu;
    GtkWidget *item;
    menu = GTK_MENU_SHELL(gtk_menu_new());

    //we get all infos from selection
    int nb = gtk_tree_selection_count_selected_rows(selection);
    int from_group = 0;

    GtkTreePath *it0 = NULL;
    int depth=0;
    if (nb>0)
    {
      it0 = (GtkTreePath *)g_list_nth_data(gtk_tree_selection_get_selected_rows(selection,NULL),0);
      depth = gtk_tree_path_get_depth (it0);
    }
    if (depth > 1) from_group = 1;

    if (nb==0)
    {
      item = gtk_menu_item_new_with_label(_("add circle"));
      g_signal_connect(item, "activate",(GCallback) _tree_add_circle, module);
      gtk_menu_shell_append(menu, item);

      item = gtk_menu_item_new_with_label(_("add ellipse"));
      g_signal_connect(item, "activate",(GCallback) _tree_add_ellipse, module);
      gtk_menu_shell_append(menu, item);

      item = gtk_menu_item_new_with_label(_("add path"));
      g_signal_connect(item, "activate",(GCallback) _tree_add_path, module);
      gtk_menu_shell_append(menu, item);

      item = gtk_menu_item_new_with_label(_("add gradient"));
      g_signal_connect(item, "activate",(GCallback) _tree_add_gradient, module);
      gtk_menu_shell_append(menu, item);

      gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
    }

    if (nb==1)
    {
      //we check if the form is a group or not
      int grpid = 0;
      if (gtk_tree_model_get_iter (model,&iter,it0))
      {
        GValue gv = {0,};
        gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv);
        grpid = g_value_get_int(&gv);
      }
      dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grpid);
      if (grp && (grp->type & DT_MASKS_GROUP))
      {
        item = gtk_menu_item_new_with_label(_("add brush"));
        g_signal_connect(item, "activate",(GCallback) _tree_add_brush, module);
        gtk_menu_shell_append(menu, item);

        item = gtk_menu_item_new_with_label(_("add circle"));
        g_signal_connect(item, "activate",(GCallback) _tree_add_circle, module);
        gtk_menu_shell_append(menu, item);

        item = gtk_menu_item_new_with_label(_("add ellipse"));
        g_signal_connect(item, "activate",(GCallback) _tree_add_ellipse, module);
        gtk_menu_shell_append(menu, item);

        item = gtk_menu_item_new_with_label(_("add path"));
        g_signal_connect(item, "activate",(GCallback) _tree_add_path, module);
        gtk_menu_shell_append(menu, item);

        item = gtk_menu_item_new_with_label(_("add gradient"));
        g_signal_connect(item, "activate",(GCallback) _tree_add_gradient, module);
        gtk_menu_shell_append(menu, item);

        item = gtk_menu_item_new_with_label(_("add existing shape"));
        gtk_menu_shell_append(menu, item);
        gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
        //existing forms
        GtkWidget *menu0 = gtk_menu_new();
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu0);
        GList *forms = g_list_first(darktable.develop->forms);
        while (forms)
        {
          dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
          if ((form->type & DT_MASKS_CLONE) || form->formid == grpid)
          {
            forms = g_list_next(forms);
            continue;
          }
          char str[10000] = "";
          strcat(str,form->name);
          int nbuse = 0;

          //we search were this form is used
          GList *modules = g_list_first(darktable.develop->iop);
          while (modules)
          {
            dt_iop_module_t *m = (dt_iop_module_t *)modules->data;
            dt_masks_form_t *grp = dt_masks_get_from_id(m->dev,m->blend_params->mask_id);
            if (grp && (grp->type & DT_MASKS_GROUP))
            {
              GList *pts = g_list_first(grp->points);
              while(pts)
              {
                dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
                if (pt->formid == form->formid)
                {
                  if (m == module)
                  {
                    nbuse = -1;
                    break;
                  }
                  if (nbuse==0) strcat(str," (");
                  strcat(str," ");
                  g_strlcat(str,m->name(),sizeof(str));
                  nbuse++;
                }
                pts = g_list_next(pts);
              }
            }
            modules = g_list_next(modules);
          }
          if (nbuse != -1)
          {
            if (nbuse>0) strcat(str," )");

            //we add the menu entry
            item = gtk_menu_item_new_with_label(str);
            g_object_set_data(G_OBJECT(item), "formid", GUINT_TO_POINTER(form->formid));
            g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_tree_add_exist), grp);
            gtk_menu_shell_append(menu, item);
          }

          forms = g_list_next(forms);
        }
      }
    }

    if (!from_group && nb>0)
    {
      item = gtk_menu_item_new_with_label(_("delete this shape"));
      g_signal_connect(item, "activate",(GCallback) _tree_delete_shape, self);
      gtk_menu_shell_append(menu, item);
      if (nb == 1)
      {
        item = gtk_menu_item_new_with_label(_("duplicate this shape"));
        g_signal_connect(item, "activate",(GCallback) _tree_duplicate_shape, self);
        gtk_menu_shell_append(menu, item);
      }
    }
    else if (nb>0 && depth < 3)
    {
      item = gtk_menu_item_new_with_label(_("remove from group"));
      g_signal_connect(item, "activate",(GCallback) _tree_delete_shape, self);
      gtk_menu_shell_append(menu, item);
    }

    if (nb>1 && !from_group)
    {
      gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
      item = gtk_menu_item_new_with_label(_("group the forms"));
      g_signal_connect(item, "activate",(GCallback) _tree_group, self);
      gtk_menu_shell_append(menu, item);
    }


    if (from_group && depth < 3)
    {
      gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
      item = gtk_menu_item_new_with_label(_("use inversed shape"));
      g_signal_connect(item, "activate",(GCallback) _tree_inverse, self);
      gtk_menu_shell_append(menu, item);
      if (nb==1)
      {
        gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
        item = gtk_menu_item_new_with_label(_("mode : union"));
        g_signal_connect(item, "activate",(GCallback) _tree_union, self);
        gtk_menu_shell_append(menu, item);
        item = gtk_menu_item_new_with_label(_("mode : intersection"));
        g_signal_connect(item, "activate",(GCallback) _tree_intersection, self);
        gtk_menu_shell_append(menu, item);
        item = gtk_menu_item_new_with_label(_("mode : difference"));
        g_signal_connect(item, "activate",(GCallback) _tree_difference, self);
        gtk_menu_shell_append(menu, item);
        item = gtk_menu_item_new_with_label(_("mode : exclusion"));
        g_signal_connect(item, "activate",(GCallback) _tree_exclusion, self);
        gtk_menu_shell_append(menu, item);
      }
      gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
      item = gtk_menu_item_new_with_label(_("move up"));
      g_signal_connect(item, "activate",(GCallback) _tree_moveup, self);
      gtk_menu_shell_append(menu, item);
      item = gtk_menu_item_new_with_label(_("move down"));
      g_signal_connect(item, "activate",(GCallback) _tree_movedown, self);
      gtk_menu_shell_append(menu, item);
    }
    
    gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
    item = gtk_menu_item_new_with_label(_("cleanup unused shapes"));
    g_signal_connect(item, "activate",(GCallback) _tree_cleanup, self);
    gtk_menu_shell_append(menu, item);
      
    gtk_widget_show_all(GTK_WIDGET(menu));
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,0, gdk_event_get_time((GdkEvent*)event));

    return 1;
  }

  return 0;
}

static gboolean _tree_restrict_select (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data)
{
  dt_lib_masks_t *self = (dt_lib_masks_t *)data;
  if (self->gui_reset) return TRUE;

  //if the change is SELECT->UNSELECT no pb
  if (path_currently_selected) return TRUE;

  //if selection is empty, no pb
  if (gtk_tree_selection_count_selected_rows(selection) == 0) return TRUE;

  //now we unselect all members of selection with not the same parent node
  //idem for all those with a different depth
  int *indices = gtk_tree_path_get_indices (path);
  int depth = gtk_tree_path_get_depth (path);

  GList *items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
  while(items)
  {
    GtkTreePath *item = (GtkTreePath *)items->data;
    int dd = gtk_tree_path_get_depth (item);
    int *ii = gtk_tree_path_get_indices (item);
    int ok = 1;
    if (dd != depth) ok = 0;
    else if (dd==1) ok=1;
    else if (ii[dd-2] != indices[dd-2]) ok = 0;
    if (!ok)
    {
      gtk_tree_selection_unselect_path(selection,item);
      items = g_list_first(gtk_tree_selection_get_selected_rows(selection,NULL));
      continue;
    }
    items = g_list_next(items);
  }
  return TRUE;
}

static gboolean _tree_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, gpointer data)
{
  GtkTreeIter iter;
  GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
  GtkTreePath *path = NULL;
  gchar *tmp;
  gboolean show;

  char buffer[512];

  if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_tip, &model, &path, &iter)) return FALSE;

  gtk_tree_model_get (model, &iter, TREE_IC_USED_VISIBLE, &show, TREE_USED_TEXT, &tmp, -1);
  if (!show) return FALSE;

  g_snprintf (buffer, 511, "%s", tmp);
  gtk_tooltip_set_markup (tooltip, buffer);

  gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);

  gtk_tree_path_free (path);
  g_free (tmp);

  return TRUE;
}

static int _is_form_used(int formid, dt_masks_form_t *grp, char *text)
{
  int nb = 0;
  if (!grp)
  {
    GList *forms = g_list_first(darktable.develop->forms);
    while (forms)
    {
      dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
      if (form->type & DT_MASKS_GROUP) nb += _is_form_used(formid,form,text);
      forms = g_list_next(forms);
    }
  }
  else if (grp->type & DT_MASKS_GROUP)
  {
    GList *points = g_list_first(grp->points);
    while (points)
    {
      dt_masks_point_group_t *point = (dt_masks_point_group_t *)points->data;
      dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop,point->formid);
      if (form)
      {
        if (point->formid == formid)
        {
          nb++;
          if (nb>1) strcat(text,"\n");
          strcat(text,grp->name);
        }
        if (form->type & DT_MASKS_GROUP) nb += _is_form_used(formid,form,text);
      }
      points = g_list_next(points);
    }
  }
  return nb;
}

static void _lib_masks_list_recurs(GtkTreeStore *treestore, GtkTreeIter *toplevel, dt_masks_form_t *form,
                                   int grp_id, dt_iop_module_t *module, int gstate, float opacity,dt_lib_masks_t *lm)
{
  if (form->type & DT_MASKS_CLONE) return;
  //we create the text entry
  char str[256] = "";
  strcat(str,form->name);
  //we get the right pixbufs
  GdkPixbuf *icop = NULL;
  GdkPixbuf *icinv = NULL;
  GdkPixbuf *icuse = NULL;
  if (gstate & DT_MASKS_STATE_UNION) icop = lm->ic_union;
  else if (gstate & DT_MASKS_STATE_INTERSECTION) icop = lm->ic_intersection;
  else if (gstate & DT_MASKS_STATE_DIFFERENCE) icop = lm->ic_difference;
  else if (gstate & DT_MASKS_STATE_EXCLUSION) icop = lm->ic_exclusion;
  if (gstate & DT_MASKS_STATE_INVERSE) icinv = lm->ic_inverse;
  char str2[1000] = "";
  int nbuse = 0;
  if (grp_id == 0)
  {
    nbuse = _is_form_used(form->formid,NULL,str2);
    if (nbuse>0) icuse = lm->ic_used;
  }

  if (!(form->type & DT_MASKS_GROUP))
  {
    //we just add it to the tree
    GtkTreeIter child;
    gtk_tree_store_append(treestore, &child, toplevel);
    gtk_tree_store_set(treestore, &child, TREE_TEXT, str,TREE_MODULE,module,TREE_GROUPID,grp_id, TREE_FORMID, form->formid,
                       TREE_EDITABLE, (grp_id==0), TREE_IC_OP, icop, TREE_IC_OP_VISIBLE, (icop != NULL), TREE_IC_INVERSE, icinv,
                       TREE_IC_INVERSE_VISIBLE, (icinv!=NULL), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse>0), TREE_USED_TEXT, str2, -1);
    _set_iter_name(lm,form,gstate,opacity,GTK_TREE_MODEL(treestore),&child);
  }
  else
  {
    //we first check if it's a "module" group or not
    if (grp_id==0 && !module)
    {
      GList *iops = g_list_first(darktable.develop->iop);
      while(iops)
      {
        dt_iop_module_t *iop = (dt_iop_module_t *)iops->data;
        if ((iop->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(iop->flags() & IOP_FLAGS_NO_MASKS) && iop->blend_params->mask_id == form->formid)
        {
          module = iop;
          break;
        }
        iops = g_list_next(iops);
      }
    }

    //we add the group node to the tree
    GtkTreeIter child;
    gtk_tree_store_append(treestore, &child, toplevel);
    gtk_tree_store_set(treestore, &child, TREE_TEXT, str,TREE_MODULE,module,TREE_GROUPID,grp_id, TREE_FORMID, form->formid,
                       TREE_EDITABLE, (grp_id==0), TREE_IC_OP, icop, TREE_IC_OP_VISIBLE, (icop != NULL), TREE_IC_INVERSE, icinv,
                       TREE_IC_INVERSE_VISIBLE, (icinv!=NULL), TREE_IC_USED, icuse, TREE_IC_USED_VISIBLE, (nbuse>0), TREE_USED_TEXT, str2, -1);
    _set_iter_name(lm,form,gstate,opacity,GTK_TREE_MODEL(treestore),&child);
    //we add all nodes to the tree
    GList *forms = g_list_first(form->points);
    while (forms)
    {
      dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
      dt_masks_form_t *f = dt_masks_get_from_id(darktable.develop,grpt->formid);
      if (f) _lib_masks_list_recurs(treestore,&child,f,form->formid,module,grpt->state,grpt->opacity,lm);
      forms = g_list_next(forms);
    }
  }
}

static void _lib_masks_recreate_list(dt_lib_module_t *self)
{
  /* first destroy all buttons in list */
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  if (lm->gui_reset) return;

  //if (lm->treeview) gtk_widget_destroy(lm->treeview);
  //we set the add shape icons inactive
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lm->bt_circle), FALSE);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lm->bt_ellipse), FALSE);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lm->bt_path), FALSE);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lm->bt_gradient), FALSE);

  GtkTreeStore *treestore;
  //we store : text ; *module ; groupid ; formid
  treestore = gtk_tree_store_new(TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN,
                                 GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN, G_TYPE_STRING);

  //we first add all groups
  GList *forms = g_list_first(darktable.develop->forms);
  while (forms)
  {
    dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
    if (form->type & DT_MASKS_GROUP) _lib_masks_list_recurs(treestore, NULL, form, 0,NULL,0,1.0,lm);
    forms = g_list_next(forms);
  }

  //and we add all forms
  forms = g_list_first(darktable.develop->forms);
  while (forms)
  {
    dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
    if (!(form->type & DT_MASKS_GROUP)) _lib_masks_list_recurs(treestore, NULL, form, 0,NULL,0,1.0,lm);
    forms = g_list_next(forms);
  }

  gtk_tree_view_set_model(GTK_TREE_VIEW(lm->treeview), GTK_TREE_MODEL(treestore));
  g_object_unref(treestore);
}

static gboolean _update_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  if (!iter) return 0;

  //we retrieve the ids
  GValue gv = {0,};
  gtk_tree_model_get_value (model,iter,TREE_GROUPID,&gv);
  int grid = g_value_get_int(&gv);
  GValue gv3 = {0,};
  gtk_tree_model_get_value (model,iter,TREE_FORMID,&gv3);
  int id = g_value_get_int(&gv3);

  //we retrieve the forms
  dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop,id);
  if (!form) return 0;
  dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop,grid);

  //and the values
  int state = 0;
  float opacity = 1.0f;

  if (grp && (grp->type & DT_MASKS_GROUP))
  {
    GList *pts = g_list_first(grp->points);
    while(pts)
    {
      dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
      if (pt->formid == id)
      {
        state = pt->state;
        opacity = pt->opacity;
        break;
      }
      pts = g_list_next(pts);
    }
  }

  _set_iter_name(data,form,state,opacity,model,iter);
  return 0;
}

static void _lib_masks_update_list(dt_lib_module_t *self)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  //for each node , we refresh the string
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  gtk_tree_model_foreach(model,_update_foreach,lm);
}

static gboolean _remove_foreach(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  if (!iter) return 0;
  GList **rl = (GList **) data;
  int refid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model),"formid"));
  int refgid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(model),"groupid"));

  //we retrieve the id
  GValue gv = {0,};
  gtk_tree_model_get_value (model,iter,TREE_GROUPID,&gv);
  int grid = g_value_get_int(&gv);
  GValue gv3 = {0,};
  gtk_tree_model_get_value (model,iter,TREE_FORMID,&gv3);
  int id = g_value_get_int(&gv3);

  if (grid == refgid && id == refid)
  {
    GtkTreeRowReference  *rowref = gtk_tree_row_reference_new(model, path);
    *rl = g_list_append(*rl,rowref);
  }
  return 0;
}

static void _lib_masks_remove_item(dt_lib_module_t *self, int formid, int parentid)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  //for each node , we refresh the string
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  GList *rl = NULL;
  g_object_set_data(G_OBJECT(model), "formid", GUINT_TO_POINTER(formid));
  g_object_set_data(G_OBJECT(model), "groupid", GUINT_TO_POINTER(parentid));
  gtk_tree_model_foreach(model,_remove_foreach,&rl);

  GList *rlt = g_list_first(rl);
  while(rlt)
  {
    GtkTreePath *path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)rlt->data);
    if (path)
    {
      GtkTreeIter  iter;
      if (gtk_tree_model_get_iter(model, &iter, path))
      {
        gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
      }
    }
    rlt = g_list_next(rlt);
  }
}

static void _lib_history_change_callback(gpointer instance, gpointer user_data)
{
  //dt_lib_module_t *self = (dt_lib_module_t *)user_data;
  //_lib_masks_recreate_list(self);
}

static void _lib_masks_selection_change(dt_lib_module_t *self, int selectid, int throw_event)
{
  dt_lib_masks_t *lm = (dt_lib_masks_t *)self->data;
  if (!lm->treeview) return;

  //we first unselect all
  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lm->treeview));
  lm->gui_reset = 1;
  gtk_tree_selection_unselect_all(selection);
  lm->gui_reset = 0;

  //we go through all nodes
  lm->gui_reset = 1-throw_event;
  GtkTreeIter  iter;
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(lm->treeview));
  gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
  while (valid)
  {
    //we get the formid the the iter
    GValue gv = {0,};
    gtk_tree_model_get_value (model,&iter,TREE_FORMID,&gv);
    int id = g_value_get_int(&gv);
    if (id == selectid)
    {
      gtk_tree_selection_select_iter (selection,&iter);
      break;
    }
    valid = gtk_tree_model_iter_next(model, &iter);
  }
  lm->gui_reset = 0;
}

void gui_init(dt_lib_module_t *self)
{
  const int bs = 14;
  const int bs2 = 13;

  /* initialize ui widgets */
  dt_lib_masks_t *d = (dt_lib_masks_t *)g_malloc(sizeof(dt_lib_masks_t));
  self->data = (void *)d;
  memset(d,0,sizeof(dt_lib_masks_t));
  d->gui_reset = 0;

  //initialise all masks icons
  guchar* data = NULL;
  cairo_surface_t *inverse_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *inverse_cr = cairo_create(inverse_cst);
  cairo_set_source_rgb(inverse_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_inverse(inverse_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(inverse_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_inverse = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(inverse_cst), NULL, NULL);

  cairo_surface_t *union_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *union_cr = cairo_create(union_cst);
  cairo_set_source_rgb(union_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_union(union_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(union_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_union = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(union_cst), NULL, NULL);

  cairo_surface_t *intersection_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *intersection_cr = cairo_create(intersection_cst);
  cairo_set_source_rgb(intersection_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_intersection(intersection_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(intersection_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_intersection = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(intersection_cst), NULL, NULL);

  cairo_surface_t *difference_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *difference_cr = cairo_create(difference_cst);
  cairo_set_source_rgb(difference_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_difference(difference_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(difference_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_difference = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(difference_cst), NULL, NULL);

  cairo_surface_t *exclusion_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *exclusion_cr = cairo_create(exclusion_cst);
  cairo_set_source_rgb(exclusion_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_exclusion(exclusion_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(exclusion_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_exclusion = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(exclusion_cst), NULL, NULL);

  cairo_surface_t *used_cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bs2, bs2);
  cairo_t *used_cr = cairo_create(used_cst);
  cairo_set_source_rgb(used_cr, 0.7,0.7,0.7);
  dtgtk_cairo_paint_masks_used(used_cr, 0, 0, bs2,bs2, 0);
  data = cairo_image_surface_get_data(used_cst);
  dt_draw_cairo_to_gdk_pixbuf(data, bs2,bs2);
  d->ic_used = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, bs2, bs2, cairo_image_surface_get_stride(used_cst), NULL, NULL);

  //initialise widgets
  self->widget =  gtk_vbox_new (FALSE,2);
  GtkWidget *hbox = gtk_hbox_new(FALSE,0);

  GtkWidget *label = gtk_label_new(_("created shapes"));
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);

  d->bt_gradient = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_gradient, CPF_STYLE_FLAT|CPF_DO_NOT_USE_BORDER);
  g_signal_connect(G_OBJECT(d->bt_gradient), "button-press-event", G_CALLBACK(_bt_add_gradient), NULL);
  g_object_set(G_OBJECT(d->bt_gradient), "tooltip-text", _("add gradient"), (char *)NULL);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->bt_gradient), FALSE);
  gtk_widget_set_size_request(GTK_WIDGET(d->bt_gradient),bs,bs);
  gtk_box_pack_end (GTK_BOX (hbox),d->bt_gradient,FALSE,FALSE,0);

  d->bt_path = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_path, CPF_STYLE_FLAT|CPF_DO_NOT_USE_BORDER);
  g_signal_connect(G_OBJECT(d->bt_path), "button-press-event", G_CALLBACK(_bt_add_path), NULL);
  g_object_set(G_OBJECT(d->bt_path), "tooltip-text", _("add path"), (char *)NULL);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->bt_path), FALSE);
  gtk_widget_set_size_request(GTK_WIDGET(d->bt_path),bs,bs);
  gtk_box_pack_end (GTK_BOX (hbox),d->bt_path,FALSE,FALSE,bs);

  d->bt_ellipse = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_ellipse, CPF_STYLE_FLAT|CPF_DO_NOT_USE_BORDER);
  g_signal_connect(G_OBJECT(d->bt_ellipse), "button-press-event", G_CALLBACK(_bt_add_ellipse), NULL);
  g_object_set(G_OBJECT(d->bt_ellipse), "tooltip-text", _("add ellipse"), (char *)NULL);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->bt_ellipse), FALSE);
  gtk_widget_set_size_request(GTK_WIDGET(d->bt_ellipse),bs,bs);
  gtk_box_pack_end (GTK_BOX (hbox),d->bt_ellipse,FALSE,FALSE,0);

  d->bt_circle = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_circle, CPF_STYLE_FLAT|CPF_DO_NOT_USE_BORDER);
  g_signal_connect(G_OBJECT(d->bt_circle), "button-press-event", G_CALLBACK(_bt_add_circle), NULL);
  g_object_set(G_OBJECT(d->bt_circle), "tooltip-text", _("add circle"), (char *)NULL);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->bt_circle), FALSE);
  gtk_widget_set_size_request(GTK_WIDGET(d->bt_circle),bs,bs);
  gtk_box_pack_end (GTK_BOX (hbox),d->bt_circle,FALSE,FALSE,bs);

  d->bt_brush = dtgtk_togglebutton_new(dtgtk_cairo_paint_masks_brush, CPF_STYLE_FLAT|CPF_DO_NOT_USE_BORDER);
  g_signal_connect(G_OBJECT(d->bt_brush), "button-press-event", G_CALLBACK(_bt_add_brush), NULL);
  g_object_set(G_OBJECT(d->bt_brush), "tooltip-text", _("add brush"), (char *)NULL);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->bt_brush), FALSE);
  gtk_widget_set_size_request(GTK_WIDGET(d->bt_brush),bs,bs);
  gtk_box_pack_end (GTK_BOX (hbox),d->bt_brush,FALSE,FALSE,0);

  gtk_box_pack_start (GTK_BOX (self->widget),hbox,TRUE,TRUE,0);

  d->scroll_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(d->scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_box_pack_start (GTK_BOX (self->widget),d->scroll_window,TRUE,TRUE,0);

  d->treeview = gtk_tree_view_new();
  GtkTreeViewColumn *col = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(col, "shapes");
  gtk_tree_view_append_column(GTK_TREE_VIEW(d->treeview), col);

  GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(col, renderer, FALSE);
  gtk_tree_view_column_set_attributes(col, renderer,"pixbuf", TREE_IC_OP, NULL);
  gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_OP_VISIBLE);
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(col, renderer, FALSE);
  gtk_tree_view_column_set_attributes(col, renderer,"pixbuf", TREE_IC_INVERSE, NULL);
  gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_INVERSE_VISIBLE);
  renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(col, renderer, TRUE);
  gtk_tree_view_column_add_attribute(col, renderer, "text", TREE_TEXT);
  gtk_tree_view_column_add_attribute(col, renderer, "editable", TREE_EDITABLE);
  g_signal_connect(renderer, "editing-started", (GCallback) _tree_cell_editing_started, self);
  g_signal_connect(renderer, "edited", (GCallback) _tree_cell_edited, self);
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_end(col, renderer, FALSE);
  gtk_tree_view_column_set_attributes(col, renderer,"pixbuf", TREE_IC_USED, NULL);
  gtk_tree_view_column_add_attribute(col, renderer, "visible", TREE_IC_USED_VISIBLE);

  GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->treeview));
  gtk_tree_selection_set_mode(selection,GTK_SELECTION_MULTIPLE);
  gtk_tree_selection_set_select_function(selection,_tree_restrict_select,d,NULL);
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->treeview), FALSE);
  gtk_widget_set_size_request(d->treeview, -1, 300);
  gtk_container_add(GTK_CONTAINER(d->scroll_window), d->treeview);
  //gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(d->treeview),TREE_USED_TEXT);
  g_object_set (d->treeview, "has-tooltip", TRUE, NULL);
  g_signal_connect (d->treeview, "query-tooltip", G_CALLBACK (_tree_query_tooltip), NULL);

  g_signal_connect(selection, "changed", G_CALLBACK(_tree_selection_change), d);
  g_signal_connect(d->treeview, "button-press-event", (GCallback) _tree_button_pressed, self);

  gtk_widget_show_all (self->widget);

  /* connect to history change signal for updating the history view */
  dt_control_signal_connect(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_CHANGE, G_CALLBACK(_lib_history_change_callback), self);

  // set proxy functions
  darktable.develop->proxy.masks.module = self;
  darktable.develop->proxy.masks.list_change = _lib_masks_recreate_list;
  darktable.develop->proxy.masks.list_update = _lib_masks_update_list;
  darktable.develop->proxy.masks.list_remove = _lib_masks_remove_item;
  darktable.develop->proxy.masks.selection_change = _lib_masks_selection_change;
}

void gui_cleanup(dt_lib_module_t *self)
{
  dt_control_signal_disconnect(darktable.signals, G_CALLBACK(_lib_history_change_callback), self);

  g_free(self->data);
  self->data = NULL;
}

// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-space on;
