Blob Blame History Raw
/*
 * Copyright (C) 2008 Tristan Van Berkom.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors:
 *   Tristan Van Berkom <tvb@gnome.org>
 */

#include <config.h>
#include <gladeui/glade.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>

#include "glade-cell-renderer-editor.h"
#include "glade-column-types.h"


static void glade_cell_renderer_editor_finalize (GObject * object);

static void glade_cell_renderer_editor_editable_init (GladeEditableIface *
                                                      iface);

static void glade_cell_renderer_editor_grab_focus (GtkWidget * widget);


typedef struct
{
  GladeCellRendererEditor *editor;

  GtkWidget *attributes_check;
  GladePropertyClass *pclass;
  GladePropertyClass *attr_pclass;
  GladePropertyClass *use_attr_pclass;

  GtkWidget *use_prop_label;
  GtkWidget *use_attr_label;
  GtkWidget *use_prop_eprop;
  GtkWidget *use_attr_eprop;
} CheckTab;

static GladeEditableIface *parent_editable_iface;

G_DEFINE_TYPE_WITH_CODE (GladeCellRendererEditor, glade_cell_renderer_editor,
                         GTK_TYPE_BOX,
                         G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE,
                                                glade_cell_renderer_editor_editable_init));


static void
glade_cell_renderer_editor_class_init (GladeCellRendererEditorClass * klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = glade_cell_renderer_editor_finalize;
  widget_class->grab_focus = glade_cell_renderer_editor_grab_focus;
}

static void
glade_cell_renderer_editor_init (GladeCellRendererEditor * self)
{
  gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
				  GTK_ORIENTATION_VERTICAL);
}

static void
glade_cell_renderer_editor_load (GladeEditable * editable, GladeWidget * widget)
{
  GladeCellRendererEditor *renderer_editor =
      GLADE_CELL_RENDERER_EDITOR (editable);
  GList *l;

  /* Chain up to default implementation */
  parent_editable_iface->load (editable, widget);

  /* load the embedded editable... */
  if (renderer_editor->embed)
    glade_editable_load (GLADE_EDITABLE (renderer_editor->embed), widget);

  for (l = renderer_editor->properties; l; l = l->next)
    glade_editor_property_load_by_widget (GLADE_EDITOR_PROPERTY (l->data),
                                          widget);

  if (widget)
    {
      for (l = renderer_editor->checks; l; l = l->next)
        {
          CheckTab *tab = l->data;
          gboolean use_attr = FALSE;

          glade_widget_property_get (widget, glade_property_class_id (tab->use_attr_pclass), &use_attr);
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab->attributes_check), use_attr);

          if (use_attr)
            {
              //gtk_widget_show (tab->use_attr_label);
              gtk_widget_show (tab->use_attr_eprop);
              //gtk_widget_hide (tab->use_prop_label);
              gtk_widget_hide (tab->use_prop_eprop);
            }
          else
            {
              gtk_widget_show (tab->use_prop_label);
              gtk_widget_show (tab->use_prop_eprop);
              gtk_widget_hide (tab->use_attr_label);
              gtk_widget_hide (tab->use_attr_eprop);
            }
        }
    }
}

static void
glade_cell_renderer_editor_set_show_name (GladeEditable * editable,
                                          gboolean show_name)
{
  GladeCellRendererEditor *renderer_editor =
      GLADE_CELL_RENDERER_EDITOR (editable);

  glade_editable_set_show_name (GLADE_EDITABLE (renderer_editor->embed),
                                show_name);
}

static void
glade_cell_renderer_editor_editable_init (GladeEditableIface * iface)
{
  parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE);

  iface->load = glade_cell_renderer_editor_load;
  iface->set_show_name = glade_cell_renderer_editor_set_show_name;
}

static void
glade_cell_renderer_editor_finalize (GObject * object)
{
  GladeCellRendererEditor *renderer_editor =
      GLADE_CELL_RENDERER_EDITOR (object);

  g_list_foreach (renderer_editor->checks, (GFunc) g_free, NULL);
  g_list_free (renderer_editor->checks);
  g_list_free (renderer_editor->properties);

  renderer_editor->properties = NULL;
  renderer_editor->checks = NULL;
  renderer_editor->embed = NULL;

  glade_editable_load (GLADE_EDITABLE (object), NULL);

  G_OBJECT_CLASS (glade_cell_renderer_editor_parent_class)->finalize (object);
}

static void
glade_cell_renderer_editor_grab_focus (GtkWidget * widget)
{
  GladeCellRendererEditor *renderer_editor =
      GLADE_CELL_RENDERER_EDITOR (widget);

  gtk_widget_grab_focus (renderer_editor->embed);
}

static void
attributes_toggled (GtkWidget * widget, CheckTab * tab)
{
  GladeCellRendererEditor *renderer_editor = tab->editor;
  GladeProperty *property;
  GladeWidget   *gwidget;
  GValue value = { 0, };

  gwidget = glade_editable_loaded_widget (GLADE_EDITABLE (renderer_editor));

  if (glade_editable_loading (GLADE_EDITABLE (renderer_editor)) || !gwidget)
    return;

  glade_editable_block (GLADE_EDITABLE (renderer_editor));

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tab->attributes_check)))
    {

      glade_command_push_group (_("Setting %s to use the %s property as an attribute"),
                                glade_widget_get_name (gwidget),
                                glade_property_class_id (tab->pclass));


      property =
          glade_widget_get_property (gwidget, glade_property_class_id (tab->pclass));
      glade_property_get_default (property, &value);
      glade_command_set_property_value (property, &value);
      g_value_unset (&value);

      property =
          glade_widget_get_property (gwidget, glade_property_class_id (tab->use_attr_pclass));
      glade_command_set_property (property, TRUE);

      glade_command_pop_group ();


    }
  else
    {
      glade_command_push_group (_("Setting %s to use the %s property directly"),
                                glade_widget_get_name (gwidget),
                                glade_property_class_id (tab->pclass));

      property =
          glade_widget_get_property (gwidget, glade_property_class_id (tab->attr_pclass));
      glade_property_get_default (property, &value);
      glade_command_set_property_value (property, &value);
      g_value_unset (&value);

      property =
          glade_widget_get_property (gwidget, glade_property_class_id (tab->use_attr_pclass));
      glade_command_set_property (property, FALSE);

      glade_command_pop_group ();
    }

  glade_editable_unblock (GLADE_EDITABLE (renderer_editor));

  /* reload buttons and sensitivity and stuff... */
  glade_editable_load (GLADE_EDITABLE (renderer_editor), gwidget);
}

static gint
property_class_comp (gconstpointer a, gconstpointer b)
{
  GladePropertyClass *ca = (GladePropertyClass *)a, *cb = (GladePropertyClass *)b;
  GParamSpec *pa, *pb;

  pa = glade_property_class_get_pspec (ca);
  pb = glade_property_class_get_pspec (cb);

  if (pa->owner_type == pb->owner_type)
    {
      gdouble result = glade_property_class_weight (ca) - glade_property_class_weight (cb);
      /* Avoid cast to int */
      if (result < 0.0)
        return -1;
      else if (result > 0.0)
        return 1;
      else
        return 0;
    }
  else
    {
      if (g_type_is_a (pa->owner_type, pb->owner_type))
        return (glade_property_class_common (ca) || glade_property_class_get_is_packing (ca)) ? 1 : -1;
      else
        return (glade_property_class_common (ca) || glade_property_class_get_is_packing (ca)) ? -1 : 1;
    }
}

static GList *
get_sorted_properties (GladeWidgetAdaptor * adaptor, GladeEditorPageType type)
{
  GList *list = NULL;
  const GList *l;

  for (l = glade_widget_adaptor_get_properties (adaptor); l; l = l->next)
    {
      GladePropertyClass *klass = l->data;

      if (GLADE_PROPERTY_CLASS_IS_TYPE (klass, type) &&
          (glade_property_class_is_visible (klass)))
        {
          list = g_list_prepend (list, klass);
        }
    }
  return g_list_sort (list, property_class_comp);
}


GtkWidget *
glade_cell_renderer_editor_new (GladeWidgetAdaptor * adaptor,
                                GladeEditorPageType type, GladeEditable * embed)
{
  GladeCellRendererEditor *renderer_editor;
  GladeEditorProperty *eprop;
  GladePropertyClass *pclass, *attr_pclass, *use_attr_pclass;
  GList *list, *sorted;
  GtkWidget *hbox_left, *hbox_right, *grid;
  gchar *str;
  gint row = 0;

  g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL);
  g_return_val_if_fail (GLADE_IS_EDITABLE (embed), NULL);

  renderer_editor = g_object_new (GLADE_TYPE_CELL_RENDERER_EDITOR, NULL);
  renderer_editor->embed = GTK_WIDGET (embed);

  /* Pack the parent on top... */
  gtk_box_pack_start (GTK_BOX (renderer_editor), GTK_WIDGET (embed), FALSE,
                      FALSE, 0);

  /* Next pack in a grid for all the renderers */
  grid = gtk_grid_new ();
  gtk_orientable_set_orientation (GTK_ORIENTABLE (grid),
                                  GTK_ORIENTATION_VERTICAL);
  gtk_grid_set_row_spacing (GTK_GRID (grid), 4);
  gtk_box_pack_start (GTK_BOX (renderer_editor), grid, FALSE, FALSE, 0);

  sorted = get_sorted_properties (adaptor, type);

  /* For each normal property, if we have an attr- and use-attr- counterpart, load
   * a check button property pair into the table...
   */
  for (list = sorted; list; list = list->next)
    {
      gchar *attr_name;
      gchar *use_attr_name;

      pclass = list->data;

      /* "stock-size" is a normal property, but we virtualize it to use the GtkIconSize enumeration */
      if (glade_property_class_get_virtual (pclass) &&
	  strcmp (glade_property_class_id (pclass), "stock-size") != 0)
	continue;

      attr_name = g_strdup_printf ("attr-%s", glade_property_class_id (pclass));
      use_attr_name = g_strdup_printf ("use-attr-%s", glade_property_class_id (pclass));

      attr_pclass =
          glade_widget_adaptor_get_property_class (adaptor, attr_name);
      use_attr_pclass =
          glade_widget_adaptor_get_property_class (adaptor, use_attr_name);

      if (attr_pclass && use_attr_pclass)
        {
          CheckTab *tab = g_new0 (CheckTab, 1);
	  GParamSpec *pspec;

	  pspec = glade_property_class_get_pspec (pclass);

          tab->editor = renderer_editor;
          tab->pclass = pclass;
          tab->attr_pclass = attr_pclass;
          tab->use_attr_pclass = use_attr_pclass;

          /* Label appearance... */
          hbox_left = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
          hbox_right = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
          gtk_widget_set_hexpand (hbox_right, TRUE);

          tab->attributes_check = gtk_check_button_new ();
          str = g_strdup_printf (_("Retrieve %s from model (type %s)"),
                                 glade_property_class_get_name (pclass),
                                 g_type_name (pspec->value_type));
          gtk_widget_set_tooltip_text (tab->attributes_check, str);
          g_free (str);

          gtk_box_pack_start (GTK_BOX (hbox_left), tab->attributes_check, FALSE,
                              FALSE, 4);

          /* Edit property */
          eprop = glade_widget_adaptor_create_eprop (adaptor, pclass, TRUE);
          gtk_box_pack_start (GTK_BOX (hbox_left), glade_editor_property_get_item_label (eprop), TRUE,
                              TRUE, 4);
          gtk_box_pack_start (GTK_BOX (hbox_right), GTK_WIDGET (eprop), FALSE,
                              FALSE, 4);
          renderer_editor->properties =
              g_list_prepend (renderer_editor->properties, eprop);

          tab->use_prop_label = glade_editor_property_get_item_label (eprop);
          tab->use_prop_eprop = GTK_WIDGET (eprop);

          /* Edit attribute */
          eprop =
              glade_widget_adaptor_create_eprop (adaptor, attr_pclass, TRUE);
          gtk_box_pack_start (GTK_BOX (hbox_right), GTK_WIDGET (eprop), FALSE,
                              FALSE, 4);
          renderer_editor->properties =
              g_list_prepend (renderer_editor->properties, eprop);

          gtk_grid_attach (GTK_GRID (grid), hbox_left, 0, row, 1, 1);
          gtk_grid_attach (GTK_GRID (grid), hbox_right, 1, row++, 1, 1);

          tab->use_attr_label = glade_editor_property_get_item_label (eprop);
          tab->use_attr_eprop = GTK_WIDGET (eprop);

          g_signal_connect (G_OBJECT (tab->attributes_check), "toggled",
                            G_CALLBACK (attributes_toggled), tab);

          renderer_editor->checks =
              g_list_prepend (renderer_editor->checks, tab);
        }
      g_free (attr_name);
      g_free (use_attr_name);
    }
  g_list_free (sorted);

  gtk_widget_show_all (GTK_WIDGET (renderer_editor));

  return GTK_WIDGET (renderer_editor);
}

/***************************************************************************
 *                             Editor Property                             *
 ***************************************************************************/
typedef struct
{
  GladeEditorProperty parent_instance;

  GtkTreeModel *columns;

  GtkWidget *spin;
  GtkWidget *combo;
} GladeEPropCellAttribute;

GLADE_MAKE_EPROP (GladeEPropCellAttribute, glade_eprop_cell_attribute)
#define GLADE_EPROP_CELL_ATTRIBUTE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttribute))
#define GLADE_EPROP_CELL_ATTRIBUTE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttributeClass))
#define GLADE_IS_EPROP_CELL_ATTRIBUTE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_EPROP_CELL_ATTRIBUTE))
#define GLADE_IS_EPROP_CELL_ATTRIBUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_EPROP_CELL_ATTRIBUTE))
#define GLADE_EPROP_CELL_ATTRIBUTE_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), GLADE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttributeClass))
static void
glade_eprop_cell_attribute_finalize (GObject *object)
{
  /* Chain up */
  GObjectClass *parent_class =
      g_type_class_peek_parent (G_OBJECT_GET_CLASS (object));
  //GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (object);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static GladeWidget *
glade_cell_renderer_parent_get_model (GladeWidget *widget)
{
  GtkTreeModel *model = NULL;
  
  glade_widget_property_get (widget, "model", &model);

  do
    {
      if (GTK_IS_TREE_MODEL_SORT (model))
        model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
      else if (GTK_IS_TREE_MODEL_FILTER (model))
        model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
      else
        break;
    } while (model);

  if (model)
    return glade_widget_get_from_gobject (model);

  return NULL;
}

GladeWidget *
glade_cell_renderer_get_model (GladeWidget * renderer)
{
  GladeWidget *gparent;
  GObject *parent;

  if ((gparent = glade_widget_get_parent (renderer)) == NULL)
    return NULL;

  parent = glade_widget_get_object (gparent);
  
  /* Keep inline with all new cell layouts !!! */
  if (GTK_IS_TREE_VIEW_COLUMN (parent))
    {
      GladeWidget *treeview = glade_widget_get_parent (gparent);

      if (treeview && GTK_IS_TREE_VIEW (glade_widget_get_object (treeview)))
        return glade_cell_renderer_parent_get_model (treeview);
    }
  else if (GTK_IS_ICON_VIEW (parent) || GTK_IS_COMBO_BOX (parent) ||
           GTK_IS_ENTRY_COMPLETION (parent))
    return glade_cell_renderer_parent_get_model (gparent);

  return NULL;
}

static void
glade_eprop_cell_attribute_load (GladeEditorProperty * eprop,
                                 GladeProperty * property)
{
  GladeEditorPropertyClass *parent_class =
      g_type_class_peek_parent (GLADE_EDITOR_PROPERTY_GET_CLASS (eprop));
  GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (eprop);

  /* Chain up in a clean state... */
  parent_class->load (eprop, property);

  if (property)
    {
      GladeWidget *gmodel;
      GtkListStore *store = GTK_LIST_STORE (eprop_attribute->columns);
      GtkTreeIter iter;

      gtk_list_store_clear (store);

      /* Generate model and set active iter */
      if ((gmodel = glade_cell_renderer_get_model (glade_property_get_widget (property))) != NULL)
        {
          GList *columns = NULL, *l;

          glade_widget_property_get (gmodel, "columns", &columns);

          gtk_list_store_append (store, &iter);
          /* translators: the adjective not the verb */
          gtk_list_store_set (store, &iter, 0, _("unset"), -1);

          for (l = columns; l; l = l->next)
            {
              GladeColumnType *column = l->data;
              gchar *str = g_strdup_printf ("%s - %s", column->column_name,
                                            column->type_name);

              gtk_list_store_append (store, &iter);
              gtk_list_store_set (store, &iter, 0, str, -1);

              g_free (str);
            }

          gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_attribute->combo),
                                    CLAMP (g_value_get_int (glade_property_inline_value (property)) +
                                           1, 0, g_list_length (columns) + 1));

          gtk_widget_set_sensitive (eprop_attribute->combo, TRUE);
        }
      else
        {
          gtk_list_store_append (store, &iter);
          gtk_list_store_set (store, &iter, 0, _("no model"), -1);
          gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_attribute->combo), 0);
          gtk_widget_set_sensitive (eprop_attribute->combo, FALSE);
        }

      gtk_spin_button_set_value (GTK_SPIN_BUTTON (eprop_attribute->spin),
                                 (gdouble) g_value_get_int (glade_property_inline_value (property)));
    }
}

static void
combo_changed (GtkWidget * combo, GladeEditorProperty * eprop)
{
  GValue val = { 0, };

  if (glade_editor_property_loading (eprop))
    return;

  g_value_init (&val, G_TYPE_INT);
  g_value_set_int (&val,
                   (gint) gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) - 1);
  glade_editor_property_commit (eprop, &val);
  g_value_unset (&val);
}


static void
spin_changed (GtkWidget * spin, GladeEditorProperty * eprop)
{
  GValue val = { 0, };

  if (glade_editor_property_loading (eprop))
    return;

  g_value_init (&val, G_TYPE_INT);
  g_value_set_int (&val, gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)));
  glade_editor_property_commit (eprop, &val);
  g_value_unset (&val);
}

static GtkWidget *
glade_eprop_cell_attribute_create_input (GladeEditorProperty * eprop)
{
  GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (eprop);
  GtkWidget *hbox;
  GtkAdjustment *adjustment;
  GtkCellRenderer *cell;

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);

  adjustment = glade_property_class_make_adjustment (glade_editor_property_get_pclass (eprop));
  eprop_attribute->spin = gtk_spin_button_new (adjustment, 1.0, 0);

  eprop_attribute->columns =
      (GtkTreeModel *) gtk_list_store_new (1, G_TYPE_STRING);
  eprop_attribute->combo =
      gtk_combo_box_new_with_model (eprop_attribute->columns);

  gtk_combo_box_set_popup_fixed_width (GTK_COMBO_BOX (eprop_attribute->combo),
                                       FALSE);

  /* Add cell renderer */
  cell = gtk_cell_renderer_text_new ();
  g_object_set (cell,
                "xpad", 0,
                "xalign", 0.0F,
                "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 10, NULL);

  gtk_cell_layout_clear (GTK_CELL_LAYOUT (eprop_attribute->combo));

  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (eprop_attribute->combo), cell,
                              TRUE);
  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (eprop_attribute->combo),
                                  cell, "text", 0, NULL);

  gtk_box_pack_start (GTK_BOX (hbox), eprop_attribute->spin, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), eprop_attribute->combo, FALSE, FALSE, 0);

  g_signal_connect (G_OBJECT (eprop_attribute->combo), "changed",
                    G_CALLBACK (combo_changed), eprop);
  g_signal_connect (G_OBJECT (eprop_attribute->spin), "value-changed",
                    G_CALLBACK (spin_changed), eprop);

  return hbox;
}