1 // This may look like C code, but it's really -*- C++ -*-
2 /*
3  * Copyright (C) 2009 Emweb bv, Herent, Belgium.
4  *
5  * See the LICENSE file for terms of use.
6  */
7 #ifndef WT_WTABLEVIEW_H_
8 #define WT_WTABLEVIEW_H_
9 
10 #include <Wt/WAbstractItemView.h>
11 #include <Wt/WContainerWidget.h>
12 
13 namespace Wt {
14 
15   class WContainerWidget;
16   class WModelIndex;
17 
18 /*! \class WTableView Wt/WTableView.h Wt/WTableView.h
19  *  \brief An MVC View widget for tabular data.
20  *
21  * The view displays data from a WAbstractItemModel in a table. It
22  * provides incremental rendering, without excessive use of client- or
23  * serverside resources.
24  *
25  * The rendering (and editing) of items is handled by a
26  * WAbstractItemDelegate, by default it uses WItemDelegate which
27  * renders data of all predefined roles (see also Wt::ItemDataRole),
28  * including text, icons, checkboxes, and tooltips.
29  *
30  * The view provides virtual scrolling in both horizontal and vertical
31  * directions, and can therefore be used to display large data models
32  * (with large number of columns and rows).
33  *
34  * When the view is updated, it will read the data from the model
35  * row per row, starting at the top visible row. If \c (r1,c1) and \c (r2,c2)
36  * are two model indexes of visible table cells, and \c r1 \c < \c r2 or
37  * \c r1 \c == \c r2 and \c c1 \c < \c c2, then the data for the first
38  * model index is read before the second. Keep this into account when
39  * implementing a custom WAbstractItemModel if you want to optimize
40  * performance.
41  *
42  * The view may support editing of items, if the model indicates
43  * support (see the Wt::ItemFlag::Editable flag). You can define triggers
44  * that initiate editing of an item using setEditTriggers(). The
45  * actual editing is provided by the item delegate (you can set an
46  * appropriate delegate for one column using
47  * setItemDelegateForColumn()). Using setEditOptions() you can
48  * customize if and how the view deals with multiple editors.
49  *
50  * By default, all columns are given a width of 150px. Column widths
51  * of all columns can be set through the API method setColumnWidth(),
52  * and also by the user using handles provided in the header.
53  *
54  * If the model supports sorting (WAbstractItemModel::sort()), such as
55  * the WStandardItemModel, then you can enable sorting buttons in the
56  * header, using setSortingEnabled().
57  *
58  * You can allow selection on row or item level (using
59  * setSelectionBehavior()), and selection of single or multiple items
60  * (using setSelectionMode()), and listen for changes in the selection
61  * using the selectionChanged() signal.
62  *
63  * You may enable drag & drop support for this view, with awareness
64  * of the items in the model. When enabling dragging (see
65  * setDragEnabled()), the current selection may be dragged, but only
66  * when all items in the selection indicate support for dragging
67  * (controlled by the \link Wt::ItemFlag::DragEnabled
68  * ItemFlag::DragEnabled\endlink flag), and if the model indicates a
69  * mime-type (controlled by WAbstractItemModel::mimeType()). Likewise,
70  * by enabling support for dropping (see setDropsEnabled()), the view
71  * may receive a drop event on a particular item, at least if the item
72  * indicates support for drops (controlled by the \link
73  * Wt::ItemFlag::DropEnabled ItemFlag::DropEnabled\endlink flag).
74  *
75  * You may also react to mouse click events on any item, by connecting
76  * to one of the clicked() or doubleClicked() signals.
77  *
78  * If a WTableView is not constrained in height (either by
79  * a layout manager or by setHeight()), then it will grow according
80  * to the size of the model.
81  *
82  * \ingroup modelview
83  */
84 class WT_API WTableView : public WAbstractItemView
85 {
86 public:
87   /*! \brief Constructor
88    */
89   WTableView();
90 
91   virtual ~WTableView();
92 
93   virtual WWidget *itemWidget(const WModelIndex& index) const override;
94   virtual void setModel(const std::shared_ptr<WAbstractItemModel>& model)
95     override;
96 
97   virtual void setColumnWidth(int column, const WLength& width) override;
98   virtual void setAlternatingRowColors(bool enable) override;
99   virtual void setRowHeight(const WLength& rowHeight) override;
100   virtual void setHeaderHeight(const WLength& height) override;
101 #ifndef WT_CNOR
102   using WAbstractItemView::setHeaderHeight;
103 #endif
104   virtual void resize(const WLength& width, const WLength& height) override;
105   virtual void setColumnHidden(int column, bool hidden) override;
106   virtual void setRowHeaderCount(int count) override;
107 
108   virtual int pageCount() const override;
109   virtual int pageSize() const override;
110   virtual int currentPage() const override;
111   virtual void setCurrentPage(int page) override;
112 
113   virtual void scrollTo(const WModelIndex& index,
114 			ScrollHint hint = ScrollHint::EnsureVisible) override;
115 
116   /*! \brief Scrolls the view x px left and y px top.
117    */
118   void scrollTo(int x, int y);
119 
120   /*! \brief set css overflow
121    */
122   void setOverflow(Overflow overflow,
123 		   WFlags<Orientation> orientation
124 		   = (Orientation::Horizontal | Orientation::Vertical));
125 
126   /*! \brief Sets preloading margin
127    *
128    * By default the table view loads in an area equal to 3 times its height
129    * and 3 times its width. This makes it so that the user can scroll a full
130    * page in each direction without the delay caused when the table view
131    * dynamically needs to load more data.
132    *
133    * setPreloadMargin() allows to customize this margin.
134    *
135    * e.g. if the table view is H pixels high, and C is the preload margin in pixels
136    * set on the top and bottom, then enough rows are loaded to fill the area
137    * that is H + 2C pixels high. H pixels visible, C pixels above, and C pixels below.
138    *
139    * Set to 0 pixels if you don't want to load more rows or columns than are currently visible.
140    *
141    * Set to a default-constructed WLength (auto) if you want to keep default behaviour.
142    */
143   void setPreloadMargin(const WLength &margin, WFlags<Side> side = AllSides);
144 
145   /*! \brief Retrieves the preloading margin
146    *
147    * \sa setPreloadMargin
148    */
149   WLength preloadMargin(Side side) const;
150 
151   virtual void setHidden(bool hidden,
152 			 const WAnimation& animation = WAnimation()) override;
153 
154   /*! \brief Returns the model index corresponding to a widget.
155    *
156    * This returns the model index for the item that is or contains the
157    * given widget.
158    */
159   WModelIndex modelIndexAt(WWidget *widget) const;
160 
161   virtual EventSignal<WScrollEvent>& scrolled() override;
162 
163  protected:
164   virtual void render(WFlags<RenderFlag> flags) override;
165 
166   /*! \brief Called when rows or columns are inserted/removed.
167    *
168    * Override this method when you want to adjust the table's size when
169    * columns or rows are inserted or removed. The method is also called when
170    * the model is reset. The default implementation does nothing.
171    */
adjustSize()172   virtual void adjustSize() {}
173 
174   virtual void enableAjax() override;
175 
176 private:
177   class ColumnWidget;
178 
179   ColumnWidget *createColumnWidget(int column);
180 
181   class ColumnWidget : public WContainerWidget
182   {
183   public:
column()184     int column() const { return column_; }
185 
186   private:
187     ColumnWidget(int column);
188 
189     int column_;
190 
191     friend ColumnWidget *WTableView::createColumnWidget(int column);
192   };
193 
194   /* For Ajax implementation */
195   WContainerWidget *headers_, *canvas_, *table_;
196   WContainerWidget *headerContainer_, *contentsContainer_;
197   WContainerWidget *headerColumnsCanvas_, *headerColumnsTable_;
198   WContainerWidget *headerColumnsHeaderContainer_, *headerColumnsContainer_;
199 
200   /* For plain HTML implementation */
201   WTable *plainTable_;
202 
203   JSignal<int, int, std::string, std::string, WMouseEvent> dropEvent_;
204   JSignal<int, int, std::string, std::string, std::string, WMouseEvent> rowDropEvent_;
205   JSignal<int, int, int, int> scrolled_;
206   JSignal<WTouchEvent> itemTouchSelectEvent_;
207 
208   Signals::connection touchStartConnection_;
209   Signals::connection touchMoveConnection_;
210   Signals::connection touchEndConnection_;
211 
212   WLength preloadMargin_[4];
213 
214   /* Ajax only: First and last columns rendered (this somewhat
215    * redundant with the state contained in the widgets, but because
216    * columns are variable width, we cache these values as well). The
217    * first and last rows rendered can be derived from widget
218    * properties. */
219   int firstColumn_, lastColumn_;
220 
221   /* Current size of the viewport */
222   int viewportLeft_, viewportWidth_, viewportTop_, viewportHeight_;
223 
224   /* Desired rendered area */
225   int renderedFirstRow_, renderedLastRow_,
226     renderedFirstColumn_, renderedLastColumn_;
227 
228   /* Scroll to to process after viewport height is known */
229   int scrollToRow_;
230   ScrollHint scrollToHint_;
231   bool columnResizeConnected_;
232 
233   void updateTableBackground();
234 
235   ColumnWidget *columnContainer(int renderedColumn) const;
236 
237   void modelColumnsInserted(const WModelIndex& parent, int start, int end);
238   void modelColumnsAboutToBeRemoved(const WModelIndex& parent,
239 				    int start, int end);
240   void modelRowsInserted(const WModelIndex& parent, int start, int end);
241   void modelRowsAboutToBeRemoved(const WModelIndex& parent, int start, int end);
242   void modelRowsRemoved(const WModelIndex& parent, int start, int end);
243   virtual void modelDataChanged(const WModelIndex& topLeft,
244 				const WModelIndex& bottomRight) override;
245 
246   virtual void modelLayoutChanged() override;
247 
248   std::unique_ptr<WWidget> renderWidget(WWidget* w, const WModelIndex& index);
249 
250   int spannerCount(const Side side) const;
251   void setSpannerCount(const Side side, const int count);
252 
253   void renderTable(const int firstRow, const int lastRow,
254 		   const int firstColumn, const int lastColumn);
255   void addSection(const Side side);
256   void removeSection(const Side side);
257   int firstRow() const;
258   int lastRow() const;
259   int firstColumn() const;
260   int lastColumn() const;
261 
262   void setup();
263   void reset();
264   void rerenderHeader();
265   void rerenderData();
266   void adjustToViewport();
267   void computeRenderedArea();
268 
headerContainer()269   virtual WContainerWidget* headerContainer() override {
270     return headerContainer_;
271   }
272 
273   virtual WWidget *headerWidget(int column, bool contentsOnly = true) override;
274 
275   void onViewportChange(int left, int top, int width, int height);
276   void onColumnResize();
277   void resetGeometry();
278 
279   void handleSingleClick(bool headerColumns, const WMouseEvent& event);
280   void handleDblClick(bool headerColumns, const WMouseEvent& event);
281   void handleMouseWentDown(bool headerColumns, const WMouseEvent& event);
282   void handleMouseWentUp(bool headerColumns, const WMouseEvent& event);
283   void handleTouchSelected(const WTouchEvent& event);
284   void handleTouchStarted(const WTouchEvent& event);
285   void handleTouchMoved(const WTouchEvent& event);
286   void handleTouchEnded(const WTouchEvent& event);
287   WModelIndex translateModelIndex(bool headerColumns, const WMouseEvent& event);
288   WModelIndex translateModelIndex(const Touch& touch);
289 
290   void handleRootSingleClick(int u, const WMouseEvent& event);
291   void handleRootDoubleClick(int u, const WMouseEvent& event);
292   void handleRootMouseWentDown(int u, const WMouseEvent& event);
293   void handleRootMouseWentUp(int u, const WMouseEvent& event);
294 
295   void updateItem(const WModelIndex& index,
296 		  int renderedRow, int renderedColumn);
297 
298   virtual bool internalSelect(const WModelIndex& index, SelectionFlag option)
299     override;
300   virtual void selectRange(const WModelIndex& first, const WModelIndex& last)
301     override;
302   void shiftModelIndexRows(int start, int count);
303   void shiftModelIndexColumns(int start, int count);
304   void renderSelected(bool selected, const WModelIndex& index);
305   int renderedColumnsCount() const;
306 
307   void defineJavaScript();
308 
309   bool isRowRendered(const int row) const;
310   bool isColumnRendered(const int column) const;
311   void updateColumnOffsets();
312   void updateModelIndexes();
313   void updateModelIndex(const WModelIndex& index,
314 			int renderedRow, int renderedColumn);
315 
316   void onDropEvent(int renderedRow, int columnId,
317 		   std::string sourceId, std::string mimeType,
318 		   WMouseEvent event);
319   void onRowDropEvent(int renderedRow, int columnId,
320                       std::string sourceId, std::string mimeType,
321                       std::string side, WMouseEvent event);
322 
323   void deleteItem(int row, int col, WWidget *widget);
324 
ajaxMode()325   bool ajaxMode() const { return table_ != nullptr; }
326   double canvasHeight() const;
327   void setRenderedHeight(double th);
328 };
329 
330 }
331 
332 #endif // WT_WTABLEVIEW_H
333