/** \file
 * \brief List Control
 *
 * See Copyright Notice in "iup.h"
 */

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#if GTK_CHECK_VERSION(3, 0, 0)
#include <gdk/gdkkeysyms-compat.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <stdarg.h>

#include "iup.h"
#include "iupcbs.h"

#include "iup_object.h"
#include "iup_layout.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drv.h"
#include "iup_drvfont.h"
#include "iup_mask.h"
#include "iup_key.h"
#include "iup_image.h"
#include "iup_list.h"
#include "iup_childtree.h"

#include "iupgtk_drv.h"


enum
{
  IUPGTK_LIST_IMAGE,  /* "pixbuf" */
  IUPGTK_LIST_TEXT,  /* "text" */
  IUPGTK_LIST_LAST_DATA  /* used as a count */
};

static void gtkListSelectionChanged(GtkTreeSelection* selection, Ihandle* ih);
static void gtkListComboBoxChanged(GtkComboBox* widget, Ihandle* ih);

/* Custom Virtual List Model for VIRTUALMODE */
typedef struct _IupGtkVirtualListModel IupGtkVirtualListModel;
typedef struct _IupGtkVirtualListModelClass IupGtkVirtualListModelClass;

struct _IupGtkVirtualListModel
{
  GObject parent;
  Ihandle* ih;
  gint stamp;
  gint count;  /* Model's own count, used during detach/reattach */
};

struct _IupGtkVirtualListModelClass
{
  GObjectClass parent_class;
};

GType iup_gtk_virtual_list_model_get_type(void);
#define IUP_TYPE_GTK_VIRTUAL_LIST_MODEL (iup_gtk_virtual_list_model_get_type())
#define IUP_GTK_VIRTUAL_LIST_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), IUP_TYPE_GTK_VIRTUAL_LIST_MODEL, IupGtkVirtualListModel))

static void iup_gtk_virtual_list_model_tree_model_init(GtkTreeModelIface *iface);

G_DEFINE_TYPE_WITH_CODE(IupGtkVirtualListModel, iup_gtk_virtual_list_model, G_TYPE_OBJECT,
                        G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, iup_gtk_virtual_list_model_tree_model_init))

static void iup_gtk_virtual_list_model_init(IupGtkVirtualListModel *model)
{
  model->stamp = g_random_int();
}

static void iup_gtk_virtual_list_model_class_init(IupGtkVirtualListModelClass *klass)
{
  (void)klass;
}

static GtkTreeModelFlags iup_gtk_virtual_list_model_get_flags(GtkTreeModel *tree_model)
{
  (void)tree_model;
  return GTK_TREE_MODEL_LIST_ONLY;
}

static gint iup_gtk_virtual_list_model_get_n_columns(GtkTreeModel *tree_model)
{
  (void)tree_model;
  return IUPGTK_LIST_LAST_DATA;  /* IMAGE + TEXT columns */
}

static GType iup_gtk_virtual_list_model_get_column_type(GtkTreeModel *tree_model, gint index)
{
  (void)tree_model;
  if (index == IUPGTK_LIST_IMAGE)
    return GDK_TYPE_PIXBUF;
  return G_TYPE_STRING;
}

static gboolean iup_gtk_virtual_list_model_get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);
  gint *indices;
  gint depth, row;

  indices = gtk_tree_path_get_indices(path);
  depth = gtk_tree_path_get_depth(path);

  if (depth != 1)
    return FALSE;

  row = indices[0];
  if (row < 0 || row >= model->count)
    return FALSE;

  iter->stamp = model->stamp;
  iter->user_data = GINT_TO_POINTER(row);
  iter->user_data2 = NULL;
  iter->user_data3 = NULL;

  return TRUE;
}

static GtkTreePath *iup_gtk_virtual_list_model_get_path(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);

  if (iter->stamp != model->stamp)
    return NULL;

  gint row = GPOINTER_TO_INT(iter->user_data);
  return gtk_tree_path_new_from_indices(row, -1);
}

static void iup_gtk_virtual_list_model_get_value(GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);
  gint row;
  char *item_value;

  if (column == IUPGTK_LIST_IMAGE)
  {
    g_value_init(value, GDK_TYPE_PIXBUF);

    if (iter->stamp != model->stamp)
    {
      g_value_set_object(value, NULL);
      return;
    }

    row = GPOINTER_TO_INT(iter->user_data);
    if (row < 0 || row >= model->count)
    {
      g_value_set_object(value, NULL);
      return;
    }

    /* Query image via IMAGE_CB (pos is 1-based) */
    if (model->ih->data->show_image)
    {
      char* image_name = iupListGetItemImageCb(model->ih, row + 1);
      if (image_name)
      {
        GdkPixbuf* pixImage = (GdkPixbuf*)iupImageGetImage(image_name, model->ih, 0, NULL);
        if (pixImage)
        {
          /* Scale image down if needed to fit item height */
          int charheight;
          iupdrvFontGetCharSize(model->ih, NULL, &charheight);
          int available_height = charheight + 2 * model->ih->data->spacing;
          int img_height = gdk_pixbuf_get_height(pixImage);
          int img_width = gdk_pixbuf_get_width(pixImage);

          if (model->ih->data->fit_image && img_height > available_height)
          {
            int scaled_width = (img_width * available_height) / img_height;
            GdkPixbuf* scaled = gdk_pixbuf_scale_simple(pixImage, scaled_width, available_height, GDK_INTERP_BILINEAR);
            g_value_set_object(value, scaled);
            g_object_unref(scaled);
          }
          else
          {
            g_value_set_object(value, pixImage);
          }
          return;
        }
      }
    }
    g_value_set_object(value, NULL);
    return;
  }

  g_value_init(value, G_TYPE_STRING);

  if (iter->stamp != model->stamp)
  {
    g_value_set_string(value, "");
    return;
  }

  row = GPOINTER_TO_INT(iter->user_data);
  if (row < 0 || row >= model->count)
  {
    g_value_set_string(value, "");
    return;
  }

  /* Query data via VALUE_CB (pos is 1-based for IUP) */
  item_value = iupListGetItemValueCb(model->ih, row + 1);
  g_value_set_string(value, item_value ? item_value : "");
}

static gboolean iup_gtk_virtual_list_model_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);
  gint row;

  if (iter->stamp != model->stamp)
    return FALSE;

  row = GPOINTER_TO_INT(iter->user_data);
  row++;

  if (row >= model->count)
    return FALSE;

  iter->user_data = GINT_TO_POINTER(row);
  return TRUE;
}

static gboolean iup_gtk_virtual_list_model_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);

  if (parent != NULL)
    return FALSE;

  if (model->count == 0)
    return FALSE;

  iter->stamp = model->stamp;
  iter->user_data = GINT_TO_POINTER(0);
  iter->user_data2 = NULL;
  iter->user_data3 = NULL;
  return TRUE;
}

static gboolean iup_gtk_virtual_list_model_iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
  (void)tree_model;
  (void)iter;
  return FALSE;
}

static gint iup_gtk_virtual_list_model_iter_n_children(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);

  if (iter == NULL)
    return model->count;

  return 0;
}

static gboolean iup_gtk_virtual_list_model_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
{
  IupGtkVirtualListModel *model = IUP_GTK_VIRTUAL_LIST_MODEL(tree_model);

  if (parent != NULL)
    return FALSE;

  if (n < 0 || n >= model->count)
    return FALSE;

  iter->stamp = model->stamp;
  iter->user_data = GINT_TO_POINTER(n);
  iter->user_data2 = NULL;
  iter->user_data3 = NULL;
  return TRUE;
}

static gboolean iup_gtk_virtual_list_model_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
{
  (void)tree_model;
  (void)iter;
  (void)child;
  return FALSE;
}

static void iup_gtk_virtual_list_model_tree_model_init(GtkTreeModelIface *iface)
{
  iface->get_flags = iup_gtk_virtual_list_model_get_flags;
  iface->get_n_columns = iup_gtk_virtual_list_model_get_n_columns;
  iface->get_column_type = iup_gtk_virtual_list_model_get_column_type;
  iface->get_iter = iup_gtk_virtual_list_model_get_iter;
  iface->get_path = iup_gtk_virtual_list_model_get_path;
  iface->get_value = iup_gtk_virtual_list_model_get_value;
  iface->iter_next = iup_gtk_virtual_list_model_iter_next;
  iface->iter_children = iup_gtk_virtual_list_model_iter_children;
  iface->iter_has_child = iup_gtk_virtual_list_model_iter_has_child;
  iface->iter_n_children = iup_gtk_virtual_list_model_iter_n_children;
  iface->iter_nth_child = iup_gtk_virtual_list_model_iter_nth_child;
  iface->iter_parent = iup_gtk_virtual_list_model_iter_parent;
}

static IupGtkVirtualListModel *iup_gtk_virtual_list_model_new(Ihandle *ih)
{
  IupGtkVirtualListModel *model = g_object_new(IUP_TYPE_GTK_VIRTUAL_LIST_MODEL, NULL);
  model->ih = ih;
  model->count = ih->data->item_count;  /* Use count set before mapping */
  return model;
}

/* Notify the view that model item count has changed. */
static void iup_gtk_virtual_list_model_notify_count_changed(IupGtkVirtualListModel *model, Ihandle* ih, int new_count)
{
  GtkTreeView* tree_view;

  if (!ih->handle || !GTK_IS_TREE_VIEW(ih->handle))
  {
    /* Not mapped yet, just update count and stamp */
    model->count = new_count;
    model->stamp = g_random_int();
    return;
  }

  tree_view = GTK_TREE_VIEW(ih->handle);

  /* Detach model, model->count still has old value so GTK can unref correctly */
  gtk_tree_view_set_model(tree_view, NULL);

  /* Now set new count and invalidate iterators */
  model->count = new_count;
  model->stamp = g_random_int();

  /* Reattach model, GTK will build tree with new count */
  gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(model));
}

/* Cell data function for virtual mode, called only for visible rows */
static void gtkListVirtualCellDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer,
                                       GtkTreeModel* tree_model, GtkTreeIter* iter, gpointer user_data)
{
  Ihandle* ih = (Ihandle*)user_data;
  gint row;
  char* text;

  (void)column;
  (void)tree_model;

  row = GPOINTER_TO_INT(iter->user_data);
  text = iupListGetItemValueCb(ih, row + 1);  /* 1-based for IUP */
  g_object_set(renderer, "text", text ? text : "", NULL);
}

/* Cached measurement for list item space */
static int iupgtk_list_item_space = -1;
static int iupgtk_list_row_height = -1;

static void iupgtkListMeasureItemMetrics(void)
{
  if (iupgtk_list_item_space < 0)
  {
    GtkWidget *temp_window = gtk_offscreen_window_new();
    GtkListStore *temp_store = gtk_list_store_new(1, G_TYPE_STRING);
    GtkWidget *temp_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(temp_store));
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, NULL);
    GtkTreeIter iter;
    int char_height;
    PangoContext *context;
    PangoLayout *layout;
    int row1_y, row2_y;

    g_object_set(G_OBJECT(renderer), "xpad", 0, NULL);
    g_object_set(G_OBJECT(renderer), "ypad", 0, NULL);

    gtk_tree_view_append_column(GTK_TREE_VIEW(temp_tree), column);

    gtk_list_store_append(temp_store, &iter);
    gtk_list_store_set(temp_store, &iter, 0, "X", -1);
    gtk_list_store_append(temp_store, &iter);
    gtk_list_store_set(temp_store, &iter, 0, "X", -1);

    gtk_container_add(GTK_CONTAINER(temp_window), temp_tree);
    gtk_widget_show_all(temp_window);

    /* Measure actual row stride by comparing Y positions of row 0 and row 1 */
    {
      GtkTreePath *path0 = gtk_tree_path_new_from_string("0");
      GtkTreePath *path1 = gtk_tree_path_new_from_string("1");
      GdkRectangle rect0, rect1;

      gtk_tree_view_get_background_area(GTK_TREE_VIEW(temp_tree), path0, column, &rect0);
      gtk_tree_view_get_background_area(GTK_TREE_VIEW(temp_tree), path1, column, &rect1);

      row1_y = rect0.y;
      row2_y = rect1.y;
      iupgtk_list_row_height = rect1.y - rect0.y;

      gtk_tree_path_free(path0);
      gtk_tree_path_free(path1);
    }

    context = gtk_widget_get_pango_context(temp_tree);
    layout = pango_layout_new(context);
    pango_layout_set_text(layout, "X", -1);
    pango_layout_get_pixel_size(layout, NULL, &char_height);
    g_object_unref(layout);

    /* item_space = actual_row_height - char_height */
    iupgtk_list_item_space = iupgtk_list_row_height - char_height;
    if (iupgtk_list_item_space < 0) iupgtk_list_item_space = 2;

    g_object_unref(temp_store);
    gtk_widget_destroy(temp_window);
  }
}

void iupdrvListAddItemSpace(Ihandle* ih, int *h)
{
  (void)ih;
  iupgtkListMeasureItemMetrics();
  *h += iupgtk_list_item_space;
}

void iupdrvListAddBorders(Ihandle* ih, int *x, int *y)
{
  /* LAYOUT_DECORATION_ESTIMATE */
  static int dropdown_border_x = -1;
  static int dropdown_border_y = -1;
  static int dropdown_editbox_border_x = -1;
  static int dropdown_editbox_border_y = -1;
  static int editbox_border_y = -1;
  static int scrolled_window_border = -1;

  /* Measure scrolled_window border for plain lists */
  if (scrolled_window_border == -1)
  {
    GtkWidget *temp_window = gtk_offscreen_window_new();
    GtkWidget *temp_scrolled = gtk_scrolled_window_new(NULL, NULL);
    GtkWidget *temp_label = gtk_label_new("X");
    GtkRequisition label_min, label_nat;
    GtkRequisition sw_min, sw_nat;

    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(temp_scrolled), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(temp_scrolled), GTK_SHADOW_IN);

    gtk_container_add(GTK_CONTAINER(temp_scrolled), temp_label);
    gtk_container_add(GTK_CONTAINER(temp_window), temp_scrolled);
    gtk_widget_show_all(temp_window);

#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_widget_get_preferred_size(temp_label, &label_min, &label_nat);
    gtk_widget_get_preferred_size(temp_scrolled, &sw_min, &sw_nat);

    /* Border is the difference between scrolled_window and its child */
    scrolled_window_border = sw_nat.height - label_nat.height;
    if (scrolled_window_border < 0) scrolled_window_border = 10; /* fallback to safe default */
#else
    /* GTK2: Use size_request instead */
    gtk_widget_size_request(temp_label, &label_nat);
    gtk_widget_size_request(temp_scrolled, &sw_nat);

    scrolled_window_border = sw_nat.height - label_nat.height;
    if (scrolled_window_border < 0) scrolled_window_border = 10;
#endif

    gtk_widget_destroy(temp_window);
  }

  /* Use measured border for Y, keep 10px for X (horizontal padding) */
  (*x) += 10;
  (*y) += scrolled_window_border;

  /* Measure plain GtkEntry for non-dropdown editbox list */
  if (editbox_border_y == -1)
  {
    GtkWidget *temp_window = gtk_offscreen_window_new();
    GtkWidget *temp_entry = gtk_entry_new();

    gtk_container_add(GTK_CONTAINER(temp_window), temp_entry);
    gtk_widget_show_all(temp_window);

#if GTK_CHECK_VERSION(3, 0, 0)
    {
      int entry_nat_h;
      gtk_widget_get_preferred_height(temp_entry, NULL, &entry_nat_h);
      editbox_border_y = entry_nat_h;
    }
#else
    {
      GtkRequisition requisition;
      gtk_widget_size_request(temp_entry, &requisition);
      editbox_border_y = requisition.height;
    }
#endif

    gtk_widget_destroy(temp_window);
  }

  if (ih->data->is_dropdown)
  {
    if (dropdown_border_x == -1)
    {
      GtkWidget *temp_window = gtk_offscreen_window_new();
      GtkListStore *temp_store = gtk_list_store_new(1, G_TYPE_STRING);
      GtkTreeIter iter;
      gtk_list_store_append(temp_store, &iter);
      gtk_list_store_set(temp_store, &iter, 0, "X", -1);

      /* Measure regular dropdown (no editbox) */
      GtkWidget* temp_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(temp_store));
      GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
      gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(temp_combo), renderer, TRUE);
      gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(temp_combo), renderer, "text", 0, NULL);

      gtk_container_add(GTK_CONTAINER(temp_window), temp_combo);
      gtk_widget_show_all(temp_window);

      GtkRequisition combo_nat;
      gint renderer_nat_w, renderer_nat_h;
      int sb_size = iupdrvGetScrollbarSize();

#if GTK_CHECK_VERSION(3, 0, 0)
      gtk_widget_get_preferred_size(temp_combo, NULL, &combo_nat);
      gtk_cell_renderer_get_preferred_width(renderer, temp_combo, NULL, &renderer_nat_w);
      gtk_cell_renderer_get_preferred_height(renderer, temp_combo, NULL, &renderer_nat_h);
#else
      gtk_widget_size_request(temp_combo, &combo_nat);
      gtk_cell_renderer_get_size(renderer, temp_combo, NULL, NULL, NULL, &renderer_nat_w, &renderer_nat_h);
#endif

      /* Border = combo size - content - arrow (core adds sb_size for arrow) */
      dropdown_border_x = combo_nat.width - renderer_nat_w - sb_size;
      if (dropdown_border_x < 0) dropdown_border_x = 0;

      dropdown_border_y = combo_nat.height - renderer_nat_h;
      if (dropdown_border_y < 0) dropdown_border_y = 0;

      gtk_widget_destroy(temp_window);

      /* Measure dropdown with editbox */
      temp_window = gtk_offscreen_window_new();
#if GTK_CHECK_VERSION(2, 24, 0)
      GtkWidget* temp_combo_entry = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(temp_store));
      gtk_combo_box_set_entry_text_column((GtkComboBox*)temp_combo_entry, 0);
#else
      GtkWidget* temp_combo_entry = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(temp_store), 0);
#endif

      gtk_container_add(GTK_CONTAINER(temp_window), temp_combo_entry);
      gtk_widget_show_all(temp_window);

      GtkRequisition combo_entry_nat;

#if GTK_CHECK_VERSION(3, 0, 0)
      gtk_widget_get_preferred_size(temp_combo_entry, NULL, &combo_entry_nat);

      /* Measure entry child CSS decoration */
      {
        GtkWidget* entry = gtk_bin_get_child(GTK_BIN(temp_combo_entry));
        gint entry_nat_w, entry_nat_h;
        gtk_widget_get_preferred_width(entry, NULL, &entry_nat_w);
        gtk_widget_get_preferred_height(entry, NULL, &entry_nat_h);

        GtkStyleContext* entry_ctx = gtk_widget_get_style_context(entry);
        GtkBorder entry_css_border, entry_css_padding;
        gtk_style_context_get_border(entry_ctx, GTK_STATE_FLAG_NORMAL, &entry_css_border);
        gtk_style_context_get_padding(entry_ctx, GTK_STATE_FLAG_NORMAL, &entry_css_padding);

        int entry_decor_x = entry_css_border.left + entry_css_border.right +
                            entry_css_padding.left + entry_css_padding.right;
        int entry_decor_y = entry_css_border.top + entry_css_border.bottom +
                            entry_css_padding.top + entry_css_padding.bottom;

        int combo_outer_x = combo_entry_nat.width - entry_nat_w;
        int combo_outer_y = combo_entry_nat.height - entry_nat_h;
        if (combo_outer_x < 0) combo_outer_x = 0;
        if (combo_outer_y < 0) combo_outer_y = 0;

        dropdown_editbox_border_x = entry_decor_x + combo_outer_x - sb_size;
        if (dropdown_editbox_border_x < 0) dropdown_editbox_border_x = 0;

        dropdown_editbox_border_y = entry_decor_y + combo_outer_y;
      }
#else
      gtk_widget_size_request(temp_combo_entry, &combo_entry_nat);

      dropdown_editbox_border_x = combo_entry_nat.width - renderer_nat_w - sb_size;
      if (dropdown_editbox_border_x < 0) dropdown_editbox_border_x = 0;

      dropdown_editbox_border_y = combo_entry_nat.height - renderer_nat_h;
      if (dropdown_editbox_border_y < 0) dropdown_editbox_border_y = 0;
#endif

      gtk_widget_destroy(temp_window);
      g_object_unref(temp_store);
    }

    /* Dropdown doesn't use scrolled_window, remove those base borders */
    (*x) -= 10;
    (*y) -= scrolled_window_border;

    if (ih->data->has_editbox)
    {
      (*x) += dropdown_editbox_border_x;
      (*y) += dropdown_editbox_border_y;
    }
    else
    {
      (*x) += dropdown_border_x;
      (*y) += dropdown_border_y;
    }
  }
  else
  {
    int visiblelines = iupAttribGetInt(ih, "VISIBLELINES");

    if (ih->data->has_editbox)
    {
      /* For EDITBOX: VISIBLELINES includes the entry line */
      if (visiblelines > 0)
      {
        /* Remove one item's height since entry takes one "line" of VISIBLELINES */
        int char_width, char_height;
        iupdrvFontGetCharSize(ih, &char_width, &char_height);
        int item_height = char_height;
        iupdrvListAddItemSpace(ih, &item_height);
        (*y) -= item_height;
      }

      (*y) += editbox_border_y;
      if (ih->data->sb && !visiblelines)
        (*y) += iupdrvGetScrollbarSize();
    }
    else if (ih->data->sb && !visiblelines)
    {
      (*y) += iupdrvGetScrollbarSize();
    }
  }
}

static int gtkListConvertXYToPos(Ihandle* ih, int x, int y)
{
  if (!ih->data->is_dropdown)
  {
    GtkTreePath* path;
    if (gtk_tree_view_get_dest_row_at_pos((GtkTreeView*)ih->handle, x, y, &path, NULL))
    {
      int* indices = gtk_tree_path_get_indices(path);
      int pos = indices[0]+1;  /* IUP starts at 1 */
      gtk_tree_path_free (path);
      return pos;
    }
  }

  return -1;
}

static GtkTreeModel* gtkListGetModel(Ihandle* ih)
{
  if (ih->data->is_dropdown)
    return gtk_combo_box_get_model((GtkComboBox*)ih->handle);
  else
    return gtk_tree_view_get_model((GtkTreeView*)ih->handle);
}

int iupdrvListGetCount(Ihandle* ih)
{
  GtkTreeModel *model = gtkListGetModel(ih);
  return gtk_tree_model_iter_n_children(model, NULL);
}

void iupdrvListAppendItem(Ihandle* ih, const char* value)
{
  GtkTreeModel *model = gtkListGetModel(ih);
  GtkTreeIter iter;

  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
  gtk_list_store_append(GTK_LIST_STORE(model), &iter);

  gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_TEXT, iupgtkStrConvertToSystem(value), -1);
  gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, NULL, -1);
  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
}

void iupdrvListInsertItem(Ihandle* ih, int pos, const char* value)
{
  GtkTreeModel *model = gtkListGetModel(ih);
  GtkTreeIter iter;

  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
  gtk_list_store_insert(GTK_LIST_STORE(model), &iter, pos);

  gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_TEXT, iupgtkStrConvertToSystem(value), -1);
  gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, NULL, -1);
  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);

  iupListUpdateOldValue(ih, pos, 0);
}

void iupdrvListRemoveItem(Ihandle* ih, int pos)
{
  GtkTreeModel *model = gtkListGetModel(ih);
  GtkTreeIter iter;
  if (gtk_tree_model_iter_nth_child(model, &iter, NULL, pos))
  {
    if (ih->data->is_dropdown && !ih->data->has_editbox)
    {
      /* must check if removing the current item */
      int curpos = gtk_combo_box_get_active((GtkComboBox*)ih->handle);
      if (pos == curpos)
      {
        if (curpos > 0) 
          curpos--;
        else 
        {
          curpos=1;
          if (iupdrvListGetCount(ih)==1)
            curpos = -1; /* remove the selection */
        }

        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
        gtk_combo_box_set_active((GtkComboBox*)ih->handle, curpos);
        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
      }
    }

    iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
    gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);

    iupListUpdateOldValue(ih, pos, 1);
  }
}

void iupdrvListRemoveAllItems(Ihandle* ih)
{
  GtkTreeModel *model = gtkListGetModel(ih);
  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
  gtk_list_store_clear(GTK_LIST_STORE(model));
  iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
}


/*********************************************************************************/


static int gtkListSetFontAttrib(Ihandle* ih, const char* value)
{
  if (!iupdrvSetFontAttrib(ih, value))
    return 0;

  if (ih->handle)
  {
    if (ih->data->is_dropdown)
    {
      GtkCellRenderer* renderer = (GtkCellRenderer*)iupAttribGet(ih, "_IUPGTK_RENDERER");
      if (renderer)
        iupgtkUpdateObjectFont(ih, G_OBJECT(renderer));
    }

    if (ih->data->has_editbox)
    {
      GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
      if (entry)
        iupgtkUpdateWidgetFont(ih, (GtkWidget*)entry);

      gtk_entry_set_width_chars(entry, 1);  /* minimum size */
    }
  }
  return 1;
}

static char* gtkListGetIdValueAttrib(Ihandle* ih, int id)
{
  int pos = iupListGetPosAttrib(ih, id);
  if (pos >= 0)
  {
    GtkTreeIter iter;
    GtkTreeModel* model = gtkListGetModel(ih);
    if (gtk_tree_model_iter_nth_child(model, &iter, NULL, pos))
    {
      gchar *text = NULL;
      gtk_tree_model_get(model, &iter, IUPGTK_LIST_TEXT, &text, -1);
      if (text)
      {
        char* ret_str = iupStrReturnStr(iupgtkStrConvertFromSystem(text));
        g_free(text);
        return ret_str;
      }
    }
  }
  return NULL;
}


static void gtkComboBoxChildrenToggleCb(GtkWidget *widget, gpointer client_data)
{
  if (GTK_IS_TOGGLE_BUTTON(widget))
  {
    GtkWidget** toggle = (GtkWidget**)client_data;
    *toggle = widget;
  }
}

static void gtkComboBoxChildrenSetBgColor(GtkWidget *widget, gpointer client_data)
{
  GdkColor* c = (GdkColor*)client_data;
  iupgtkSetBgColor(widget, (unsigned char)c->red, (unsigned char)c->green, (unsigned char)c->blue);
}

static int gtkListSetBgColorAttrib(Ihandle* ih, const char* value)
{
  unsigned char r, g, b;

  GtkScrolledWindow* scrolled_window = (GtkScrolledWindow*)iupAttribGet(ih, "_IUP_EXTRAPARENT");
  if (scrolled_window && !ih->data->is_dropdown && iupStrBoolean(IupGetGlobal("SB_BGCOLOR")))
  {
    /* ignore given value, must use only from parent for the scrollbars */
    char* parent_value = iupBaseNativeParentGetBgColor(ih);

    if (iupStrToRGB(parent_value, &r, &g, &b))
    {
      GtkWidget* sb;

      if (!GTK_IS_SCROLLED_WINDOW(scrolled_window))
        scrolled_window = (GtkScrolledWindow*)iupAttribGet(ih, "_IUPGTK_SCROLLED_WINDOW");

      iupgtkSetBgColor((GtkWidget*)scrolled_window, r, g, b);

#if GTK_CHECK_VERSION(2, 8, 0)
      sb = gtk_scrolled_window_get_hscrollbar(scrolled_window);
      if (sb) iupgtkSetBgColor(sb, r, g, b);

      sb = gtk_scrolled_window_get_vscrollbar(scrolled_window);
      if (sb) iupgtkSetBgColor(sb, r, g, b);
#endif
    }
  }

  if (!iupStrToRGB(value, &r, &g, &b))
    return 0;

  if (ih->data->has_editbox)
  {
    GtkWidget* entry = (GtkWidget*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    iupgtkSetBgColor(entry, r, g, b);
  }

  if (ih->data->is_dropdown)
  {
    GdkColor c;
    GtkContainer *container = (GtkContainer*)ih->handle;
    c.blue = b;
    c.green = g;
    c.red = r;
    gtk_container_forall(container, gtkComboBoxChildrenSetBgColor, &c);

    /* do not set for the event_box or 
       there will be an invalid background outside the dropdown */
  }

  /* TODO: this test is not necessary, 
     but when dropdown=yes the color is not set for the popup menu, 
     and the result is very weird. So we avoid setting it when dropdown=yes
     until we figure out how to set for the popup menu. */
  if (!ih->data->is_dropdown)
  {
    GtkCellRenderer* renderer = (GtkCellRenderer*)iupAttribGet(ih, "_IUPGTK_RENDERER");
    if (renderer)
    {
#if GTK_CHECK_VERSION(3, 4, 0)
      GdkRGBA rgba;
      iupgdkRGBASet(&rgba, r, g, b);
      g_object_set(G_OBJECT(renderer), "cell-background-rgba", &rgba, NULL);
#else
      GdkColor color;
      iupgdkColorSetRGB(&color, r, g, b);
      g_object_set(G_OBJECT(renderer), "cell-background-gdk", &color, NULL);
#endif
    }
  }

  return iupdrvBaseSetBgColorAttrib(ih, value);
}

static int gtkListSetFgColorAttrib(Ihandle* ih, const char* value)
{
  unsigned char r, g, b;
  if (!iupStrToRGB(value, &r, &g, &b))
    return 0;

  iupgtkSetFgColor(ih->handle, r, g, b);

  if (ih->data->has_editbox)
  {
    GtkWidget* entry = (GtkWidget*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    iupgtkSetFgColor(entry, r, g, b);
  }

  /* TODO: see comment in BGCOLOR */
  if (!ih->data->is_dropdown)
  {
    GtkCellRenderer* renderer = (GtkCellRenderer*)iupAttribGet(ih, "_IUPGTK_RENDERER");
    if (renderer)
    {
#if GTK_CHECK_VERSION(3, 4, 0)
      GdkRGBA rgba;
      iupgdkRGBASet(&rgba, r, g, b);
      g_object_set(G_OBJECT(renderer), "foreground-rgba", &rgba, NULL);
#else
      GdkColor color;
      iupgdkColorSetRGB(&color, r, g, b);
      g_object_set(G_OBJECT(renderer), "foreground-gdk", &color, NULL);
#endif
    }
  }

  return 1;
}

static char* gtkListGetValueAttrib(Ihandle* ih)
{
  if (ih->data->has_editbox)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    return iupStrReturnStr(iupgtkStrConvertFromSystem(gtk_entry_get_text(entry)));
  }
  else 
  {
    if (ih->data->is_dropdown)
    {
      int pos = gtk_combo_box_get_active((GtkComboBox*)ih->handle);
      return iupStrReturnInt(pos+1);  /* IUP starts at 1 */
    }
    else
    {
      GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ih->handle));
      if (!ih->data->is_multiple)
      {
        GtkTreeIter iter;
        GtkTreeModel* tree_model;
        if (gtk_tree_selection_get_selected(selection, &tree_model, &iter))
        {
          GtkTreePath *path = gtk_tree_model_get_path(tree_model, &iter);
          int* indices = gtk_tree_path_get_indices(path);
          int ret = indices[0]+1;  /* IUP starts at 1 */
          gtk_tree_path_free (path);
          return iupStrReturnInt(ret);  
        }
      }
      else
      {
        GList *il, *list = gtk_tree_selection_get_selected_rows(selection, NULL);
        int count = iupdrvListGetCount(ih);
        char* str = iupStrGetMemory(count+1);
        memset(str, '-', count);
        str[count]=0;
        for (il=list; il; il=il->next)
        {
          GtkTreePath* path = (GtkTreePath*)il->data;
          int* indices = gtk_tree_path_get_indices(path);
          str[indices[0]] = '+';
          gtk_tree_path_free(path);
        }
        g_list_free(list);
        return str;
      }
    }
  }

  return NULL;
}

static int gtkListSetValueAttrib(Ihandle* ih, const char* value)
{
  if (ih->data->has_editbox)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    if (!value) value = "";
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1");
    gtk_entry_set_text(entry, iupgtkStrConvertToSystem(value));
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);
  }
  else 
  {
    if (ih->data->is_dropdown)
    {
      int pos;
      GtkTreeModel *model = gtkListGetModel(ih);
      iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
      if (iupStrToInt(value, &pos) == 1 &&
          (pos>0 && pos<=gtk_tree_model_iter_n_children(model, NULL)))
      {
        gtk_combo_box_set_active((GtkComboBox*)ih->handle, pos-1);    /* IUP starts at 1 */
        iupAttribSetInt(ih, "_IUPLIST_OLDVALUE", pos);
      }
      else
      {
        gtk_combo_box_set_active((GtkComboBox*)ih->handle, -1);    /* none */
        iupAttribSet(ih, "_IUPLIST_OLDVALUE", NULL);
      }
      iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
    }
    else
    {
      GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ih->handle));
      if (!ih->data->is_multiple)
      {
        int pos;
        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
        if (iupStrToInt(value, &pos) == 1)
        {
          GtkTreePath* path = gtk_tree_path_new_from_indices(pos-1, -1);   /* IUP starts at 1 */
          gtk_tree_selection_select_path(selection, path);
          gtk_tree_path_free(path);
          iupAttribSetInt(ih, "_IUPLIST_OLDVALUE", pos);
        }
        else
        {
          gtk_tree_selection_unselect_all(selection);
          iupAttribSet(ih, "_IUPLIST_OLDVALUE", NULL);
        }
        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
      }
      else
      {
        /* User has changed a multiple selection on a simple list. */
	      int i, len, count;

        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");

        /* Clear all selections */
        gtk_tree_selection_unselect_all(selection);

        if (!value)
        {
          iupAttribSet(ih, "_IUPLIST_OLDVALUE", NULL);
          return 0;
        }

	      len = (int)strlen(value);
        count = iupdrvListGetCount(ih);
        if (len < count) 
          count = len;

        /* update selection list */
        for (i = 0; i<count; i++)
        {
          if (value[i]=='+')
          {
            GtkTreePath* path = gtk_tree_path_new_from_indices(i, -1);
            gtk_tree_selection_select_path(selection, path);
            gtk_tree_path_free(path);
          }
        }
        iupAttribSetStr(ih, "_IUPLIST_OLDVALUE", value);
        iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
      }
    }
  }

  return 0;
}

static int gtkListSetShowDropdownAttrib(Ihandle* ih, const char* value)
{
  if (ih->data->is_dropdown)
  {
    if (iupStrBoolean(value))
      gtk_combo_box_popup((GtkComboBox*)ih->handle); 
    else
      gtk_combo_box_popdown((GtkComboBox*)ih->handle); 
  }
  return 0;
}

static int gtkListSetTopItemAttrib(Ihandle* ih, const char* value)
{
  if (!ih->data->is_dropdown)
  {
    int pos = 1;
    if (iupStrToInt(value, &pos))
    {
      GtkTreePath* path = gtk_tree_path_new_from_indices(pos-1, -1);   /* IUP starts at 1 */
      gtk_tree_view_scroll_to_cell((GtkTreeView*)ih->handle, path, NULL, TRUE, 0, 0);  /* scroll to visible, top */
      gtk_tree_path_free(path);
    }
  }
  return 0;
}

static int gtkListSetSpacingAttrib(Ihandle* ih, const char* value)
{
  if (ih->data->is_dropdown)
    return 0;

  if (!iupStrToInt(value, &ih->data->spacing))
    ih->data->spacing = 0;

  if (ih->handle)
  {
    GtkCellRenderer* renderer = (GtkCellRenderer*)iupAttribGet(ih, "_IUPGTK_RENDERER");
    if (renderer)
      g_object_set(G_OBJECT(renderer), "xpad", ih->data->spacing, 
                                       "ypad", ih->data->spacing, 
                                       NULL);
    return 0;
  }
  else
    return 1; /* store until not mapped, when mapped will be set again */
}

static int gtkListSetPaddingAttrib(Ihandle* ih, const char* value)
{
  if (!ih->data->has_editbox)
    return 0;

  iupStrToIntInt(value, &ih->data->horiz_padding, &ih->data->vert_padding, 'x');
  if (ih->handle)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
#if GTK_CHECK_VERSION(3, 4, 0)
    iupgtkSetMargin(GTK_WIDGET(entry), ih->data->horiz_padding, ih->data->vert_padding, 0);
#else
#if GTK_CHECK_VERSION(2, 10, 0)
    GtkBorder border;
    border.bottom = border.top = (gint16)ih->data->vert_padding;
    border.left = border.right = (gint16)ih->data->horiz_padding;
    gtk_entry_set_inner_border(entry, &border);
#endif
#endif
    return 0;
  }
  else
    return 1; /* store until not mapped, when mapped will be set again */
}

static int gtkListSetSelectionAttrib(Ihandle* ih, const char* value)
{
  int start=1, end=1;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (!value || iupStrEqualNoCase(value, "NONE"))
  {
    gtk_editable_select_region(GTK_EDITABLE(entry), 0, 0);
    return 0;
  }

  if (iupStrEqualNoCase(value, "ALL"))
  {
    gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
    return 0;
  }

  if (iupStrToIntInt(value, &start, &end, ':')!=2) 
    return 0;

  if(start<1 || end<1) 
    return 0;

  start--; /* IUP starts at 1 */
  end--;

  gtk_editable_select_region(GTK_EDITABLE(entry), start, end);

  return 0;
}

static char* gtkListGetSelectionAttrib(Ihandle* ih)
{
  int start, end;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return NULL;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end))
  {
    start++; /* IUP starts at 1 */
    end++;
    return iupStrReturnIntInt((int)start, (int)end, ':');
  }

  return NULL;
}

static int gtkListSetSelectionPosAttrib(Ihandle* ih, const char* value)
{
  int start=0, end=0;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (!value || iupStrEqualNoCase(value, "NONE"))
  {
    gtk_editable_select_region(GTK_EDITABLE(entry), 0, 0);
    return 0;
  }

  if (iupStrEqualNoCase(value, "ALL"))
  {
    gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
    return 0;
  }

  if (iupStrToIntInt(value, &start, &end, ':')!=2) 
    return 0;

  if(start<0 || end<0) 
    return 0;

  gtk_editable_select_region(GTK_EDITABLE(entry), start, end);

  return 0;
}

static char* gtkListGetSelectionPosAttrib(Ihandle* ih)
{
  int start, end;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return NULL;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end))
    return iupStrReturnIntInt((int)start, (int)end, ':');

  return NULL;
}

static int gtkListSetSelectedTextAttrib(Ihandle* ih, const char* value)
{
  int start, end;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end))
  {
    /* disable callbacks */
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1");
    gtk_editable_delete_selection(GTK_EDITABLE(entry));
    gtk_editable_insert_text(GTK_EDITABLE(entry), iupgtkStrConvertToSystem(value), -1, &start);
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);
  }

  return 0;
}

static char* gtkListGetSelectedTextAttrib(Ihandle* ih)
{
  int start, end;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return NULL;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end))
  {
    char* selectedtext = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
    char* str = iupStrReturnStr(iupgtkStrConvertFromSystem(selectedtext));
    g_free(selectedtext);
    return str;
  }

  return NULL;
}

static int gtkListSetCaretAttrib(Ihandle* ih, const char* value)
{
  int pos = 1;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  iupStrToInt(value, &pos);
  pos--; /* IUP starts at 1 */
  if (pos < 0) pos = 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  gtk_editable_set_position(GTK_EDITABLE(entry), pos);

  return 0;
}

static char* gtkListGetCaretAttrib(Ihandle* ih)
{
  if (ih->data->has_editbox)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    int pos = gtk_editable_get_position(GTK_EDITABLE(entry));
    pos++; /* IUP starts at 1 */
    return iupStrReturnInt((int)pos);
  }
  else
    return NULL;
}

static int gtkListSetCaretPosAttrib(Ihandle* ih, const char* value)
{
  int pos = 0;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  iupStrToInt(value, &pos);
  if (pos < 0) pos = 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  gtk_editable_set_position(GTK_EDITABLE(entry), pos);

  return 0;
}

static char* gtkListGetCaretPosAttrib(Ihandle* ih)
{
  if (ih->data->has_editbox)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    int pos = gtk_editable_get_position(GTK_EDITABLE(entry));
    return iupStrReturnInt((int)pos);
  }
  else
    return NULL;
}

static int gtkListSetScrollToAttrib(Ihandle* ih, const char* value)
{
  int pos = 1;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  iupStrToInt(value, &pos);
  if (pos < 1) pos = 1;
  pos--;  /* return to GTK reference */

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  gtk_editable_set_position(GTK_EDITABLE(entry), pos);

  return 0;
}

static int gtkListSetScrollToPosAttrib(Ihandle* ih, const char* value)
{
  int pos = 0;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  iupStrToInt(value, &pos);
  if (pos < 0) pos = 0;

  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  gtk_editable_set_position(GTK_EDITABLE(entry), pos);

  return 0;
}

static int gtkListSetInsertAttrib(Ihandle* ih, const char* value)
{
  gint pos;
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  if (!value)
    return 0;

  iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1");  /* disable callbacks */
  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  pos = gtk_editable_get_position(GTK_EDITABLE(entry));
  gtk_editable_insert_text(GTK_EDITABLE(entry), iupgtkStrConvertToSystem(value), -1, &pos);
  iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);

  return 0;
}

static int gtkListSetAppendAttrib(Ihandle* ih, const char* value)
{
  if (ih->data->has_editbox)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    gint pos = (gint)strlen(gtk_entry_get_text(entry))+1;
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1"); /* disable callbacks */
    gtk_editable_insert_text(GTK_EDITABLE(entry), iupgtkStrConvertToSystem(value), -1, &pos);
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);
  }
  return 0;
}

static int gtkListSetNCAttrib(Ihandle* ih, const char* value)
{
  if (!ih->data->has_editbox)
    return 0;

  if (!iupStrToInt(value, &ih->data->nc))
    ih->data->nc = INT_MAX;

  if (ih->handle)
  {
    GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
    gtk_entry_set_max_length(entry, ih->data->nc);
    return 0;
  }
  else
    return 1; /* store until not mapped, when mapped will be set again */
}

static int gtkListSetClipboardAttrib(Ihandle *ih, const char *value)
{
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;

  /* disable callbacks */
  iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1");
  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  if (iupStrEqualNoCase(value, "COPY"))
    gtk_editable_copy_clipboard(GTK_EDITABLE(entry));
  else if (iupStrEqualNoCase(value, "CUT"))
    gtk_editable_cut_clipboard(GTK_EDITABLE(entry));
  else if (iupStrEqualNoCase(value, "PASTE"))
    gtk_editable_paste_clipboard(GTK_EDITABLE(entry));
  else if (iupStrEqualNoCase(value, "CLEAR"))
    gtk_editable_delete_selection(GTK_EDITABLE(entry));
  iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);
  return 0;
}

static int gtkListSetReadOnlyAttrib(Ihandle* ih, const char* value)
{
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return 0;
  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  gtk_editable_set_editable(GTK_EDITABLE(entry), !iupStrBoolean(value));
  return 0;
}

static char* gtkListGetReadOnlyAttrib(Ihandle* ih)
{
  GtkEntry* entry;
  if (!ih->data->has_editbox)
    return NULL;
  entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
  return iupStrReturnBoolean (!gtk_editable_get_editable(GTK_EDITABLE(entry))); 
}

static int gtkListSetImageAttrib(Ihandle* ih, int id, const char* value)
{
  GtkTreeModel* model = gtkListGetModel(ih);
  GdkPixbuf* pixImage = iupImageGetImage(value, ih, 0, NULL);
  GtkTreeIter iter;
  int pos = iupListGetPosAttrib(ih, id);

  if (!ih->data->show_image || !gtk_tree_model_iter_nth_child(model, &iter, NULL, pos))
    return 0;

  /* Scale image down if needed to fit font height */
  if (pixImage)
  {
    int charheight;
    iupdrvFontGetCharSize(ih, NULL, &charheight);
    int available_height = charheight + 2 * ih->data->spacing;
    int img_height = gdk_pixbuf_get_height(pixImage);
    int img_width = gdk_pixbuf_get_width(pixImage);

    if (ih->data->fit_image && img_height > available_height)
    {
      /* Scale down proportionally to fit available height */
      int scaled_width = (img_width * available_height) / img_height;
      GdkPixbuf* scaled = gdk_pixbuf_scale_simple(pixImage, scaled_width, available_height, GDK_INTERP_BILINEAR);
      gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, scaled, -1);
      g_object_unref(scaled);
    }
    else
    {
      gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, pixImage, -1);
    }
  }
  else
  {
    gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, NULL, -1);
  }

  return 0;
}

static char* gtkListGetImageNativeHandleAttribId(Ihandle* ih, int id)
{
  GdkPixbuf* pixImage;
  GtkTreeModel* model = gtkListGetModel(ih);
  GtkTreeIter iter;

  if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, id - 1))
    return NULL;

  gtk_tree_model_get(model, &iter, IUPGTK_LIST_IMAGE, &pixImage, -1);

  return (char*)pixImage;
}

void* iupdrvListGetImageHandle(Ihandle* ih, int id)
{
  GdkPixbuf* pixImage;
  GtkTreeModel* model = gtkListGetModel(ih);
  GtkTreeIter iter;

  if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, id-1))
    return NULL;

  gtk_tree_model_get(model, &iter, IUPGTK_LIST_IMAGE, &pixImage, -1);

  return pixImage;
}

int iupdrvListSetImageHandle(Ihandle* ih, int id, void* hImage)
{
  GtkTreeModel* model = gtkListGetModel(ih);
  GtkTreeIter iter;

  gtk_tree_model_iter_nth_child(model, &iter, NULL, id);
  gtk_list_store_set(GTK_LIST_STORE(model), &iter, IUPGTK_LIST_IMAGE, (GdkPixbuf*)hImage, -1);

  return 0;
}

void iupdrvListSetItemCount(Ihandle* ih, int count)
{
  IupGtkVirtualListModel* model;

  if (!ih->data->is_virtual)
    return;

  model = (IupGtkVirtualListModel*)iupAttribGet(ih, "_IUPGTK_VIRTUAL_MODEL");
  if (model)
    iup_gtk_virtual_list_model_notify_count_changed(model, ih, count);
}

/*********************************************************************************/

static void gtkListDragDataReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y, 
                                    GtkSelectionData *selection_data, guint info, guint time, Ihandle* ih)
{
  int is_ctrl;
  int idDrag = iupAttribGetInt(ih, "_IUPLIST_DRAGITEM");  /* starts at 1 */
  int idDrop = gtkListConvertXYToPos(ih, x, y); /* starts at 1 */

  /* shift to start at 0 */
  idDrag--;
  idDrop--;

  if (iupListCallDragDropCb(ih, idDrag, idDrop, &is_ctrl) == IUP_CONTINUE)  /* starts at 0 */
  {
    GtkTreePath* path;
    GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ih->handle));
    int count = iupdrvListGetCount(ih);
    GtkTreeModel *model = gtkListGetModel(ih);
    GtkTreeIter iterDrag, iterDrop;
    gchar *text = NULL;
    GdkPixbuf *pixImage = NULL;

    /* Copy text and image of the dragged item */
    gtk_tree_model_iter_nth_child(model, &iterDrag, NULL, idDrag);  /* starts at 0 */
    gtk_tree_model_get(model, &iterDrag, IUPGTK_LIST_TEXT, &text, 
                                         IUPGTK_LIST_IMAGE, &pixImage, -1);

    /* Copy the item to the idDrop position */
    if (idDrop >= 0 && idDrop < count) /* starts at 0 */
    {
      iupdrvListInsertItem(ih, idDrop, "");   /* starts at 0, insert before */
      if (idDrag > idDrop)
        idDrag++;
    }
    else  /* idDrop = -1 */
    {
      iupdrvListAppendItem(ih, "");
      idDrop = count;  /* new item is the previous count */
    }

    gtk_tree_model_iter_nth_child(model, &iterDrop, NULL, idDrop);  /* starts at 0 */
    gtk_list_store_set(GTK_LIST_STORE(model), &iterDrop, IUPGTK_LIST_TEXT, text, -1);
    gtk_list_store_set(GTK_LIST_STORE(model), &iterDrop, IUPGTK_LIST_IMAGE, pixImage, -1);
    g_free(text);
    if (pixImage) g_object_unref(pixImage);

    /* Selects the drop item */
    path = gtk_tree_path_new_from_indices(idDrop, -1);  /* starts at 0 */
    gtk_tree_selection_select_path(selection, path);
    gtk_tree_path_free(path);
    iupAttribSetInt(ih, "_IUPLIST_OLDVALUE", idDrop+1);  /* starts at 1 */

    /* Remove the Drag item if moving */
    if (!is_ctrl)
      iupdrvListRemoveItem(ih, idDrag);   /* starts at 0 */
  }

  iupAttribSet(ih, "_IUPLIST_DRAGITEM", NULL);

  (void)widget;
  (void)info;
  (void)context;
  (void)time;
  (void)selection_data;
}

static gboolean gtkListDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, Ihandle* ih)
{
  GtkTreePath* path;
  GtkTreeViewDropPosition pos;
  if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(ih->handle), x, y, &path, &pos))
  {
    if (pos == GTK_TREE_VIEW_DROP_BEFORE) pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
    if (pos == GTK_TREE_VIEW_DROP_AFTER) pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
    /* highlight row */
    gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(widget), path, pos);
    gtk_tree_path_free(path);

#if GTK_CHECK_VERSION(2, 22, 0)
    gdk_drag_status(context, gdk_drag_context_get_suggested_action(context), time);
#else
    gdk_drag_status(context, context->suggested_action, time);
#endif
    return TRUE;
  }

  return FALSE;
}

static void gtkListDragBegin(GtkWidget *widget, GdkDragContext *context, Ihandle* ih)
{
  int x = iupAttribGetInt(ih, "_IUPLIST_DRAG_X");
  int y = iupAttribGetInt(ih, "_IUPLIST_DRAG_Y");
  int idDrag = gtkListConvertXYToPos(ih, x, y);   /* starts at 1 */
  iupAttribSetInt(ih, "_IUPLIST_DRAGITEM", idDrag);
  (void)context;
  (void)widget;
}

static gboolean gtkListDragButtonEvent(GtkWidget *widget, GdkEventButton *evt, Ihandle *ih)
{
  if (iupgtkButtonEvent(widget, evt, ih) == TRUE)
    return TRUE;

  if (evt->type == GDK_BUTTON_PRESS && evt->button == 1)  /* left single press */
  {
    iupAttribSetInt(ih, "_IUPLIST_DRAG_X", (int)evt->x);
    iupAttribSetInt(ih, "_IUPLIST_DRAG_Y", (int)evt->y);
  }

  return FALSE;
}

static void gtkListEnableDragDrop(Ihandle* ih)
{
  const GtkTargetEntry row_targets[] = {
    /* use a custom target to avoid the internal gtkTreView DND */
    { "IUP_LIST_TARGET", GTK_TARGET_SAME_WIDGET, 0 }
  };

  gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW(ih->handle),
    GDK_BUTTON1_MASK,
    row_targets,
    G_N_ELEMENTS (row_targets),
    GDK_ACTION_MOVE|GDK_ACTION_COPY);
  gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW(ih->handle),
    row_targets,
    G_N_ELEMENTS (row_targets),
    GDK_ACTION_MOVE|GDK_ACTION_COPY);

  /* let gtkTreView begin the drag, then use the signal to save the drag start path */
  g_signal_connect_after(G_OBJECT(ih->handle),  "drag-begin", G_CALLBACK(gtkListDragBegin), ih);

  /* to avoid drop between nodes. */
  g_signal_connect(G_OBJECT(ih->handle), "drag-motion", G_CALLBACK(gtkListDragMotion), ih);

  /* to avoid the internal gtkTreView DND, we do the drop manually */
  g_signal_connect(G_OBJECT(ih->handle), "drag-data-received", G_CALLBACK(gtkListDragDataReceived), ih);

  /* to save X,Y position */
  g_signal_connect(G_OBJECT(ih->handle), "button-press-event", G_CALLBACK(gtkListDragButtonEvent), ih);
}

/*********************************************************************************/


static void gtkListEditMoveCursor(GtkWidget* entry, GtkMovementStep step, gint count, gboolean extend_selection, Ihandle* ih)
{
  int pos;

  IFniii cb = (IFniii)IupGetCallback(ih, "CARET_CB");
  if (!cb) return;

  pos = gtk_editable_get_position(GTK_EDITABLE(entry));

  if (pos != ih->data->last_caret_pos)
  {
    ih->data->last_caret_pos = pos;

    cb(ih, 1, pos+1, pos);
  }

  (void)step;
  (void)count;
  (void)extend_selection;
}

static gboolean gtkListEditKeyPressEvent(GtkWidget* entry, GdkEventKey *evt, Ihandle *ih)
{
  if (iupgtkKeyPressEvent(entry, evt, ih) == TRUE)
    return TRUE;

  if ((evt->keyval == GDK_Up || evt->keyval == GDK_KP_Up) ||
      (evt->keyval == GDK_Prior || evt->keyval == GDK_KP_Page_Up) ||
      (evt->keyval == GDK_Down || evt->keyval == GDK_KP_Down) ||
      (evt->keyval == GDK_Next || evt->keyval == GDK_KP_Page_Down))
  {
    int pos = -1;
    GtkTreeIter iter;
    GtkTreeModel* model = NULL;
    GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ih->handle));
    if (gtk_tree_selection_get_selected(selection, &model, &iter))
    {
      GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
      int* indices = gtk_tree_path_get_indices(path);
      pos = indices[0];
      gtk_tree_path_free(path);
    }

    if (pos == -1)
      pos = 0;
    else if (evt->keyval == GDK_Up || evt->keyval == GDK_KP_Up)
    {
      pos--;
      if (pos < 0) pos = 0;
    }
    else if (evt->keyval == GDK_Prior || evt->keyval == GDK_KP_Page_Up)
    {
      pos -= 5;
      if (pos < 0) pos = 0;
    }
    else if (evt->keyval == GDK_Down || evt->keyval == GDK_KP_Down)
    {
      int count = gtk_tree_model_iter_n_children(model, NULL);
      pos++;
      if (pos > count-1) pos = count-1;
    }
    else if (evt->keyval == GDK_Next || evt->keyval == GDK_KP_Page_Down)
    {
      int count = gtk_tree_model_iter_n_children(model, NULL);
      pos += 5;
      if (pos > count-1) pos = count-1;
    }

    if (pos != -1)
    {
      GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
      iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", "1");
      gtk_tree_selection_select_path(selection, path);
      iupAttribSet(ih, "_IUPLIST_IGNORE_ACTION", NULL);
      gtk_tree_path_free(path);
      iupAttribSetInt(ih, "_IUPLIST_OLDVALUE", pos+1); /* starts at 1 */

      if (!model) model = gtkListGetModel(ih);

      if (gtk_tree_model_iter_nth_child(model, &iter, NULL, pos))
      {
        gchar *text = NULL;
        gtk_tree_model_get(model, &iter, IUPGTK_LIST_TEXT, &text, -1);
        if (text)
        {
          gtk_entry_set_text((GtkEntry*)entry, text);
          g_free(text);
        }
      }
    }
  }

  return FALSE;
}

static gboolean gtkListEditKeyReleaseEvent(GtkWidget *widget, GdkEventKey *evt, Ihandle *ih)
{
  gtkListEditMoveCursor(widget, 0, 0, 0, ih);
  (void)evt;
  return FALSE;
}

static gboolean gtkListEditButtonEvent(GtkWidget *widget, GdkEventButton *evt, Ihandle *ih)
{
  gtkListEditMoveCursor(widget, 0, 0, 0, ih);
  (void)evt;
  return FALSE;
}

static void gtkListEditDeleteText(GtkEditable *editable, int start, int end, Ihandle* ih)
{
  IFnis cb = (IFnis)IupGetCallback(ih, "EDIT_CB");
  int ret;

  if (iupAttribGet(ih, "_IUPGTK_DISABLE_TEXT_CB"))
    return;

  ret = iupEditCallActionCb(ih, cb, NULL, start, end, ih->data->mask, ih->data->nc, 1, iupgtkStrGetUTF8Mode());
  if (ret == 0)
    g_signal_stop_emission_by_name(editable, "delete_text");
}

static void gtkListEditInsertText(GtkEditable *editable, char *insert_value, int len, int *pos, Ihandle* ih)
{
  IFnis cb = (IFnis)IupGetCallback(ih, "EDIT_CB");
  int ret;

  if (iupAttribGet(ih, "_IUPGTK_DISABLE_TEXT_CB"))
    return;

  ret = iupEditCallActionCb(ih, cb, iupgtkStrConvertFromSystem(insert_value), *pos, *pos, ih->data->mask, ih->data->nc, 0, iupgtkStrGetUTF8Mode());
  if (ret == 0)
    g_signal_stop_emission_by_name(editable, "insert_text");
  else if (ret != -1)
  {
    insert_value[0] = (char)ret;  /* replace key */

    /* disable callbacks */
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", "1");
    gtk_editable_insert_text(editable, insert_value, 1, pos);
    iupAttribSet(ih, "_IUPGTK_DISABLE_TEXT_CB", NULL);

    g_signal_stop_emission_by_name(editable, "insert_text"); 
  }

  (void)len;
}

static void gtkListEditChanged(void* dummy, Ihandle* ih)
{
  if (iupAttribGet(ih, "_IUPGTK_DISABLE_TEXT_CB"))
    return;

  iupBaseCallValueChangedCb(ih);
  (void)dummy;
}

static void gtkListComboBoxPopupShown(GtkComboBox* widget, GParamSpec *pspec, Ihandle* ih)
{
  IFni cb = (IFni)IupGetCallback(ih, "DROPDOWN_CB");
  iupAttribSet(ih, "_IUPDROPDOWN_POPUP", "1");
  if (cb)
  {
    gboolean popup_shown;
    g_object_get(widget, "popup-shown", &popup_shown, NULL);
    cb(ih, popup_shown);
  }
  (void)pspec;
}

static void gtkListComboBoxChanged(GtkComboBox* widget, Ihandle* ih)
{
  IFnsii cb;

  if (iupAttribGet(ih, "_IUPLIST_IGNORE_ACTION"))
    return;

  cb = (IFnsii)IupGetCallback(ih, "ACTION");
  if (cb)
  {
    int pos = gtk_combo_box_get_active((GtkComboBox*)ih->handle);
    pos++;  /* IUP starts at 1 */
    iupListSingleCallActionCb(ih, cb, pos);
  }

  if (!ih->data->has_editbox)
    iupBaseCallValueChangedCb(ih);

  (void)widget;
}

static gboolean gtkListSimpleKeyPressEvent(GtkWidget *widget, GdkEventKey *evt, Ihandle *ih)
{
  if (iupgtkKeyPressEvent(widget, evt, ih) == TRUE)
    return TRUE;

  if (evt->keyval == GDK_Return || evt->keyval == GDK_KP_Enter)
    return TRUE; /* used to avoid the call to DBLCLICK_CB in the default processing */
  return FALSE;
}

static void gtkListRowActivated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, Ihandle* ih)
{
  IFnis cb = (IFnis) IupGetCallback(ih, "DBLCLICK_CB");
  if (cb)
  {
    int* indices = gtk_tree_path_get_indices(path);
    iupListSingleCallDblClickCb(ih, cb, indices[0]+1);  /* IUP starts at 1 */
  }
  (void)column;
  (void)tree_view;
}

static void gtkListSelectionChanged(GtkTreeSelection* selection, Ihandle* ih)
{
  if (ih->data->has_editbox)
  {
    /* must manually update its contents */
    GtkTreeIter iter;
    GtkTreeModel* tree_model;
    if (gtk_tree_selection_get_selected(selection, &tree_model, &iter))
    {
      GtkTreePath *path = gtk_tree_model_get_path(tree_model, &iter);
      char* value = NULL;
            
      gtk_tree_model_get(tree_model, &iter, IUPGTK_LIST_TEXT, &value, -1);
      if (value)
      {
        GtkEntry* entry = (GtkEntry*)iupAttribGet(ih, "_IUPGTK_ENTRY");
        gtk_entry_set_text(entry, value);        
        g_free(value);
      }

      gtk_tree_path_free(path);
    }
  }

  if (iupAttribGet(ih, "_IUPLIST_IGNORE_ACTION"))
    return;

  if (!ih->data->is_multiple)
  {
    IFnsii cb = (IFnsii)IupGetCallback(ih, "ACTION");
    if (cb)
    {
      GtkTreeIter iter;
      GtkTreeModel* tree_model;
      if (gtk_tree_selection_get_selected(selection, &tree_model, &iter))
      {
        GtkTreePath *path = gtk_tree_model_get_path(tree_model, &iter);
        int* indices = gtk_tree_path_get_indices(path);
        iupListSingleCallActionCb(ih, cb, indices[0]+1);  /* IUP starts at 1 */
        gtk_tree_path_free (path);
      }
    }
  }
  else
  {
    IFns multi_cb = (IFns)IupGetCallback(ih, "MULTISELECT_CB");
    IFnsii cb = (IFnsii) IupGetCallback(ih, "ACTION");
    if (multi_cb || cb)
    {
      GList *il, *list = gtk_tree_selection_get_selected_rows(selection, NULL);
      int i, sel_count = g_list_length(list);
      int* pos = malloc(sizeof(int)*sel_count);
      for (il=list, i=0; il; il=il->next, i++)
      {
        GtkTreePath* path = (GtkTreePath*)il->data;
        int* indices = gtk_tree_path_get_indices(path);
        pos[i] = indices[0];
        gtk_tree_path_free(path);
      }
      g_list_free(list);

      iupListMultipleCallActionCb(ih, cb, multi_cb, pos, sel_count);
      free(pos);
    }
  }

  if (!ih->data->has_editbox)
    iupBaseCallValueChangedCb(ih);
}

static gboolean gtkListComboFocusInOutEvent(GtkWidget *widget, GdkEventFocus *evt, Ihandle *ih)
{
  /* Used only when DROPDOWN=YES and EDITBOX=NO */
  if (iupAttribGetStr(ih, "_IUPDROPDOWN_POPUP"))
  {
    /* A dropdrop will generate leavewindow+killfocus, then enterwindow+getfocus,
       so on a get focus we reset the flag. */
    if (evt->in)
      iupAttribSet(ih, "_IUPDROPDOWN_POPUP", NULL);
    return FALSE;
  }

  return iupgtkFocusInOutEvent(widget, evt, ih);
}

static gboolean gtkListComboEnterLeaveEvent(GtkWidget *widget, GdkEventCrossing *evt, Ihandle *ih)
{
  /* Used only when DROPDOWN=YES and EDITBOX=NO */
  if (iupAttribGetStr(ih, "_IUPDROPDOWN_POPUP"))
    return FALSE;
  return iupgtkEnterLeaveEvent(widget, evt, ih);
}


/*********************************************************************************/

/* Callback to track scrolled window size allocation and clamp if needed */
static void gtkListScrolledWindowSizeAllocate(GtkWidget* widget, GdkRectangle* allocation, gpointer user_data)
{
  Ihandle* ih = (Ihandle*)user_data;
  int sw_req_w, sw_req_h;

  gtk_widget_get_size_request(widget, &sw_req_w, &sw_req_h);

  /* If VISIBLELINES is set and we have a size_request height, clamp allocation to it */
  int visiblelines = iupAttribGetInt(ih, "VISIBLELINES");
  if (visiblelines > 0 && sw_req_h > 0 && allocation->height > sw_req_h)
  {
    /* Create a new clamped allocation and apply it */
    GtkAllocation clamped = *allocation;
    clamped.height = sw_req_h;

    /* Block this signal handler to prevent recursion */
    g_signal_handlers_block_by_func(widget, gtkListScrolledWindowSizeAllocate, user_data);

    /* Apply the clamped allocation - this will allocate children correctly */
    gtk_widget_size_allocate(widget, &clamped);

    /* Unblock the signal handler */
    g_signal_handlers_unblock_by_func(widget, gtkListScrolledWindowSizeAllocate, user_data);

    /* Update the allocation parameter to reflect what we actually did */
    *allocation = clamped;
  }
}

static int gtkListMapMethod(Ihandle* ih)
{
  GtkScrolledWindow* scrolled_window = NULL;
  GtkListStore *store;
  GtkCellRenderer *renderer = NULL, *renderer_img = NULL;

  store = gtk_list_store_new(IUPGTK_LIST_LAST_DATA, GDK_TYPE_PIXBUF, G_TYPE_STRING);

  if (ih->data->is_dropdown)
  {
    if (ih->data->has_editbox)
    {
#if GTK_CHECK_VERSION(2, 24, 0)
      ih->handle = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(store));
      gtk_combo_box_set_entry_text_column ((GtkComboBox*)ih->handle, IUPGTK_LIST_TEXT);
#else
      ih->handle = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(store), IUPGTK_LIST_TEXT);
#endif
    }
    else
      ih->handle = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));

    g_object_unref(store);

    if (!ih->handle)
      return IUP_ERROR;

    g_object_set(G_OBJECT(ih->handle), "has-frame", TRUE, NULL);

    if(ih->data->show_image)
    {
      renderer_img = gtk_cell_renderer_pixbuf_new();
      gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ih->handle), renderer_img, FALSE);
      gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ih->handle), renderer_img, "pixbuf", IUPGTK_LIST_IMAGE, NULL);
      iupAttribSet(ih, "_IUPGTK_RENDERER_IMG", (char*)renderer_img);
    }

    if (ih->data->has_editbox)
    {
      GtkWidget *entry;
#if GTK_CHECK_VERSION(2, 12, 0)
      GList* list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(ih->handle));
      renderer = list->data;
      g_list_free(list);
#endif
      entry = gtk_bin_get_child(GTK_BIN(ih->handle));
      iupAttribSet(ih, "_IUPGTK_ENTRY", (char*)entry);

      g_signal_connect(G_OBJECT(entry), "focus-in-event",     G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(entry), "focus-out-event",    G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(entry), "enter-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(entry), "leave-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(entry), "show-help",          G_CALLBACK(iupgtkShowHelp), ih);
      g_signal_connect(G_OBJECT(entry), "key-press-event",    G_CALLBACK(iupgtkKeyPressEvent), ih);

      g_signal_connect(G_OBJECT(entry), "delete-text", G_CALLBACK(gtkListEditDeleteText), ih);
      g_signal_connect(G_OBJECT(entry), "insert-text", G_CALLBACK(gtkListEditInsertText), ih);
      /* g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(gtkListEditChanged), ih); */
      g_signal_connect_after(G_OBJECT(entry), "move-cursor", G_CALLBACK(gtkListEditMoveCursor), ih);  /* only report some caret movements */
      g_signal_connect_after(G_OBJECT(entry), "key-release-event", G_CALLBACK(gtkListEditKeyReleaseEvent), ih);
      g_signal_connect(G_OBJECT(entry), "button-press-event", G_CALLBACK(gtkListEditButtonEvent), ih);  /* if connected "after" then it is ignored */
      g_signal_connect(G_OBJECT(entry), "button-release-event",G_CALLBACK(gtkListEditButtonEvent), ih);

      if (!iupAttribGetBoolean(ih, "CANFOCUS"))
        iupgtkSetCanFocus(ih->handle, 0);

      if(ih->data->show_image)
        gtk_cell_layout_reorder(GTK_CELL_LAYOUT(ih->handle), renderer_img, IUPGTK_LIST_IMAGE);
    }
    else
    {
      GtkWidget *toggle = NULL;
      Ihandle* native_parent = iupChildTreeGetNativeParent(ih);

      /* had to add an event box so it can be positioned in an IupCanvas based control */
      if (native_parent->iclass->nativetype == IUP_TYPECANVAS)
      {
        GtkWidget *box = gtk_event_box_new();
        gtk_container_add((GtkContainer*)box, ih->handle);
        iupAttribSet(ih, "_IUP_EXTRAPARENT", (char*)box);
      }

      renderer = gtk_cell_renderer_text_new();
      gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ih->handle), renderer, TRUE);
      gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ih->handle), renderer, "text", IUPGTK_LIST_TEXT, NULL);

      /* use the internal toggle for keyboard, focus and enter/leave */
      gtk_container_forall((GtkContainer*)ih->handle, gtkComboBoxChildrenToggleCb, &toggle);

      if (toggle)
      {
        g_signal_connect(G_OBJECT(toggle), "focus-in-event", G_CALLBACK(gtkListComboFocusInOutEvent), ih);
        g_signal_connect(G_OBJECT(toggle), "focus-out-event", G_CALLBACK(gtkListComboFocusInOutEvent), ih);
        g_signal_connect(G_OBJECT(toggle), "enter-notify-event", G_CALLBACK(gtkListComboEnterLeaveEvent), ih);
        g_signal_connect(G_OBJECT(toggle), "leave-notify-event", G_CALLBACK(gtkListComboEnterLeaveEvent), ih);
        g_signal_connect(G_OBJECT(toggle), "key-press-event", G_CALLBACK(iupgtkKeyPressEvent), ih);
        g_signal_connect(G_OBJECT(toggle), "show-help", G_CALLBACK(iupgtkShowHelp), ih);
      }
      else
      {
        /* TODO: dummy code, actually it is NOT working */
        g_signal_connect(G_OBJECT(ih->handle), "focus-in-event", G_CALLBACK(iupgtkFocusInOutEvent), ih);
        g_signal_connect(G_OBJECT(ih->handle), "focus-out-event", G_CALLBACK(iupgtkFocusInOutEvent), ih);
        g_signal_connect(G_OBJECT(ih->handle), "enter-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
        g_signal_connect(G_OBJECT(ih->handle), "leave-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
        g_signal_connect(G_OBJECT(ih->handle), "key-press-event", G_CALLBACK(iupgtkKeyPressEvent), ih);
        g_signal_connect(G_OBJECT(ih->handle), "show-help", G_CALLBACK(iupgtkShowHelp), ih);
      }

      if (!iupAttribGetBoolean(ih, "CANFOCUS"))
      {
        iupgtkSetCanFocus(ih->handle, 0);
#if GTK_CHECK_VERSION(3, 20, 0)
        gtk_widget_set_focus_on_click(ih->handle, FALSE);
#else
        gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(ih->handle), FALSE);
#endif
      }
      else
      {
#if GTK_CHECK_VERSION(3, 20, 0)
        gtk_widget_set_focus_on_click(ih->handle, TRUE);
#else
        gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(ih->handle), TRUE);
#endif
        iupgtkSetCanFocus(ih->handle, 1);
      }
    }

    g_signal_connect(ih->handle, "changed", G_CALLBACK(gtkListComboBoxChanged), ih);
    g_signal_connect(ih->handle, "notify::popup-shown", G_CALLBACK(gtkListComboBoxPopupShown), ih);

    if (renderer)
    {
#if GTK_CHECK_VERSION(2, 18, 0)
      gtk_cell_renderer_set_padding(renderer, 0, 0);
#else
      renderer->xpad = 0;
      renderer->ypad = 0;
#endif
      iupAttribSet(ih, "_IUPGTK_RENDERER", (char*)renderer);
    }
  }
  else
  {
    GtkTreeSelection* selection;
    GtkTreeViewColumn *column;
    GtkPolicyType scrollbar_policy;
    GtkTreeModel* model;

    /* Virtual mode: use custom virtual model instead of GtkListStore */
    if (ih->data->is_virtual)
    {
      IupGtkVirtualListModel* virtual_model = iup_gtk_virtual_list_model_new(ih);
      model = GTK_TREE_MODEL(virtual_model);
      iupAttribSet(ih, "_IUPGTK_VIRTUAL_MODEL", (char*)virtual_model);
      g_object_unref(store);  /* Don't need the regular store */
    }
    else
    {
      model = GTK_TREE_MODEL(store);
    }

    ih->handle = gtk_tree_view_new_with_model(model);
    if (!ih->data->is_virtual)
      g_object_unref(store);

    if (!ih->handle)
      return IUP_ERROR;

    scrolled_window = (GtkScrolledWindow*)gtk_scrolled_window_new(NULL, NULL);

    /* Track scrolled window size allocation for VISIBLELINES clamping */
    g_signal_connect(G_OBJECT(scrolled_window), "size-allocate", G_CALLBACK(gtkListScrolledWindowSizeAllocate), ih);

    if (ih->data->has_editbox)
    {
#if GTK_CHECK_VERSION(3, 0, 0)
      GtkBox* vbox = (GtkBox*)gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
      gtk_box_set_homogeneous(vbox, FALSE);
      gtk_widget_set_vexpand((GtkWidget*)vbox, FALSE);
      gtk_widget_set_hexpand((GtkWidget*)vbox, FALSE);
#else
      GtkBox* vbox = (GtkBox*)gtk_vbox_new(FALSE, 0);
#endif

      GtkWidget *entry = gtk_entry_new();
      gtk_widget_show(entry);
#if GTK_CHECK_VERSION(3, 0, 0)
      gtk_widget_set_vexpand(entry, FALSE);
      gtk_widget_set_hexpand(entry, FALSE);
#endif

      gtk_box_pack_start(vbox, entry, FALSE, FALSE, 0);
      iupAttribSet(ih, "_IUPGTK_ENTRY", (char*)entry);

      gtk_widget_show((GtkWidget*)vbox);

      /* Pack scrolled_window into vbox */
#if GTK_CHECK_VERSION(3, 0, 0)
      gtk_widget_set_vexpand((GtkWidget*)scrolled_window, TRUE);
#endif
      gtk_box_pack_end(vbox, (GtkWidget*)scrolled_window, TRUE, TRUE, 0);
      iupAttribSet(ih, "_IUP_EXTRAPARENT", (char*)vbox);
      iupAttribSet(ih, "_IUPGTK_SCROLLED_WINDOW", (char*)scrolled_window);

      iupgtkSetCanFocus(ih->handle, 0);  /* focus goes only to the edit box */
      if (!iupAttribGetBoolean(ih, "CANFOCUS"))
        iupgtkSetCanFocus(entry, 0);

      g_signal_connect(G_OBJECT(entry), "focus-in-event",     G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(entry), "focus-out-event",    G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(entry), "enter-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(entry), "leave-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(entry), "show-help",          G_CALLBACK(iupgtkShowHelp), ih);

      g_signal_connect(G_OBJECT(entry), "delete-text", G_CALLBACK(gtkListEditDeleteText), ih);
      g_signal_connect(G_OBJECT(entry), "insert-text", G_CALLBACK(gtkListEditInsertText), ih);
      g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(gtkListEditChanged), ih);
      g_signal_connect_after(G_OBJECT(entry), "move-cursor", G_CALLBACK(gtkListEditMoveCursor), ih);  /* only report some caret movements */
      g_signal_connect(G_OBJECT(entry), "key-press-event",    G_CALLBACK(gtkListEditKeyPressEvent), ih);
      g_signal_connect_after(G_OBJECT(entry), "key-release-event", G_CALLBACK(gtkListEditKeyReleaseEvent), ih);
      g_signal_connect(G_OBJECT(entry), "button-press-event", G_CALLBACK(gtkListEditButtonEvent), ih);  /* if connected "after" then it is ignored */
      g_signal_connect(G_OBJECT(entry), "button-release-event",G_CALLBACK(gtkListEditButtonEvent), ih);
    }
    else
    {
      iupAttribSet(ih, "_IUP_EXTRAPARENT", (char*)scrolled_window);

      if (!iupAttribGetBoolean(ih, "CANFOCUS"))
        iupgtkSetCanFocus(ih->handle, 0);

      g_signal_connect(G_OBJECT(ih->handle), "focus-in-event",     G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(ih->handle), "focus-out-event",    G_CALLBACK(iupgtkFocusInOutEvent), ih);
      g_signal_connect(G_OBJECT(ih->handle), "enter-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(ih->handle), "leave-notify-event", G_CALLBACK(iupgtkEnterLeaveEvent), ih);
      g_signal_connect(G_OBJECT(ih->handle), "key-press-event",    G_CALLBACK(gtkListSimpleKeyPressEvent), ih);
      g_signal_connect(G_OBJECT(ih->handle), "show-help",          G_CALLBACK(iupgtkShowHelp), ih);
    }

    column = gtk_tree_view_column_new();

    if (ih->data->show_image)
    {
      renderer_img = gtk_cell_renderer_pixbuf_new();
      gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer_img, FALSE);
      gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer_img, "pixbuf", IUPGTK_LIST_IMAGE, NULL);
      iupAttribSet(ih, "_IUPGTK_RENDERER_IMG", (char*)renderer_img);
    }

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), renderer, TRUE);
    if (ih->data->is_virtual)
    {
      /* Virtual mode: use cell data func instead of attributes to avoid GTK querying all rows */
      gtk_tree_view_column_set_cell_data_func(column, renderer, gtkListVirtualCellDataFunc, ih, NULL);

      /* Use FIXED sizing to prevent GTK from measuring all rows for column width */
      gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);

      /* Enable fixed height mode, requires all columns to be FIXED type.
         This prevents GTK from iterating all rows during validation. */
      gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(ih->handle), TRUE);
    }
    else
    {
      gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(column), renderer, "text", IUPGTK_LIST_TEXT, NULL);
    }
    iupAttribSet(ih, "_IUPGTK_RENDERER", (char*)renderer);
    g_object_set(G_OBJECT(renderer), "xpad", 0, NULL);
    g_object_set(G_OBJECT(renderer), "ypad", 0, NULL);

    gtk_tree_view_append_column(GTK_TREE_VIEW(ih->handle), column);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ih->handle), FALSE);
    gtk_tree_view_set_enable_search(GTK_TREE_VIEW(ih->handle), FALSE);   /* TODO: check "start-interactive-search" signal */

    gtk_container_add((GtkContainer*)scrolled_window, ih->handle);
    gtk_widget_show((GtkWidget*)scrolled_window);
    gtk_scrolled_window_set_shadow_type(scrolled_window, GTK_SHADOW_IN);

    /* VISIBLELINES requires AUTOMATIC policy for min/max_content_height to work */
    int visiblelines = iupAttribGetInt(ih, "VISIBLELINES");

    if (visiblelines > 0)
    {
      /* GTK3 only applies min/max_content_height when policy is AUTOMATIC (not NEVER or ALWAYS) */
      scrollbar_policy = GTK_POLICY_AUTOMATIC;
    }
    else if (ih->data->sb)
    {
      if (iupAttribGetBoolean(ih, "AUTOHIDE"))
        scrollbar_policy = GTK_POLICY_AUTOMATIC;
      else
        scrollbar_policy = GTK_POLICY_ALWAYS;
    }
    else
    {
      scrollbar_policy = GTK_POLICY_NEVER;
    }

    gtk_scrolled_window_set_policy(scrolled_window, scrollbar_policy, scrollbar_policy);

    /* Mark container with VISIBLELINES flag so iupgtkSetPosSize can set size correctly */
    {
      int visiblelines = iupAttribGetInt(ih, "VISIBLELINES");
      if (visiblelines > 0)
      {
        GtkWidget* container;

        /* Get the container that IUP's layout will call iupgtkSetPosSize on */
        container = (GtkWidget*)iupAttribGet(ih, "_IUP_EXTRAPARENT");
        if (!container)
          container = (GtkWidget*)scrolled_window;

        /* Mark container so iupgtkSetPosSize will use IUP's calculated height directly */
        g_object_set_data(G_OBJECT(container), "iup-visiblelines-set", (gpointer)"1");

        /* For editbox lists, also mark the scrolled_window */
        if (ih->data->has_editbox)
        {
          GtkWidget* sw = (GtkWidget*)iupAttribGet(ih, "_IUPGTK_SCROLLED_WINDOW");
          if (sw)
            g_object_set_data(G_OBJECT(sw), "iup-visiblelines-scrolled", (gpointer)"1");
        }
      }
    }

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ih->handle));
    if (!ih->data->has_editbox && ih->data->is_multiple)
    {
      gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
  #if GTK_CHECK_VERSION(2, 10, 0)
      gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(ih->handle), TRUE);
  #endif
    }
    else
      gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);

    g_signal_connect(G_OBJECT(selection), "changed",  G_CALLBACK(gtkListSelectionChanged), ih);
    g_signal_connect(G_OBJECT(ih->handle), "row-activated", G_CALLBACK(gtkListRowActivated), ih);
    g_signal_connect(G_OBJECT(ih->handle), "motion-notify-event",G_CALLBACK(iupgtkMotionNotifyEvent), ih);
    g_signal_connect(G_OBJECT(ih->handle), "button-press-event", G_CALLBACK(iupgtkButtonEvent), ih);
    g_signal_connect(G_OBJECT(ih->handle), "button-release-event", G_CALLBACK(iupgtkButtonEvent), ih);
  }

  /* Enable internal drag and drop support */
  if(ih->data->show_dragdrop && !ih->data->is_dropdown && !ih->data->is_multiple)
    gtkListEnableDragDrop(ih);

  if (iupAttribGetBoolean(ih, "SORT") && !ih->data->is_virtual)
  {
    GtkTreeModel* tree_model = gtkListGetModel(ih);
    if (GTK_IS_LIST_STORE(tree_model))
      gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(tree_model), IUPGTK_LIST_TEXT, GTK_SORT_ASCENDING);
  }

  /* add to the parent, all GTK controls must call this. */
  iupgtkAddToParent(ih);

  if (scrolled_window)
    gtk_widget_realize((GtkWidget*)scrolled_window);
  gtk_widget_realize(ih->handle);

  /* configure for DRAG&DROP */
  if (IupGetCallback(ih, "DROPFILES_CB"))
    iupAttribSet(ih, "DROPFILESTARGET", "YES");

  IupSetCallback(ih, "_IUP_XY2POS_CB", (Icallback)gtkListConvertXYToPos);

  /* Don't populate items in virtual mode */
  if (!ih->data->is_virtual)
    iupListSetInitialItems(ih);

  /* update a mnemonic in a label if necessary */
  iupgtkUpdateMnemonic(ih);

  return IUP_NOERROR;
}

static void gtkListUnMapMethod(Ihandle* ih)
{
  /* For virtual mode, detach model before destruction to avoid expensive iteration */
  if (ih->data->is_virtual && ih->handle && GTK_IS_TREE_VIEW(ih->handle))
  {
    IupGtkVirtualListModel* model = (IupGtkVirtualListModel*)iupAttribGet(ih, "_IUPGTK_VIRTUAL_MODEL");
    if (model)
      model->count = 0;  /* Set to 0 so GTK sees empty model during detach */
    gtk_tree_view_set_model(GTK_TREE_VIEW(ih->handle), NULL);
  }

  iupdrvBaseUnMapMethod(ih);
}

void iupdrvListInitClass(Iclass* ic)
{
  /* Driver Dependent Class functions */
  ic->Map = gtkListMapMethod;
  ic->UnMap = gtkListUnMapMethod;

  /* Driver Dependent Attribute functions */

  /* Overwrite Common */
  iupClassRegisterAttribute(ic, "FONT", NULL, gtkListSetFontAttrib, IUPAF_SAMEASSYSTEM, "DEFAULTFONT", IUPAF_NOT_MAPPED);  /* inherited */

  /* Visual */
  iupClassRegisterAttribute(ic, "BGCOLOR", NULL, gtkListSetBgColorAttrib, IUPAF_SAMEASSYSTEM, "TXTBGCOLOR", IUPAF_DEFAULT);

  /* Special */
  iupClassRegisterAttribute(ic, "FGCOLOR", NULL, gtkListSetFgColorAttrib, IUPAF_SAMEASSYSTEM, "TXTFGCOLOR", IUPAF_DEFAULT);

  /* IupList only */
  iupClassRegisterAttributeId(ic, "IDVALUE", gtkListGetIdValueAttrib, iupListSetIdValueAttrib, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "VALUE", gtkListGetValueAttrib, gtkListSetValueAttrib, NULL, NULL, IUPAF_NO_DEFAULTVALUE|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SHOWDROPDOWN", NULL, gtkListSetShowDropdownAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TOPITEM", NULL, gtkListSetTopItemAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SPACING", iupListGetSpacingAttrib, gtkListSetSpacingAttrib, IUPAF_SAMEASSYSTEM, "0", IUPAF_NOT_MAPPED);

  iupClassRegisterAttribute(ic, "PADDING", iupListGetPaddingAttrib, gtkListSetPaddingAttrib, IUPAF_SAMEASSYSTEM, "0x0", IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "SELECTEDTEXT", gtkListGetSelectedTextAttrib, gtkListSetSelectedTextAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SELECTION", gtkListGetSelectionAttrib, gtkListSetSelectionAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SELECTIONPOS", gtkListGetSelectionPosAttrib, gtkListSetSelectionPosAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CARET", gtkListGetCaretAttrib, gtkListSetCaretAttrib, NULL, NULL, IUPAF_NO_SAVE|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CARETPOS", gtkListGetCaretPosAttrib, gtkListSetCaretPosAttrib, IUPAF_SAMEASSYSTEM, "0", IUPAF_NO_SAVE|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "INSERT", NULL, gtkListSetInsertAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "APPEND", NULL, gtkListSetAppendAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "READONLY", gtkListGetReadOnlyAttrib, gtkListSetReadOnlyAttrib, NULL, NULL, IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "NC", iupListGetNCAttrib, gtkListSetNCAttrib, NULL, NULL, IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "CLIPBOARD", NULL, gtkListSetClipboardAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SCROLLTO", NULL, gtkListSetScrollToAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SCROLLTOPOS", NULL, gtkListSetScrollToPosAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);

  iupClassRegisterAttributeId(ic, "IMAGE", NULL, gtkListSetImageAttrib, IUPAF_IHANDLENAME|IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId(ic, "IMAGENATIVEHANDLE", gtkListGetImageNativeHandleAttribId, NULL, IUPAF_NO_STRING | IUPAF_READONLY | IUPAF_NO_INHERIT);

  /* Not Supported */
  iupClassRegisterAttribute(ic, "VISIBLEITEMS", NULL, NULL, IUPAF_SAMEASSYSTEM, "5", IUPAF_NOT_SUPPORTED);
  iupClassRegisterAttribute(ic, "DROPEXPAND", NULL, NULL, IUPAF_SAMEASSYSTEM, "Yes", IUPAF_NOT_SUPPORTED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "AUTOREDRAW", NULL, NULL, IUPAF_SAMEASSYSTEM, "Yes", IUPAF_NOT_SUPPORTED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SCROLLVISIBLE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_NO_INHERIT);
}
