// 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 EXT_TABLE_VIEW_H_
#define EXT_TABLE_VIEW_H_

#include <map>
#include <Wt/WJavaScript>
#include <Wt/Ext/Panel>

namespace Wt {

  class WAbstractItemModel;
  class WModelIndex;

  namespace Ext {

    class DataStore;
    class FormField;

/*! \class TableView Wt/Ext/TableView Wt/Ext/TableView
 *  \brief A widget that displays data in a table.
 *
 * This class is an MVC view widget, which works in conjunction with a
 * WAbstractItemModel for the data. The model may be set (and changed)
 * using setModel().
 *
 * The widget may be configured to allow the user to hide or resize
 * columns, sort on column data, or reorder columns using drag&drop.
 *
 * By default, the table is not editable. Use setEditor() to specify a
 * form field that may be used for inline editing for a particular
 * column. Changes are then reflected in the model().
 *
 * The table supports single and multiple selection modes, that work
 * on a row-level, or cell-level. The latter option is enforced when
 * the table is editable.
 *
 * By default, the data of the model is stored client-side, but this
 * may be changed using setDataLocation() to be server-side. The
 * latter option allows, in conjunction with a paging tool bar (see
 * createPagingToolBar()) to support viewing (and editing) of large
 * data sets.
 *
 * Although TableView inherits from Container (through Panel),
 * specifying a layout for adding or removing widgets is not
 * supported. The Panel methods to specify tool bars, titles, and
 * buttons are however supported.
 *
 * \image html ExtTableView-1.png "Example of a TableView"
 * \image html ExtTableView-2.png "Example of a editing in a TableView using a ComboBox"
 *
 * <h3>CSS</h3>
 *
 * A TableView has the <tt>table.x-grid3-row-table</tt> style
 * classes.
 * \ingroup ext
 */
class WT_EXT_API TableView : public Panel
{
public:
  /*! \brief Create a new table view.
   *
   * You should specify a model using setModel(WAbstractItemModel *).
   */
  TableView(WContainerWidget *parent = 0);

  /*! \brief Specify the model.
   *
   * You can change the model at any time, with the contraint that you
   * should keep the same column configuration.
   *
   * You may also reset the same model. This will result in
   * retransmission of the model from scratch. In some cases, this
   * could result in a higher preformance when you have removed many
   * rows or modified a lot of data.
   */
  void setModel(WAbstractItemModel *model);

  /*! \brief Return the model.
   *
   * \sa setModel(WAbstractItemModel *)
   */
  WAbstractItemModel *model() const { return model_; }

  /*! \brief Let the table view resize columns to fit their contents.
   *
   * By default, columns are sized using the column sizes that are
   * provided. Using this method, this is changed to let columns
   * expand to fit the entire table. By setting <i>onResize</i>, this
   * is done also whenever the entire table or one of the columns is
   * resized.
   */
  void resizeColumnsToContents(bool onResize = false);

  /*! \brief Set the column which will auto-expand to take the
   *         remaining space.
   *
   * By default the last column will do that.
   */
  void setAutoExpandColumn(int column, int minWidth = 50, int maxWidth = 1000);

  /*! \brief Return the column index of the column that auto-expands.
   */
  int autoExpandColumn() const;

  /*! \brief Configure the location of the data.
   *
   * By default, data is stored at the client, and therefore entirely
   * transmitted when rendering the table for the first
   * time. Alternatively, the data may be kept at the server. Unless a
   * paging tool bar is configured however, this will still cause the
   * entire table to be anyway, after the table is rendered. When a paging
   * tool bar is configured, only a single page of data is displayed, and
   * transmitted, giving the best performance for big data sets.
   *
   * \sa createPagingToolBar()
   */
  void setDataLocation(DataLocation dataLocation);

  /*! \brief Allow the user to move columns using drag and drop.
   *
   * Setting movable to true, enables the user to move columns around by
   * drag and drop.
   *
   * <i>Note: this currently breaks the CellSelection mode to record
   * the view column number, but not the data column number.</i>
   *
   * \sa columnsMovable()
   */
  void setColumnsMovable(bool movable);

  /*! \brief Return if columns are movable.
   *
   * \sa setColumnsMovable(bool)
   */
  bool columnsMovable() const { return columnMove_; }

  /*! \brief Render rows with alternating colors.
   *
   * By defaults, all rows are rendered using the same color.
   *
   * \sa alternatingRowColors()
   */
  void setAlternatingRowColors(bool enable);

  /*! \brief Return if rows are rendered with alternating colors.
   *
   * \sa setAlternatingRowColors(bool)
   */
  bool alternatingRowColors() const { return alternatingRowColors_; }

  /*! \brief Configure if the row under the mouse will be highlighted.
   *
   * By default, the row under the mouse is not highlighted.
   *
   * \sa highlightMouseOver()
   */
  void setHighlightMouseOver(bool highlight);

  /*! \brief Return if the row under the mouse will be highlighted.
   */
  bool highlightMouseOver() const { return highlightMouseOver_; }  

  // Configuration settings for individual columns

  /*! \brief Change the visibility of a column.
   *
   * \sa isColumnHidden(int), enableColumnHiding(int, bool)
   */
  void setColumnHidden(int column, bool hide);

  /*! \brief Return if a column is hidden.
   *
   * \sa setColumnHidden(int, bool)
   */
  bool isColumnHidden(int column) const;

  /*! \brief Hide a column.
   *
   * \sa showColumn(int), setColumnHidden(int, bool)
   */
  void hideColumn(int column);

  /*! \brief Show a column.
   *
   * \sa hideColumn(int), setColumnHidden(int, bool)
   */
  void showColumn(int column);

  /*! \brief Set the column width (in pixels) for a column.
   *
   * \sa columnWidth(int)
   */
  void setColumnWidth(int column, int pixels);

  /*! \brief Return the column width.
   *
   * \sa setColumnWidth(int, int)
   */
  int columnWidth(int column) const;

  /*! \brief Set the horizontal content alignment of a column.
   *
   * The default value is \link Wt::AlignLeft AlignLeft\endlink.
   * The alignment parameter is a horizontal alignment flag.
   */
  void setColumnAlignment(int column, AlignmentFlag alignment);

  /*! \brief Return the horizontal content alignment of a column.
   *
   * \sa setColumnAlignment(int, AlignmentFlag)
   */
  AlignmentFlag columnAlignment(int column) const;

  /*! \brief Allow a column to be sorted by the user.
   *
   * \sa isColumnSortable(int)
   */
  void setColumnSortable(int column, bool sortable);

  /*! \brief Return if a column is sortable.
   *
   * \sa setColumnSortable(int, bool)
   */
  bool isColumnSortable(int column) const;

  /*! \brief Allow a column to be hidden through its context menu.
   *
   * \sa isColumnHidingEnabled(int)
   */
  void enableColumnHiding(int column, bool enable);

  /*! \brief Return if a column may be hidden through its context menu.
   *
   * \sa enableColumnHiding(int, bool)
   */
  bool isColumnHidingEnabled(int column) const;

  /*! \brief Configure an editor for the given column.
   *
   * Sets an inline editor that will be used to edit values in this column.
   * The edited value will be reflected in the data model.
   *
   * When configuring an editor, the selectionBehaviour() is set to
   * \link Wt::SelectItems SelectItems\endlink mode.
   *
   * \sa LineEdit, NumberField, DateField, ComboBox
   */
  void setEditor(int column, FormField *editor);

  /*! \brief Configure a custom renderer for the given column.
   *
   * Sets a JavaScript function to render values in the given column.
   * The JavaScript function takes one argument (the value), which has
   * a type that corresponds to the C++ type:
   * <table>
   *   <tr><td><b>C++ type</b></td><td><b>JavaScript type</b></td></tr>
   *   <tr><td>WString</td><td>string</td></tr>
   *   <tr><td>WDate</td><td>Ext.Date</td></tr>
   *   <tr><td><i>number</i> type</td><td>number</td></tr>
   * </table>
   *
   * An example of rendererJS for numerical data, which renders positive
   * values in green and negative values in red could be:
   * \code
   * function change(val) {
   *   if (val > 0){
   *     return '\<span style="color:green;"\>' + val + '\</span\>';
   *   } else if(val < 0) {
   *     return '\<span style="color:red;"\>' + val + '\</span\>';
   *   }
   *   return val;
   * }
   * \endcode
   *
   * \sa dateRenderer()
   */
  void setRenderer(int column, const std::string& rendererJS);

  /*! \brief Configure a page size to browse the data page by page.
   *
   * By setting a <i>pageSize</i> that is different from -1, the table
   * view will display only single pages of the whole data set. You
   * should probably add a paging tool bar to allow the user to scroll
   * through the pages.
   *
   * \sa pageSize(), createPagingToolBar()
   */
  void setPageSize(int pageSize);

  /*! \brief Return the page size.
   *
   * \sa setPageSize(int)
   */
  int pageSize() const { return pageSize_; }

  /*! \brief Create a paging tool bar.
   *
   * Create a toolbar that provides paging controls for this
   * table. You should configure the page size using setPageSize(int).
   *
   * \sa setPageSize(int), setDataLocation(DataLocation)
   * \sa setBottomToolBar(ToolBar *), setTopToolBar(ToolBar *)
   */
  ToolBar *createPagingToolBar();

  virtual void refresh();

  /*! \brief Create a date renderer for the given format.
   *
   * The result is a JavaScript function that renders WDate (or more
   * precisely, Ext.Date) values according to the given format, for
   * use in setRenderer()
   *
   * \sa setRenderer()
   * \sa \link WDate::toString(const WString&) const WDate::toString(const WString& format) \endlink
   */
  static std::string dateRenderer(const WString& format);

  /*! \brief Give a cell focus
   *
   * When selectionBehavior() is \link Wt::SelectRows
   * SelectRows\endlink, only the <i>row</i> argument is used, and the
   * effect is to select a particular row.
   *
   * Even when selectionMode() is \link Wt::ExtendedSelection
   * ExtendedSelection\endlink, this method will first clear
   * selection, and the result is that the given
   * <i>row</i>,<i>column</i> will be the only selected cell.
   *
   * \sa currentRow(), currentColumn(), currentCellChanged() signal
   * \sa setSelectionMode(SelectionMode),
   *     setSelectionBehavior(SelectionBehavior)
   */
  void setCurrentCell(int row, int column);

  /*! \brief Return the index of the row currently selected
   *
   * \sa currentColumn(), setCurrentCell(int, int)
   */
  int currentRow() const { return currentRow_; }

  /*! \brief Return the index of the column currently selected
   *
   * \sa currentRow(), setCurrentCell(int, int)
   */
  int currentColumn() const { return currentColumn_; }

  /*! \brief The list of rows that are currently selected.
   *
   * This is the way to retrieve the list of currently selected rows
   * when selectionBehavior() is \link Wt::SelectRows
   * SelectRows\endlink. This list is always empty when
   * selectionBehavior() is \link Wt::SelectItems SelectItems\endlink
   * and you should use currentRow() and currentColumn() instead.
   */
  const std::vector<int>&     selectedRows() { return selectedRows_; }

  /*! \brief Clear the current selection.
   *
   * \sa setCurrentCell(int, int)
   */
  void clearSelection();

  /*! \brief Return the current selection mode.
   *
   * \sa setSelectionMode(SelectionMode)
   */
  SelectionMode selectionMode() const { return selectionMode_; }

  /*! \brief Set the selection mode.
   *
   * The selection mode determines if no, only one, or multiple items may
   * be selected.
   *
   * <i>When selectionBehavior() is \link Wt::SelectItems
   * SelectItems\endlink, \link Wt::ExtendedSelection
   * ExtendedSelection\endlink is not supported.</i>
   */
  void setSelectionMode(SelectionMode mode);

  /*! \brief Set the selection behaviour.
   *
   * The selection behavior defines the unit of selection. The selection
   * behavior also determines the set of methods that must be used to
   * inspect the current selection.
   *
   * You may either:
   *  - use \link Wt::SelectRows SelectRows\endlink to
   *    only select whole rows, and use the method selectedRows() to
   *    inspect the current selection.
   *  - or use \link Wt::SelectItems SelectItems\endlink to select
   *    (a single) individual item, and use the methods currentColumn(),
   *    currentRow(), setCurrentCell() to inspect and modify the current
   *    selection.
   */
  void setSelectionBehavior(SelectionBehavior behavior);

  /*! \brief Return the current selection behaviour.
   *
   * \sa selectionBehavior()
   */
  SelectionBehavior selectionBehavior() const { return selectionBehavior_; }

  /*! \brief %Signal emitted when a cell is clicked.
   *
   * The signal arguments are row and column of the cell that is clicked.
   *
   * \sa currentCellChanged()
   */
  Signal<int, int>& cellClicked() { return cellClicked_; }

  /*! \brief %Signal emitted when a new cell received focus.
   *
   * This signal is only emitted when selectionBehavior() is \link
   * Wt::SelectItems SelectItems\endlink.  The four arguments are
   * <i>row</i>, <i>column</i>, <i>prevrow</i>, <i>prevcolumn</i>
   * which hold respectively the location of the new focussed cell,
   * and the previously focussed cell.
   *
   * Values of -1 indicate 'no selection'.
   */
  Signal<int, int, int, int>& currentCellChanged()
    { return currentCellChanged_; }

  /*! \brief %Signal emitted when the selection changes.
   *
   * \sa currentRow(), currentColumn() when selectionBehavior() is
   * \link Wt::SelectItems SelectItems\endlink
   * \sa selectedRows() when selectionBehavior() is \link Wt::SelectRows
   * SelectRows\endlink.
   */
  Signal<>& itemSelectionChanged() { return itemSelectionChanged_; }

protected:
  virtual void updateExt();
  virtual void createConfig(std::ostream& config);
  virtual std::string extClassName() const;

private:
  Signal<int, int> cellClicked_;
  Signal<int, int, int, int> currentCellChanged_;
  Signal<> itemSelectionChanged_;

  DataLocation        dataLocation_;
  WAbstractItemModel *model_;
  SelectionMode       selectionMode_;
  SelectionBehavior   selectionBehavior_;

  std::vector<int>    selectedRows_;
  int                 currentRow_, currentColumn_;
  int                 pageSize_;

  struct ColumnModel {
    bool        sortable_;
    bool        hideable_;
    bool        hidden_;
    bool        resizable_;
    int         width_;
    FormField  *editor_;
    std::string rendererJS_;
    AlignmentFlag alignment_;

    ColumnModel();
    ~ColumnModel();
  };

  typedef std::map<int, ColumnModel> ColumnMap;
  ColumnMap columnInfo_;
  int  autoExpandColumn_;
  int  autoExpandColumnMinWidth_;
  int  autoExpandColumnMaxWidth_;
  bool autoFill_;
  bool forceFit_;
  bool columnMove_;
  bool alternatingRowColors_;
  bool highlightMouseOver_;
  bool haveEditor_;

  DataStore *dataStore_;

  std::vector<boost::signals::connection> modelConnections_;

  virtual std::string createJS(DomElement *inContainer);

  void modelColumnsInserted(const WModelIndex& parent, int start, int end);
  void modelColumnsRemoved(const WModelIndex& parent, int start, int end);
  void modelRowsInserted(const WModelIndex& parent, int start, int end);
  void modelRowsRemoved(const WModelIndex& parent, int start, int end);
  void modelDataChanged(const WModelIndex& topLeft,
			const WModelIndex& bottomRight);
  void modelLayoutChanged();

  JSignal<std::string,int,std::string> edited_;
  JSignal<std::string> selectionChanged_;
  JSignal<std::string, int> rawCellClicked_;
  void onEdit(std::string field, int rowId, std::string value);
  void onSelectionChange(const std::string selection);
  void onCellClicked(std::string field, int rowId);

  void shiftSelectedRows(int start, int count);
};

  }
}

#endif // EXT_TABLE_VIEW_H_
