// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WGRID_LAYOUT_H_
#define WGRID_LAYOUT_H_

#include <vector>
#include <Wt/WLayout>
#include <Wt/WLength>
#include <Wt/WGlobal>

namespace Wt {

  namespace Impl {

struct Grid {
  int horizontalSpacing_, verticalSpacing_;

  struct Row {
    int stretch_;
    bool resizable_;

    Row(int stretch = 0);
  };

  struct Column {
    int stretch_;
    bool resizable_;

    Column(int stretch = 0);
  };

  struct Item {
    WLayoutItem *item_;
    int rowSpan_;
    int colSpan_;
    WFlags<AlignmentFlag> alignment_;

    Item(WLayoutItem *item = 0, WFlags<AlignmentFlag> alignment = 0);
  };

  std::vector<Row>    rows_;
  std::vector<Column> columns_;
  std::vector<std::vector<Item> > items_; // [row][column]

  Grid();
  ~Grid();
};

  }

/*! \class WGridLayout Wt/WGridLayout Wt/WGridLayout
 *  \brief A layout manager which arranges widgets in a grid
 *
 * This is a layout class that arranges widgets in a grid, to span the
 * entire area of the parent container. Each grid location (row,
 * column) may contain one widget or nested layout. Horizontal and
 * vertical space are divided so that each column/row is given its
 * minimum size and the remaining space is dived according to stretch
 * factors among the columns/rows. The minimum width of a column/row
 * is based on the minimum dimensions of contained widgets or nested
 * layouts. The default minimum height and width may be overridden
 * using WWidget::setMinimumSize().
 *
 * If you want to use the layout manager for a container which does
 * not have a height that is constrained somehow, you need to specify
 * AlignTop in the alignment flags of
 * WContainerWidget::setLayout(). Otherwise the behavior is undefined
 * (the parent container will continue to increase in size as it tries
 * to satisfy the constraints assuming a contrained height).
 *
 * You can use \link WContainerWidget::setOverflow()
 * WContainerWidget::setOverflow(OverflowAuto) \endlink or use a
 * WScrollArea to automatically show scrollbars on a widget inserted
 * in the layout.
 *
 * A caveat with layout managers is that you cannot reliably use a
 * stylesheet to add borders (or margin) to a widget inserted in a
 * layout: this is broken on Internet Explorer. To provide the layout,
 * the layout manager needs to set sizes on the contained widget but
 * these sizes also need to take into account the border/margin
 * width. Since on IE, this value will be 0 if the border or margin is
 * provided by a stylesheet (as opposed to by inline CSS by using
 * WWidget::decorationStyle()), the result will be wrong behaviour
 * like widgets that keep growing in size.
 *
 * A layout manager may provide resize handles between columns or rows
 * which allow the user to change the automatic layout provided by the
 * layout manager (see setRowResizable() and
 * setColumnResizable()). Resize handles between rows only work when
 * the layout fills the parent vertical space (i.e. is not aligned to
 * the top). Likewise, resize handles between columns only work when
 * the layout fills the parent horiziontal space (i.e. is not aligned
 * left, right or centered).
 *
 * Columns and rows are separated using a constant spacing, which
 * defaults to 6 pixels by default, and can be changed using
 * setHorizontalSpacing() and setVerticalSpacing(). In addition, when
 * this layout is a top-level layout (i.e. is not nested inside
 * another layout), a margin is set around the contents, which thus
 * replaces padding defined for the container. It is not allowed to
 * define padding for the container widget using its CSS 'padding'
 * property or the WContainerWidget::setPadding(). This margin also
 * defaults to 9 pixels, and can be changed using
 * setContentsMargins().
 *
 * For each column or row, a stretch factor may be defined, which
 * controls how remaining horizontal or vertical space is used. Each
 * column and row is stretched using the stretch factor to fill the
 * remaining space. When the stretch factor is 0, the height of the row
 * and its contents is not actively managed. As a consequence, the contents
 * of each cell will not fill the cell. You may use a special stretch factor
 * of -1 to indicate that the height of the row should not stretch but the
 * contents height should be actively managed. This has as draw-back that
 * the height of the row will no longer reduce in size when any of the cell
 * contents reduces in size.
 *
 * Usage example:
 * \if cpp
 * \code
 * Wt::WContainerWidget *w = new Wt::WContainerWidget(this);
 * w->resize(WLength::Auto, 600);
 *
 * Wt::WGridLayout *layout = new Wt::WGridLayout();
 * layout->addWidget(new Wt::WText("Item 0 0"), 0, 0);
 * layout->addWidget(new Wt::WText("Item 0 1"), 0, 1);
 * layout->addWidget(new Wt::WText("Item 1 0"), 1, 0);
 * layout->addWidget(new Wt::WText("Item 1 1"), 1, 1);
 *
 * w->setLayout(layout);
 * \endcode
 * \elseif java
 * \code
 * WContainerWidget w = new WContainerWidget(this);
 * w.resize(WLength.Auto, new WLength(600));
 *		 
 * WGridLayout layout = new WGridLayout();
 * layout.addWidget(new WText("Item 0 0"), 0, 0);
 * layout.addWidget(new WText("Item 0 1"), 0, 1);
 * layout.addWidget(new WText("Item 1 0"), 1, 0);
 * layout.addWidget(new WText("Item 1 1"), 1, 1);
 *		 
 * w.setLayout(layout);
 * \endcode
 * \endif
 *
 * \if cpp
 * \note This layout manager is applicable only to WContainerWidget
 * container widgets. You may use it within an Ext::Container
 * indirectly by first setting a WContainerWidget using a WFitLayout.
 * \endif
 *
 * \note When JavaScript support is not available, only Safari and
 * Firefox properly implement this layout. For other browsers, only
 * the horizontal layout is properly implemented, while vertically all
 * widgets use their minimum size.
 *
 * \note When set on a WContainerWidget, this layout manager accepts the
 *       following hints (see setLayoutHint()):
 *   <ul>
 *     <li>"table-layout" with possible values "auto" (default) or "fixed".<br>
 *       Use "fixed" to prevent nested tables from overflowing the layout.
 *       In that case, you will need to specify a width (in CSS or otherwise)
 *       for at least one item in every column that has no stretch factor.</li>
 *  </ul>
 */
class WT_API WGridLayout : public WLayout
{
public:
  /*! \brief Create a new grid layout.
   *
   * The grid will grow dynamically as items are added.
   *
   * Use \p parent = \c 0 to create a layout manager that can be
   * nested inside other layout managers or to specify a specific alignment
   * when setting the layout to a WContainerWidget.
   */
  WGridLayout(WWidget *parent = 0);

  virtual void addItem(WLayoutItem *item);
  virtual void removeItem(WLayoutItem *item);
  virtual WLayoutItem *itemAt(int index) const;
  virtual int count() const;

  /*! \brief Adds a layout item to the grid.
   *
   * Adds the <i>item</i> at (<i>row</i>, \p column). If an item
   * was already added to that location, it is replaced (but not
   * deleted).
   *
   * An item may span several more rows or columns, which is
   * controlled by <i>rowSpan</i> and \p columnSpan.
   *
   * The \p alignment specifies the vertical and horizontal
   * alignment of the item. The default value 0 indicates that the
   * item is stretched to fill the entire grid cell. The alignment can
   * be specified as a logical combination of a horizontal alignment
   * (Wt::AlignLeft, Wt::AlignCenter, or Wt::AlignRight) and a
   * vertical alignment (Wt::AlignTop, Wt::AlignMiddle, or
   * Wt::AlignBottom).
   *
   * \sa addLayout(), addWidget() 
   */
  void addItem(WLayoutItem *item, int row, int column,
	       int rowSpan = 1, int columnSpan = 1,
	       WFlags<AlignmentFlag> alignment = 0);

  /*! \brief Adds a nested layout item to the grid.
   *
   * Adds the <i>layout</i> at (<i>row</i>, \p column). If an item
   * was already added to that location, it is replaced (but not
   * deleted).
   *
   * The \p alignment specifies the vertical and horizontal
   * alignment of the item. The default value 0 indicates that the
   * item is stretched to fill the entire grid cell. The alignment can
   * be specified as a logical combination of a horizontal alignment
   * (Wt::AlignLeft, Wt::AlignCenter, or Wt::AlignRight) and a
   * vertical alignment (Wt::AlignTop, Wt::AlignMiddle, or
   * Wt::AlignBottom).
   *
   * \sa addLayout(WLayout *, int, int, int, int, WFlags<AlignmentFlag>) 
   */
  void addLayout(WLayout *layout, int row, int column,
		 WFlags<AlignmentFlag> alignment = 0);

  /*! \brief Adds a nested layout item to the grid.
   *
   * Adds the <i>layout</i> at (<i>row</i>, \p column). If an item
   * was already added to that location, it is replaced (but not
   * deleted).
   *
   * An item may span several more rows or columns, which is
   * controlled by <i>rowSpan</i> and \p columnSpan.
   *
   * The \p alignment specifies the vertical and horizontal
   * alignment of the item. The default value 0 indicates that the
   * item is stretched to fill the entire grid cell. The alignment can
   * be specified as a logical combination of a horizontal alignment
   * (Wt::AlignLeft, Wt::AlignCenter, or Wt::AlignRight) and a
   * vertical alignment (Wt::AlignTop, Wt::AlignMiddle, or
   * Wt::AlignBottom).
   *
   * \sa addLayout(WLayout *, int, int, WFlags<AlignmentFlag>) 
   */
  void addLayout(WLayout *layout, int row, int column,
		 int rowSpan, int columnSpan,
		 WFlags<AlignmentFlag> alignment = 0);

  /*! \brief Adds a widget to the grid.
   *
   * Adds the <i>widget</i> at (<i>row</i>, \p column). If an item
   * was already added to that location, it is replaced (but not
   * deleted).
   *
   * The \p alignment specifies the vertical and horizontal
   * alignment of the item. The default value 0 indicates that the
   * item is stretched to fill the entire grid cell. The alignment can
   * be specified as a logical combination of a horizontal alignment
   * (Wt::AlignLeft, Wt::AlignCenter, or Wt::AlignRight) and a
   * vertical alignment (Wt::AlignTop, Wt::AlignMiddle, or
   * Wt::AlignBottom).
   *
   * \sa addWidget(WWidget *, int, int, int, int, WFlags<AlignmentFlag>) 
   */
  void addWidget(WWidget *widget, int row, int column,
		 WFlags<AlignmentFlag> alignment = 0);

  /*! \brief Adds a widget to the grid.
   *
   * Adds the <i>widget</i> at (<i>row</i>, \p column). If an item
   * was already added to that location, it is replaced (but not
   * deleted).
   *
   * The widget may span several more rows or columns, which is
   * controlled by <i>rowSpan</i> and \p columnSpan.
   *
   * The \p alignment specifies the vertical and horizontal
   * alignment of the item. The default value 0 indicates that the
   * item is stretched to fill the entire grid cell. The alignment can
   * be specified as a logical combination of a horizontal alignment
   * (Wt::AlignLeft, Wt::AlignCenter, or Wt::AlignRight) and a
   * vertical alignment (Wt::AlignTop, Wt::AlignMiddle, or
   * Wt::AlignBottom).
   *
   * \sa addWidget(WWidget *, int, int, WFlags<AlignmentFlag>)
   */
  void addWidget(WWidget *widget, int row, int column,
		 int rowSpan, int columnSpan,
		 WFlags<AlignmentFlag> alignment = 0);

  /*! \brief Sets the horizontal spacing.
   *
   * The default horizontal spacing is 9 pixels.
   *
   * \sa setVerticalSpacing(int) 
   */
  void setHorizontalSpacing(int size);

  /*! \brief Returns the horizontal spacing.
   *
   * \sa setHorizontalSpacing(int) 
   */
  int horizontalSpacing() const { return grid_.horizontalSpacing_; }

  /*! \brief Sets the vertical spacing.
   *
   * The default vertical spacing is 9 pixels.
   *
   * \sa setHorizontalSpacing(int) 
   */
  void setVerticalSpacing(int size);

  /*! \brief Returns the vertical spacing.
   *
   * \sa setVerticalSpacing(int) 
   */
  int verticalSpacing() const { return grid_.verticalSpacing_; }

  /*! \brief Returns the column count.
   *
   * The grid dimensions change dynamically when adding contents to
   * the grid.
   *
   * \sa rowCount()
   */
  int columnCount() const;

  /*! \brief Returns the row count.
   *
   * The grid dimensions change dynamically when adding contents to
   * the grid.
   *
   * \sa columnCount()
   */
  int rowCount() const;

  /*! \brief Sets the column stretch.
   *
   * Sets the <i>stretch</i> factor for column \p column.
   *
   * \sa columnStretch()
   */
  void setColumnStretch(int column, int stretch);

  /*! \brief Returns the column stretch.
   *
   * \sa setColumnStretch(int, int)
   */
  int columnStretch(int column) const;

  /*! \brief Sets the row stretch.
   *
   * Sets the <i>stretch</i> factor for row \p row. See the
   * description for the special value of -1.
   *
   * \sa rowStretch()
   */
  void setRowStretch(int row, int stretch);

  /*! \brief Returns the column stretch.
   *
   * \sa setRowStretch(int, int)
   */
  int rowStretch(int row) const;

  /*! \brief Sets whether the user may drag a particular column border.
   *
   * This method sets whether the border that separates column
   * <i>column</i> from the next column may be resized by the user,
   * depending on the value of <i>enabled</i>.
   *
   * The default value is <i>false</i>.
   */
  void setColumnResizable(int column, bool enabled = true);

  /*! \brief Returns whether the user may drag a particular column border.
   *
   * This method returns whether the border that separates column
   * <i>column</i> from the next column may be resized by the user.
   *
   * \sa setColumnResizable()
   */
  bool columnIsResizable(int column) const;

  /*! \brief Sets whether the user may drag a particular row border.
   *
   * This method sets whether the border that separates row <i>row</i> from
   * the next row may be resized by the user, depending on the value of
   * <i>enabled</i>.
   *
   * The default value is <i>false</i>.
   */
  void setRowResizable(int row, bool enabled = true);

  /*! \brief Returns whether the user may drag a particular row border.
   *
   * This method returns whether the border that separates row
   * <i>row</i> from the next row may be resized by the user.
   *
   * \sa setRowResizable()
   */
  bool rowIsResizable(int row) const;

  Impl::Grid& grid() { return grid_; }

private:
  Impl::Grid grid_;

  void expand(int row, int column, int rowSpan, int columnSpan);
};

}

#endif // WGRID_LAYOUT_H_
