/*
 * Mail Notification
 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

%headertop{
#include "mn-widget.h"
%}

%h{
typedef struct _MNTextTableCell MNTextTableCell;
%}

%privateheader{
typedef struct
{
  GPtrArray	*cells;
  int		height;
} Row;
%}

%{
#include "mn-util.h"

#define HORIZONTAL_SPACING	12
#define VERTICAL_SPACING	0

#define COLUMN_SIZE(self, column) \
  g_array_index((self)->_priv->column_sizes, int, (column))

struct _MNTextTableCell
{
  PangoLayout	*layout;	/* NULL for a blank cell */
  gboolean	dirty;
  int		width;
  int		height;
};
%}

/*
 * This widget is used to display the mail summaries.
 *
 * We do not use a GtkBox (or GtkTable) containing labels because it
 * is extremely slow when we add a few hundred labels.
 *
 * A possible alternative would be to use a GtkTextView, which
 * provides acceptable performance. However, creating a table using a
 * GtkTextView is extremely cumbersome.
 */

class MN:Text:Table from Mn:Widget
{
  private GPtrArray *rows = {g_ptr_array_new()}
    destroy { mn_g_ptr_array_free_deep_custom(VAR, (GFunc) self_row_free, NULL); };

  private GArray *column_sizes = {g_array_new(FALSE, TRUE, sizeof(int))}
    destroy { g_array_free(VAR, TRUE); };

  private Row *row;

  private gboolean dirty = TRUE;
  private int width;
  private int height;

  init (self)
  {
    gtk_widget_set_has_window(GTK_WIDGET(self), FALSE);

    g_object_connect(self,
                     "swapped-signal::style-set", self_context_changed, self,
                     "swapped-signal::direction-changed", self_context_changed, self,
                     NULL);
  }

  private void
    row_free (Row *row (check null))
  {
    mn_g_ptr_array_free_deep_custom(row->cells, (GFunc) self_cell_free, NULL);
    row->cells = NULL;
    g_free(row);
  }

  private void
    cell_free (MNTextTableCell *cell (check null))
  {
    if (cell->layout) {
      g_object_unref(cell->layout);
      cell->layout = NULL;
    }
    g_free(cell);
  }

  private void
  size_request_internal (GtkWidget *widget, GtkRequisition *requisition)
  {
    Self *self = SELF(widget);

    self_relayout(self);

    requisition->width = selfp->width;
    requisition->height = selfp->height;
  }

  override (Mn:Widget) void
    mn_size_request (GtkWidget *widget, GtkRequisition *requisition)
  {
    self_size_request_internal(widget, requisition);
  }

  override (Mn:Widget) void
    mn_get_preferred_width (GtkWidget * widget, gint * minimal_width, gint * natural_width)
  {
#if GTK_CHECK_VERSION(3,0,0)
    GtkRequisition requisition;

    self_size_request_internal (widget, &requisition);
    *minimal_width = *natural_width = requisition.width;
#endif
  }

  override (Mn:Widget) void
    mn_get_preferred_height (GtkWidget * widget, gint * minimal_height, gint * natural_height)
  {
#if GTK_CHECK_VERSION(3,0,0)
    GtkRequisition requisition;

    self_size_request_internal (widget, &requisition);
    *minimal_height = *natural_height = requisition.height;
#endif
  }

  private gboolean
    draw_internal(GtkWidget *widget, GdkEventExpose *event, cairo_t *cr)
  {
    Self *self = SELF(widget);
    int i;
    int y;

#if GTK_CHECK_VERSION(3,0,0)
    y = 0;
#else
    y = widget->allocation.y;
#endif

    if (! gtk_widget_is_drawable(widget))
      return FALSE;

    self_relayout(self);

    MN_ARRAY_FOREACH(i, selfp->rows) {
      Row *row = g_ptr_array_index(selfp->rows, i);
      int j;
      int x;
      int column = 0;

#if GTK_CHECK_VERSION(3,0,0)
      x = 0;
#else
      x = widget->allocation.x;
#endif

      MN_ARRAY_FOREACH(j, row->cells) {
        MNTextTableCell *cell = g_ptr_array_index(row->cells, j);

        if (cell->layout) {
#if GTK_CHECK_VERSION(3,0,0)
          gtk_paint_layout(gtk_widget_get_style(widget),
                           cr,
                           gtk_widget_get_state_flags(widget),
                           FALSE,
                           widget,
                           NULL,
                           x,
                           y + row->height - cell->height,
                           cell->layout);
#else
          gtk_paint_layout(widget->style,
                           widget->window,
                           GTK_WIDGET_STATE(widget),
                           FALSE,
                           &event->area,
                           widget,
                           NULL,
                           x,
                           y + row->height - cell->height,
                           cell->layout);
#endif
        }

        x += COLUMN_SIZE(self, column);
        if (j < row->cells->len - 1)
          x += HORIZONTAL_SPACING;

        column++;
      }

      y += row->height;
      if (i < selfp->rows->len - 1)
        y += VERTICAL_SPACING;
    }

    return FALSE;
  }

  /* GTK2 only */
  override (Mn:Widget) gboolean
    mn_expose_event (GtkWidget *widget, GdkEventExpose *event)
  {
      return self_draw_internal(widget, event, NULL);
  }

  /* GTK3 only */
  override (Mn:Widget) gboolean
    mn_draw (GtkWidget *widget, cairo_t *cr)
  {
      return self_draw_internal(widget, NULL, cr);
  }

  private void
    set_dirty (self)
  {
    if (! selfp->dirty) {
      selfp->dirty = TRUE;
      gtk_widget_queue_resize(GTK_WIDGET(self));
    }
  }

  private void
    context_changed (self)
  {
    int i;

    MN_ARRAY_FOREACH(i, selfp->rows) {
      Row *row = g_ptr_array_index(selfp->rows, i);
      int j;

      MN_ARRAY_FOREACH(j, row->cells) {
        MNTextTableCell *cell = g_ptr_array_index(row->cells, j);

        if (cell->layout) {
          pango_layout_context_changed(cell->layout);
          cell->dirty = TRUE;
        }
      }
    }

    self_set_dirty(self);
  }

  private void
    relayout (self)
  {
    int i;

    if (! selfp->dirty)
      return;

    selfp->width = 0;
    selfp->height = 0;

    MN_ARRAY_FOREACH(i, selfp->column_sizes)
      COLUMN_SIZE(self, i) = 0;

    MN_ARRAY_FOREACH(i, selfp->rows) {
      Row *row = g_ptr_array_index(selfp->rows, i);
      int j;

      row->height = 0;

      MN_ARRAY_FOREACH(j, row->cells) {
        MNTextTableCell *cell = g_ptr_array_index(row->cells, j);
        int n_columns = j + 1;

        if (cell->dirty) {
          g_assert(cell->layout != NULL);

          pango_layout_get_pixel_size(cell->layout, &cell->width, &cell->height);
          cell->dirty = FALSE;
        }

        if (n_columns > selfp->column_sizes->len)
          g_array_set_size(selfp->column_sizes, n_columns);

        if (cell->width > COLUMN_SIZE(self, j))
          COLUMN_SIZE(self, j) = cell->width;
        if (cell->height > row->height)
          row->height = cell->height;
      }

      selfp->height += row->height;
    }

    MN_ARRAY_FOREACH(i, selfp->column_sizes)
      selfp->width += COLUMN_SIZE(self, i);

    if (selfp->column_sizes->len > 1)
      selfp->width += HORIZONTAL_SPACING * (selfp->column_sizes->len - 1);
    if (selfp->rows->len > 1)
      selfp->height += VERTICAL_SPACING * (selfp->rows->len - 1);

    selfp->dirty = FALSE;
  }

  virtual public void
    clear (self)
  {
    mn_g_ptr_array_free_deep_custom(selfp->rows, (GFunc) self_row_free, NULL);
    selfp->rows = g_ptr_array_new();

    g_array_set_size(selfp->column_sizes, 0);

    selfp->row = NULL;

    self_set_dirty(self);
  }

  public MNTextTableCell *
    append_text_cell (self, const char *text)
  {
    return self_append_text_cell_from_layout(self, gtk_widget_create_pango_layout(GTK_WIDGET(self), text));
  }

  public MNTextTableCell *
    append_text_cell_from_markup (self, const char *markup (check null))
  {
    PangoLayout *layout;

    layout = gtk_widget_create_pango_layout(GTK_WIDGET(self), NULL);
    pango_layout_set_markup(layout, markup, -1);

    return self_append_text_cell_from_layout(self, layout);
  }

  public MNTextTableCell *
    append_text_cell_from_layout (self, Pango:Layout *layout (check null))
  {
    MNTextTableCell *cell;

    cell = g_new(MNTextTableCell, 1);
    cell->layout = layout;
    cell->dirty = TRUE;

    self_append_cell_real(self, cell);

    return cell;
  }

  public MNTextTableCell *
    append_blank_cell (self, int width, int height)
  {
    MNTextTableCell *cell;

    cell = g_new(MNTextTableCell, 1);
    cell->layout = NULL;
    cell->dirty = FALSE;
    cell->width = width;
    cell->height = height;

    self_append_cell_real(self, cell);

    return cell;
  }

  private void
    append_cell_real (self, MNTextTableCell *cell)
  {
    if (! selfp->row) {
      selfp->row = g_new(Row, 1);
      selfp->row->cells = g_ptr_array_new();

      g_ptr_array_add(selfp->rows, selfp->row);
    }

    g_ptr_array_add(selfp->row->cells, cell);

    self_set_dirty(self);
  }

  public void
    cell_set_text (self,
		   MNTextTableCell *cell (check null),
		   const char *text (check null))
  {
    const char *current_text;

    g_return_if_fail(cell->layout != NULL);

    current_text = pango_layout_get_text(cell->layout);
    if (! current_text || strcmp(current_text, text)) {
      pango_layout_set_text(cell->layout, text, -1);
      cell->dirty = TRUE;
      self_set_dirty(self);
    }
  }

  public void
    line_break (self)
  {
    selfp->row = NULL;
  }
}
