/*
 * Copyright © 2019 Benjamin Otte
 *
 * 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 library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Benjamin Otte <otte@gnome.org>
 */

#include "config.h"

#include "gtkcolumnviewcolumnprivate.h"
#include "gtkcolumnviewsorterprivate.h"

#include "gtkcolumnviewprivate.h"
#include "gtkcolumnviewrowwidgetprivate.h"
#include "gtkcolumnviewtitleprivate.h"
#include "gtklistbaseprivate.h"
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtkrbtreeprivate.h"
#include "gtksizegroup.h"
#include "gtkwidgetprivate.h"
#include "gtksorter.h"

/**
 * GtkColumnViewColumn:
 *
 * `GtkColumnViewColumn` represents the columns being added to a `GtkColumnView`.
 *
 * The main ingredient for a `GtkColumnViewColumn` is the `GtkListItemFactory`
 * that tells the columnview how to create cells for this column from items in
 * the model.
 *
 * Columns have a title, and can optionally have a header menu set
 * with [method@Gtk.ColumnViewColumn.set_header_menu].
 *
 * A sorter can be associated with a column using
 * [method@Gtk.ColumnViewColumn.set_sorter], to let users influence sorting
 * by clicking on the column header.
 */

struct _GtkColumnViewColumn
{
  GObject parent_instance;

  GtkListItemFactory *factory;
  char *title;
  char *id;
  GtkSorter *sorter;

  /* data for the view */
  GtkColumnView *view;
  GtkWidget *header;

  int minimum_size_request;
  int natural_size_request;
  int allocation_offset;
  int allocation_size;
  int header_position;

  int fixed_width;

  guint visible     : 1;
  guint resizable   : 1;
  guint expand      : 1;

  GMenuModel *menu;

  /* This list isn't sorted - this is just caching for performance */
  GtkColumnViewCellWidget *first_cell; /* no reference, just caching */
};

struct _GtkColumnViewColumnClass
{
  GObjectClass parent_class;
};

enum
{
  PROP_0,
  PROP_COLUMN_VIEW,
  PROP_FACTORY,
  PROP_TITLE,
  PROP_SORTER,
  PROP_VISIBLE,
  PROP_HEADER_MENU,
  PROP_RESIZABLE,
  PROP_EXPAND,
  PROP_FIXED_WIDTH,
  PROP_ID,

  N_PROPS
};

G_DEFINE_TYPE (GtkColumnViewColumn, gtk_column_view_column, G_TYPE_OBJECT)

static GParamSpec *properties[N_PROPS] = { NULL, };

static void
gtk_column_view_column_dispose (GObject *object)
{
  GtkColumnViewColumn *self = GTK_COLUMN_VIEW_COLUMN (object);

  g_assert (self->view == NULL); /* would hold a ref otherwise */
  g_assert (self->first_cell == NULL); /* no view = no children */

  g_clear_object (&self->factory);
  g_clear_object (&self->sorter);
  g_clear_pointer (&self->title, g_free);
  g_clear_object (&self->menu);
  g_clear_pointer (&self->id, g_free);

  G_OBJECT_CLASS (gtk_column_view_column_parent_class)->dispose (object);
}

static void
gtk_column_view_column_get_property (GObject    *object,
                                     guint       property_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  GtkColumnViewColumn *self = GTK_COLUMN_VIEW_COLUMN (object);

  switch (property_id)
    {
    case PROP_COLUMN_VIEW:
      g_value_set_object (value, self->view);
      break;

    case PROP_FACTORY:
      g_value_set_object (value, self->factory);
      break;

    case PROP_TITLE:
      g_value_set_string (value, self->title);
      break;

    case PROP_SORTER:
      g_value_set_object (value, self->sorter);
      break;

    case PROP_VISIBLE:
      g_value_set_boolean (value, self->visible);
      break;

    case PROP_HEADER_MENU:
      g_value_set_object (value, self->menu);
      break;

    case PROP_RESIZABLE:
      g_value_set_boolean (value, self->resizable);
      break;

    case PROP_EXPAND:
      g_value_set_boolean (value, self->expand);
      break;

    case PROP_FIXED_WIDTH:
      g_value_set_int (value, self->fixed_width);
      break;

    case PROP_ID:
      g_value_set_string (value, self->id);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_column_view_column_set_property (GObject      *object,
                                     guint         property_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
  GtkColumnViewColumn *self = GTK_COLUMN_VIEW_COLUMN (object);

  switch (property_id)
    {
    case PROP_FACTORY:
      gtk_column_view_column_set_factory (self, g_value_get_object (value));
      break;

    case PROP_TITLE:
      gtk_column_view_column_set_title (self, g_value_get_string (value));
      break;

    case PROP_SORTER:
      gtk_column_view_column_set_sorter (self, g_value_get_object (value));
      break;

    case PROP_VISIBLE:
      gtk_column_view_column_set_visible (self, g_value_get_boolean (value));
      break;

    case PROP_HEADER_MENU:
      gtk_column_view_column_set_header_menu (self, g_value_get_object (value));
      break;

    case PROP_RESIZABLE:
      gtk_column_view_column_set_resizable (self, g_value_get_boolean (value));
      break;

    case PROP_EXPAND:
      gtk_column_view_column_set_expand (self, g_value_get_boolean (value));
      break;

    case PROP_FIXED_WIDTH:
      gtk_column_view_column_set_fixed_width (self, g_value_get_int (value));
      break;

    case PROP_ID:
      gtk_column_view_column_set_id (self, g_value_get_string (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_column_view_column_class_init (GtkColumnViewColumnClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = gtk_column_view_column_dispose;
  gobject_class->get_property = gtk_column_view_column_get_property;
  gobject_class->set_property = gtk_column_view_column_set_property;

  /**
   * GtkColumnViewColumn:column-view: (attributes org.gtk.Property.get=gtk_column_view_column_get_column_view)
   *
   * The `GtkColumnView` this column is a part of.
   */
  properties[PROP_COLUMN_VIEW] =
    g_param_spec_object ("column-view", NULL, NULL,
                         GTK_TYPE_COLUMN_VIEW,
                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:factory: (attributes org.gtk.Property.get=gtk_column_view_column_get_factory org.gtk.Property.set=gtk_column_view_column_set_factory)
   *
   * Factory for populating list items.
   *
   * The factory must be for configuring [class@Gtk.ColumnViewCell] objects.
   */
  properties[PROP_FACTORY] =
    g_param_spec_object ("factory", NULL, NULL,
                         GTK_TYPE_LIST_ITEM_FACTORY,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:title: (attributes org.gtk.Property.get=gtk_column_view_column_get_title org.gtk.Property.set=gtk_column_view_column_set_title)
   *
   * Title displayed in the header.
   */
  properties[PROP_TITLE] =
    g_param_spec_string ("title", NULL, NULL,
                          NULL,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkColumnViewColumn:sorter: (attributes org.gtk.Property.get=gtk_column_view_column_get_sorter org.gtk.Property.set=gtk_column_view_column_set_sorter)
   *
   * Sorter for sorting items according to this column.
   */
  properties[PROP_SORTER] =
    g_param_spec_object ("sorter", NULL, NULL,
                         GTK_TYPE_SORTER,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:visible: (attributes org.gtk.Property.get=gtk_column_view_column_get_visible org.gtk.Property.set=gtk_column_view_column_set_visible)
   *
   * Whether this column is visible.
   */
  properties[PROP_VISIBLE] =
    g_param_spec_boolean ("visible", NULL, NULL,
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:header-menu: (attributes org.gtk.Property.get=gtk_column_view_column_get_header_menu org.gtk.Property.set=gtk_column_view_column_set_header_menu)
   *
   * Menu model used to create the context menu for the column header.
   */
  properties[PROP_HEADER_MENU] =
    g_param_spec_object ("header-menu", NULL, NULL,
                         G_TYPE_MENU_MODEL,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:resizable: (attributes org.gtk.Property.get=gtk_column_view_column_get_resizable org.gtk.Property.set=gtk_column_view_column_set_resizable)
   *
   * Whether this column is resizable.
   */
  properties[PROP_RESIZABLE] =
    g_param_spec_boolean ("resizable", NULL, NULL,
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:expand: (attributes org.gtk.Property.get=gtk_column_view_column_get_expand org.gtk.Property.set=gtk_column_view_column_set_expand)
   *
   * Column gets share of extra width allocated to the view.
   */
  properties[PROP_EXPAND] =
    g_param_spec_boolean ("expand", NULL, NULL,
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:fixed-width: (attributes org.gtk.Property.get=gtk_column_view_column_get_fixed_width org.gtk.Property.set=gtk_column_view_column_set_fixed_width)
   *
   * If not -1, this is the width that the column is allocated,
   * regardless of the size of its content.
   */
  properties[PROP_FIXED_WIDTH] =
    g_param_spec_int ("fixed-width", NULL, NULL,
                      -1, G_MAXINT, -1,
                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GtkColumnViewColumn:id: (attributes org.gtk.Property.get=gtk_column_view_column_get_id org.gtk.Property.set=gtk_column_view_column_set_id)
   *
   * An ID for the column.
   *
   * GTK is not currently using the ID for anything, but
   * it can be used by applications when saving column view
   * configurations.
   *
   * It is up to applications to ensure uniqueness of IDs.
   *
   * Since: 4.10
   */
  properties[PROP_ID] =
    g_param_spec_string ("id", NULL, NULL,
                          NULL,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (gobject_class, N_PROPS, properties);
}

static void
gtk_column_view_column_init (GtkColumnViewColumn *self)
{
  self->minimum_size_request = -1;
  self->natural_size_request = -1;
  self->visible = TRUE;
  self->resizable = FALSE;
  self->expand = FALSE;
  self->fixed_width = -1;
}

/**
 * gtk_column_view_column_new:
 * @title: (nullable): Title to use for this column
 * @factory: (transfer full) (nullable): The factory to populate items with
 *
 * Creates a new `GtkColumnViewColumn` that uses the given @factory for
 * mapping items to widgets.
 *
 * You most likely want to call [method@Gtk.ColumnView.append_column] next.
 *
 * The function takes ownership of the argument, so you can write code like:
 *
 * ```c
 * column = gtk_column_view_column_new (_("Name"),
 *   gtk_builder_list_item_factory_new_from_resource ("/name.ui"));
 * ```
 *
 * Returns: a new `GtkColumnViewColumn` using the given @factory
 */
GtkColumnViewColumn *
gtk_column_view_column_new (const char         *title,
                            GtkListItemFactory *factory)
{
  GtkColumnViewColumn *result;

  g_return_val_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory), NULL);

  result = g_object_new (GTK_TYPE_COLUMN_VIEW_COLUMN,
                         "factory", factory,
                         "title", title,
                         NULL);

  g_clear_object (&factory);

  return result;
}

GtkColumnViewCellWidget *
gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self)
{
  return self->first_cell;
}

void
gtk_column_view_column_add_cell (GtkColumnViewColumn *self,
                                 GtkColumnViewCellWidget   *cell)
{
  self->first_cell = cell;

  gtk_widget_set_visible (GTK_WIDGET (cell), self->visible);
  gtk_column_view_column_queue_resize (self);
}

void
gtk_column_view_column_remove_cell (GtkColumnViewColumn *self,
                                    GtkColumnViewCellWidget   *cell)
{
  if (cell == self->first_cell)
    self->first_cell = gtk_column_view_cell_widget_get_next (cell);

  gtk_column_view_column_queue_resize (self);
  gtk_widget_queue_resize (GTK_WIDGET (cell));
}

void
gtk_column_view_column_queue_resize (GtkColumnViewColumn *self)
{
  GtkColumnViewCellWidget *cell;

  if (self->minimum_size_request < 0)
    return;

  self->minimum_size_request = -1;
  self->natural_size_request = -1;

  if (self->header)
    gtk_widget_queue_resize (self->header);

  for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell))
    {
      gtk_widget_queue_resize (GTK_WIDGET (cell));
    }
}

void
gtk_column_view_column_measure (GtkColumnViewColumn *self,
                                int                 *minimum,
                                int                 *natural)
{
  if (self->fixed_width > -1)
    {
      self->minimum_size_request  = self->fixed_width;
      self->natural_size_request  = self->fixed_width;
    }

  if (self->minimum_size_request < 0)
    {
      GtkColumnViewCellWidget *cell;
      int min, nat, cell_min, cell_nat;

      if (self->header)
        {
          gtk_widget_measure (self->header, GTK_ORIENTATION_HORIZONTAL, -1, &min, &nat, NULL, NULL);
        }
      else
        {
          min = 0;
          nat = 0;
        }

      for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell))
        {
          gtk_widget_measure (GTK_WIDGET (cell),
                              GTK_ORIENTATION_HORIZONTAL,
                              -1,
                              &cell_min, &cell_nat,
                              NULL, NULL);

          min = MAX (min, cell_min);
          nat = MAX (nat, cell_nat);
        }

      self->minimum_size_request = min;
      self->natural_size_request = nat;
    }

  *minimum = self->minimum_size_request;
  *natural = self->natural_size_request;
}

void
gtk_column_view_column_allocate (GtkColumnViewColumn *self,
                                 int                  offset,
                                 int                  size)
{
  self->allocation_offset = offset;
  self->allocation_size = size;
  self->header_position = offset;
}

void
gtk_column_view_column_get_allocation (GtkColumnViewColumn *self,
                                       int                 *offset,
                                       int                 *size)
{
  if (offset)
    *offset = self->allocation_offset;
  if (size)
    *size = self->allocation_size;
}

static void
gtk_column_view_column_create_cells (GtkColumnViewColumn *self)
{
  GtkListView *list;
  GtkWidget *row;

  if (self->first_cell)
    return;

  list = gtk_column_view_get_list_view (GTK_COLUMN_VIEW (self->view));
  for (row = gtk_widget_get_first_child (GTK_WIDGET (list));
       row != NULL;
       row = gtk_widget_get_next_sibling (row))
    {
      GtkColumnViewRowWidget *list_item;
      GtkListItemBase *base;
      GtkWidget *cell;

      list_item = GTK_COLUMN_VIEW_ROW_WIDGET (row);
      base = GTK_LIST_ITEM_BASE (row);
      cell = gtk_column_view_cell_widget_new (self, gtk_column_view_is_inert (self->view));
      gtk_column_view_row_widget_add_child (list_item, cell);
      gtk_list_item_base_update (GTK_LIST_ITEM_BASE (cell),
                                 gtk_list_item_base_get_position (base),
                                 gtk_list_item_base_get_item (base),
                                 gtk_list_item_base_get_selected (base));
    }
}

static void
gtk_column_view_column_remove_cells (GtkColumnViewColumn *self)
{
  while (self->first_cell)
    gtk_column_view_cell_widget_remove (self->first_cell);
}

static void
gtk_column_view_column_create_header (GtkColumnViewColumn *self)
{
  if (self->header != NULL)
    return;

  self->header = gtk_column_view_title_new (self);
  gtk_widget_set_visible (self->header, self->visible);
  gtk_column_view_row_widget_add_child (gtk_column_view_get_header_widget (self->view),
                                  self->header);
  gtk_column_view_column_queue_resize (self);
}

static void
gtk_column_view_column_remove_header (GtkColumnViewColumn *self)
{
  if (self->header == NULL)
    return;

  gtk_column_view_row_widget_remove_child (gtk_column_view_get_header_widget (self->view),
                                     self->header);
  self->header = NULL;
  gtk_column_view_column_queue_resize (self);
}

static void
gtk_column_view_column_ensure_cells (GtkColumnViewColumn *self)
{
  if (self->view && gtk_column_view_column_get_visible (self))
    gtk_column_view_column_create_cells (self);
  else
    gtk_column_view_column_remove_cells (self);

  if (self->view)
    gtk_column_view_column_create_header (self);
  else
    gtk_column_view_column_remove_header (self);
}

/**
 * gtk_column_view_column_get_column_view: (attributes org.gtk.Method.get_property=column-view)
 * @self: a `GtkColumnViewColumn`
 *
 * Gets the column view that's currently displaying this column.
 *
 * If @self has not been added to a column view yet, %NULL is returned.
 *
 * Returns: (nullable) (transfer none): The column view displaying @self.
 */
GtkColumnView *
gtk_column_view_column_get_column_view (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);

  return self->view;
}

void
gtk_column_view_column_set_column_view (GtkColumnViewColumn *self,
                                        GtkColumnView       *view)
{
  if (self->view == view)
    return;

  gtk_column_view_column_remove_cells (self);
  gtk_column_view_column_remove_header (self);

  self->view = view;

  gtk_column_view_column_ensure_cells (self);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLUMN_VIEW]);
}

void
gtk_column_view_column_set_position (GtkColumnViewColumn *self,
                                     guint                position)
{
  GtkColumnViewCellWidget *cell;

  gtk_column_view_row_widget_reorder_child (gtk_column_view_get_header_widget (self->view),
                                      self->header,
                                      position);

  for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell))
    {
      GtkColumnViewRowWidget *list_item;

      list_item = GTK_COLUMN_VIEW_ROW_WIDGET (gtk_widget_get_parent (GTK_WIDGET (cell)));
      gtk_column_view_row_widget_reorder_child (list_item, GTK_WIDGET (cell), position);
    }
}

/**
 * gtk_column_view_column_get_factory: (attributes org.gtk.Method.get_property=factory)
 * @self: a `GtkColumnViewColumn`
 *
 * Gets the factory that's currently used to populate list items for
 * this column.
 *
 * Returns: (nullable) (transfer none): The factory in use
 **/
GtkListItemFactory *
gtk_column_view_column_get_factory (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);

  return self->factory;
}

void
gtk_column_view_column_update_factory (GtkColumnViewColumn *self,
                                       gboolean             inert)
{
  GtkListItemFactory *factory;
  GtkColumnViewCellWidget *cell;

  if (self->factory == NULL)
    return;

  if (inert)
    factory = NULL;
  else
    factory = self->factory;

  for (cell = self->first_cell;
       cell;
       cell = gtk_column_view_cell_widget_get_next (cell))
    {
      gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (cell), factory);
    }
}

/**
 * gtk_column_view_column_set_factory: (attributes org.gtk.Method.set_property=factory)
 * @self: a `GtkColumnViewColumn`
 * @factory: (nullable) (transfer none): the factory to use
 *
 * Sets the `GtkListItemFactory` to use for populating list items for this
 * column.
 */
void
gtk_column_view_column_set_factory (GtkColumnViewColumn *self,
                                    GtkListItemFactory  *factory)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
  g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));

  if (self->factory && !factory)
    gtk_column_view_column_update_factory (self, TRUE);

  if (!g_set_object (&self->factory, factory))
    return;

  if (self->view && !gtk_column_view_is_inert (self->view))
    gtk_column_view_column_update_factory (self, FALSE);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}

/**
 * gtk_column_view_column_set_title: (attributes org.gtk.Method.set_property=title)
 * @self: a `GtkColumnViewColumn`
 * @title: (nullable): Title to use for this column
 *
 * Sets the title of this column.
 *
 * The title is displayed in the header of a `GtkColumnView`
 * for this column and is therefore user-facing text that should
 * be translated.
 */
void
gtk_column_view_column_set_title (GtkColumnViewColumn *self,
                                  const char          *title)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));

  if (g_strcmp0 (self->title, title) == 0)
    return;

  g_free (self->title);
  self->title = g_strdup (title);

  if (self->header)
    gtk_column_view_title_set_title (GTK_COLUMN_VIEW_TITLE (self->header), title);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]);
}

/**
 * gtk_column_view_column_get_title: (attributes org.gtk.Method.get_property=title)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns the title set with gtk_column_view_column_set_title().
 *
 * Returns: (nullable): The column's title
 */
const char *
gtk_column_view_column_get_title (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), FALSE);

  return self->title;
}

static void
gtk_column_view_column_remove_from_sorter (GtkColumnViewColumn *self)
{
  if (self->view == NULL)
    return;

  gtk_column_view_sorter_remove_column (GTK_COLUMN_VIEW_SORTER (gtk_column_view_get_sorter (self->view)), self);
}

/**
 * gtk_column_view_column_set_sorter: (attributes org.gtk.Method.set_property=sorter)
 * @self: a `GtkColumnViewColumn`
 * @sorter: (nullable): the `GtkSorter` to associate with @column
 *
 * Associates a sorter with the column.
 *
 * If @sorter is %NULL, the column will not let users change
 * the sorting by clicking on its header.
 *
 * This sorter can be made active by clicking on the column
 * header, or by calling [method@Gtk.ColumnView.sort_by_column].
 *
 * See [method@Gtk.ColumnView.get_sorter] for the necessary steps
 * for setting up customizable sorting for [class@Gtk.ColumnView].
 */
void
gtk_column_view_column_set_sorter (GtkColumnViewColumn *self,
                                   GtkSorter           *sorter)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
  g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));

  if (!g_set_object (&self->sorter, sorter))
    return;

  gtk_column_view_column_remove_from_sorter (self);

  if (self->header)
    gtk_column_view_title_update_sort (GTK_COLUMN_VIEW_TITLE (self->header));

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}

/**
 * gtk_column_view_column_get_sorter: (attributes org.gtk.Method.get_property=sorter)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns the sorter that is associated with the column.
 *
 * Returns: (nullable) (transfer none): the `GtkSorter` of @self
 */
GtkSorter *
gtk_column_view_column_get_sorter (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);

  return self->sorter;
}

void
gtk_column_view_column_notify_sort (GtkColumnViewColumn *self)
{
  if (self->header)
    gtk_column_view_title_update_sort (GTK_COLUMN_VIEW_TITLE (self->header));
}

/**
 * gtk_column_view_column_set_visible: (attributes org.gtk.Method.set_property=visible)
 * @self: a `GtkColumnViewColumn`
 * @visible: whether this column should be visible
 *
 * Sets whether this column should be visible in views.
 */
void
gtk_column_view_column_set_visible (GtkColumnViewColumn *self,
                                    gboolean             visible)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));

  if (self->visible == visible)
    return;

  self->visible = visible;

  self->minimum_size_request = -1;
  self->natural_size_request = -1;

  if (self->header)
    gtk_widget_set_visible (GTK_WIDGET (self->header), visible);

  gtk_column_view_column_ensure_cells (self);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VISIBLE]);
}

/**
 * gtk_column_view_column_get_visible: (attributes org.gtk.Method.get_property=visible)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns whether this column is visible.
 *
 * Returns: %TRUE if this column is visible
 */
gboolean
gtk_column_view_column_get_visible (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);

  return self->visible;
}

/**
 * gtk_column_view_column_set_header_menu: (attributes org.gtk.Method.set_property=header-menu)
 * @self: a `GtkColumnViewColumn`
 * @menu: (nullable): a `GMenuModel`
 *
 * Sets the menu model that is used to create the context menu
 * for the column header.
 */
void
gtk_column_view_column_set_header_menu (GtkColumnViewColumn *self,
                                        GMenuModel          *menu)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
  g_return_if_fail (menu == NULL || G_IS_MENU_MODEL (menu));

  if (!g_set_object (&self->menu, menu))
    return;

  if (self->header)
    gtk_column_view_title_set_menu (GTK_COLUMN_VIEW_TITLE (self->header), menu);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_MENU]);
}

/**
 * gtk_column_view_column_get_header_menu: (attributes org.gtk.Method.get_property=header-menu)
 * @self: a `GtkColumnViewColumn`
 *
 * Gets the menu model that is used to create the context menu
 * for the column header.
 *
 * Returns: (transfer none) (nullable): the `GMenuModel`
 */
GMenuModel *
gtk_column_view_column_get_header_menu (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);

  return self->menu;
}

/**
 * gtk_column_view_column_set_expand: (attributes org.gtk.Method.set_property=expand)
 * @self: a `GtkColumnViewColumn`
 * @expand: %TRUE if this column should expand to fill available sace
 *
 * Sets the column to take available extra space.
 *
 * The extra space is shared equally amongst all columns that
 * have the expand set to %TRUE.
 */
void
gtk_column_view_column_set_expand (GtkColumnViewColumn *self,
                                   gboolean             expand)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));

  if (self->expand == expand)
    return;

  self->expand = expand;

  if (self->visible && self->view)
    gtk_widget_queue_resize (GTK_WIDGET (self->view));

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPAND]);
}

/**
 * gtk_column_view_column_get_expand: (attributes org.gtk.Method.get_property=expand)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns whether this column should expand.
 *
 * Returns: %TRUE if this column expands
 */
gboolean
gtk_column_view_column_get_expand (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);

  return self->expand;
}

/**
 * gtk_column_view_column_set_resizable: (attributes org.gtk.Method.set_property=resizable)
 * @self: a `GtkColumnViewColumn`
 * @resizable: whether this column should be resizable
 *
 * Sets whether this column should be resizable by dragging.
 */
void
gtk_column_view_column_set_resizable (GtkColumnViewColumn *self,
                                      gboolean             resizable)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));

  if (self->resizable == resizable)
    return;

  self->resizable = resizable;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RESIZABLE]);
}

/**
 * gtk_column_view_column_get_resizable: (attributes org.gtk.Method.get_property=resizable)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns whether this column is resizable.
 *
 * Returns: %TRUE if this column is resizable
 */
gboolean
gtk_column_view_column_get_resizable (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);

  return self->resizable;
}

/**
 * gtk_column_view_column_set_fixed_width: (attributes org.gtk.Method.set_property=fixed-width)
 * @self: a `GtkColumnViewColumn`
 * @fixed_width: the new fixed width, or -1
 *
 * If @fixed_width is not -1, sets the fixed width of @column;
 * otherwise unsets it.
 *
 * Setting a fixed width overrides the automatically calculated
 * width. Interactive resizing also sets the “fixed-width” property.
 */
void
gtk_column_view_column_set_fixed_width (GtkColumnViewColumn *self,
                                        int                  fixed_width)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
  g_return_if_fail (fixed_width >= -1);

  if (self->fixed_width == fixed_width)
    return;

  self->fixed_width = fixed_width;

  gtk_column_view_column_queue_resize (self);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FIXED_WIDTH]);
}

/**
 * gtk_column_view_column_get_fixed_width: (attributes org.gtk.Method.get_property=fixed-width)
 * @self: a `GtkColumnViewColumn`
 *
 * Gets the fixed width of the column.
 *
 * Returns: the fixed with of the column
 */
int
gtk_column_view_column_get_fixed_width (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), -1);

  return self->fixed_width;
}

GtkWidget *
gtk_column_view_column_get_header (GtkColumnViewColumn *self)
{
  return self->header;
}

void
gtk_column_view_column_set_header_position (GtkColumnViewColumn *self,
                                            int                  offset)
{
  self->header_position = offset;
}

void
gtk_column_view_column_get_header_allocation (GtkColumnViewColumn *self,
                                              int                 *offset,
                                              int                 *size)
{
  if (offset)
    *offset = self->header_position;

  if (size)
    *size = self->allocation_size;
}

/**
 * gtk_column_view_column_set_id: (attributes org.gtk.Method.set_property=id)
 * @self: a `GtkColumnViewColumn`
 * @id: (nullable): ID to use for this column
 *
 * Sets the id of this column.
 *
 * GTK makes no use of this, but applications can use it when
 * storing column view configuration.
 *
 * It is up to callers to ensure uniqueness of IDs.
 *
 * Since: 4.10
 */
void
gtk_column_view_column_set_id (GtkColumnViewColumn *self,
                               const char          *id)
{
  g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));

  if (g_strcmp0 (self->id, id) == 0)
    return;

  g_free (self->id);
  self->id = g_strdup (id);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
}

/**
 * gtk_column_view_column_get_id: (attributes org.gtk.Method.get_property=id)
 * @self: a `GtkColumnViewColumn`
 *
 * Returns the ID set with gtk_column_view_column_set_id().
 *
 * Returns: (nullable): The column's ID
 *
 * Since: 4.10
 */
const char *
gtk_column_view_column_get_id (GtkColumnViewColumn *self)
{
  g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);

  return self->id;
}

