1 module dlangui.widgets.spreadsheet;
2 
3 import dlangui.core.types;
4 import dlangui.widgets.styles;
5 import dlangui.widgets.widget;
6 import dlangui.widgets.layouts;
7 import dlangui.widgets.controls;
8 import dlangui.widgets.tabs;
9 import dlangui.widgets.editors;
10 import dlangui.widgets.grid;
11 import dlangui.widgets.scrollbar;
12 
13 import std.algorithm : min;
14 
15 /// standard style id for Tab with Up alignment
16 immutable string STYLE_TAB_SHEET_DOWN = "TAB_SHEET_DOWN";
17 /// standard style id for button of Tab with Up alignment
18 immutable string STYLE_TAB_SHEET_DOWN_BUTTON = "TAB_SHEET_DOWN_BUTTON";
19 /// standard style id for button of Tab with Up alignment
20 immutable string STYLE_TAB_SHEET_DOWN_BUTTON_TEXT = "TAB_SHEET_DOWN_BUTTON_TEXT";
21 
22 class SheetTabs : TabControl {
23     /// create with ID parameter
24     this(string ID = null) {
25         super(ID, Align.Bottom);
26         setStyles(STYLE_TAB_SHEET_DOWN, STYLE_TAB_SHEET_DOWN_BUTTON, STYLE_TAB_SHEET_DOWN_BUTTON_TEXT);
27         _moreButton.visibility = Visibility.Gone;
28     }
29 }
30 
31 class SheetEditControl : HorizontalLayout {
32     EditLine _edPosition;
33     EditLine _edText;
34     this(string ID = "sheetEdit") {
35         _edPosition = new EditLine("edPosition");
36         _edText = new EditLine("edText");
37         _edPosition.maxWidth = 100;
38         _edPosition.minWidth = 100;
39         _edText.layoutWidth = FILL_PARENT;
40         addChild(_edPosition);
41         addChild(_edText);
42     }
43 }
44 
45 class SpreadSheetView : StringGridWidget {
46     this(string ID = null) {
47         super(ID);
48         layoutWidth = FILL_PARENT;
49         layoutHeight = FILL_PARENT;
50         defRowHeight = 14;
51         defColumnWidth = 80;
52         styleId = null;
53         backgroundColor = 0xFFFFFF;
54         resize(50, 50);
55         _colWidths[0] = 50;
56         for (int i = 0; i < 26; i++) {
57             dchar[1] t;
58             t[0] = cast(dchar)('A' + i);
59             setColTitle(i, t.dup);
60         }
61         for (int i = 0; i < 50; i++) {
62             dstring label = to!dstring(i + 1);
63             setRowTitle(i, label);
64         }
65     }
66 }
67 
68 class SpreadSheetWidget : WidgetGroupDefaultDrawing, OnScrollHandler, CellSelectedHandler, CellActivatedHandler, ViewScrolledHandler {
69 
70     SheetEditControl _editControl;
71     SheetTabs _tabs;
72 
73     ScrollBar _hScroll1;
74     ScrollBar _hScroll2;
75     ScrollBar _vScroll1;
76     ScrollBar _vScroll2;
77 
78     SpreadSheetView _viewTopLeft;
79     SpreadSheetView _viewTopRight;
80     SpreadSheetView _viewBottomLeft;
81     SpreadSheetView _viewBottomRight;
82 
83     SpreadSheetView[4] _views;
84     ScrollBar[4] _scrollbars;
85 
86     this(string ID = "spreadsheet") {
87         _editControl = new SheetEditControl();
88         _editControl.layoutWidth = FILL_PARENT;
89         _tabs = new SheetTabs();
90         _tabs.layoutWidth = FILL_PARENT;
91         _tabs.addTab("Sheet1", "Sheet1"d);
92         _tabs.addTab("Sheet2", "Sheet2"d);
93         _tabs.addTab("Sheet3", "Sheet3"d);
94         layoutWidth = FILL_PARENT;
95         layoutHeight = FILL_PARENT;
96         backgroundColor = 0xdce2e8;
97         minHeight = 100;
98 
99         _hScroll1 = new ScrollBar("hscroll1", Orientation.Horizontal);
100         _hScroll2 = new ScrollBar("hscroll2", Orientation.Horizontal);
101         _vScroll1 = new ScrollBar("vscroll1", Orientation.Vertical);
102         _vScroll2 = new ScrollBar("vscroll2", Orientation.Vertical);
103 
104         _scrollbars[0] = _hScroll1;
105         _scrollbars[1] = _vScroll1;
106         _scrollbars[2] = _hScroll2;
107         _scrollbars[3] = _vScroll2;
108 
109         _viewTopLeft = new SpreadSheetView("sheetViewTopLeft");
110         _viewTopRight = new SpreadSheetView("sheetViewTopRight");
111         _viewBottomLeft = new SpreadSheetView("sheetViewBottomLeft");
112         _viewBottomRight = new SpreadSheetView("sheetViewBottomRight");
113 
114         _viewTopRight.setColWidth(0, 0);
115         _viewBottomLeft.setRowHeight(0, 0);
116         _viewBottomRight.setRowHeight(0, 0);
117         _viewBottomRight.setColWidth(0, 0);
118 
119         _views[0] = _viewTopLeft;
120         _views[1] = _viewTopRight;
121         _views[2] = _viewBottomLeft;
122         _views[3] = _viewBottomRight;
123 
124         _viewTopLeft.hscrollbar = _hScroll1;
125         _viewTopLeft.vscrollbar = _vScroll1;
126         _viewTopRight.hscrollbar = _hScroll2;
127         _viewTopRight.vscrollbar = _vScroll1;
128         _viewBottomLeft.hscrollbar = _hScroll1;
129         _viewBottomLeft.vscrollbar = _vScroll2;
130         _viewBottomRight.hscrollbar = _hScroll2;
131         _viewBottomRight.vscrollbar = _vScroll2;
132 
133         addChildren([_hScroll1, _vScroll1, _hScroll2, _vScroll2,
134             _viewTopLeft, _viewTopRight, _viewBottomLeft, _viewBottomRight,
135             _editControl, _tabs
136         ]);
137 
138         foreach(sb; _scrollbars)
139             sb.scrollEvent = this;
140         foreach(view; _views) {
141             view.cellSelected = this;
142             view.cellActivated = this;
143             view.viewScrolled = this;
144         }
145     }
146 
147     /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
148     override void measure(int parentWidth, int parentHeight) {
149         if (visibility == Visibility.Gone) {
150             return;
151         }
152         _measuredWidth = parentWidth;
153         _measuredHeight = parentHeight;
154         foreach(view; _views)
155             view.measure(parentWidth, parentHeight);
156         foreach(sb; _scrollbars)
157             sb.measure(parentWidth, parentHeight);
158         _editControl.measure(parentWidth, parentHeight);
159         _tabs.measure(parentWidth, parentHeight);
160     }
161     /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
162     override void layout(Rect rc) {
163         if (visibility == Visibility.Gone) {
164             return;
165         }
166         _pos = rc;
167         _needLayout = false;
168         applyMargins(rc);
169         applyPadding(rc);
170         int editHeight = _editControl.measuredHeight;
171         _editControl.layout(Rect(rc.left, rc.top, rc.right, rc.top + editHeight));
172         rc.top += editHeight;
173         int splitWidth = 4;
174         int splitHeight = 4;
175         int hscrollHeight = _hScroll1.measuredHeight;
176         int vscrollWidth = _vScroll1.measuredWidth;
177         int tabsHeight = _tabs.measuredHeight;
178         int bottomSize = min(hscrollHeight, tabsHeight);
179         int splitx = (rc.width - vscrollWidth - splitWidth) / 2;
180         int splity = (rc.height - hscrollHeight - splitHeight) / 2;
181         _viewTopLeft.layout(Rect(rc.left, rc.top, rc.left + splitx, rc.top + splity));
182         _viewTopRight.layout(Rect(rc.left + splitx + splitWidth, rc.top, rc.right - vscrollWidth, rc.top + splity));
183         _viewBottomLeft.layout(Rect(rc.left, rc.top + splity + splitHeight, rc.left + splitx, rc.bottom - bottomSize));
184         _viewBottomRight.layout(Rect(rc.left + splitx + splitWidth, rc.top + splity + splitHeight, rc.right - vscrollWidth, rc.bottom - bottomSize));
185         int tabsWidth = splitx / 2;
186         _tabs.layout(Rect(rc.left, rc.bottom - bottomSize, rc.left + tabsWidth, rc.bottom - bottomSize + tabsHeight));
187 
188         _hScroll1.layout(Rect(rc.left + tabsWidth + splitWidth, rc.bottom - hscrollHeight, rc.left + splitx, rc.bottom));
189         _hScroll2.layout(Rect(rc.left + splitx + splitWidth, rc.bottom - hscrollHeight, rc.right - vscrollWidth, rc.bottom));
190         _vScroll1.layout(Rect(rc.right - vscrollWidth, rc.top, rc.right, rc.top + splity));
191         _vScroll2.layout(Rect(rc.right - vscrollWidth, rc.top + splity + splitHeight, rc.right, rc.bottom - bottomSize));
192     }
193 
194     /// handle scroll event
195     override bool onScrollEvent(AbstractSlider source, ScrollEvent event) {
196         if (source == _hScroll1) {
197             _viewBottomLeft.onHScroll(event);
198             return _viewTopLeft.onHScroll(event);
199         } else if (source == _hScroll2) {
200             _viewBottomRight.onHScroll(event);
201             return _viewTopRight.onHScroll(event);
202         } else if (source == _vScroll1) {
203             _viewTopRight.onVScroll(event);
204             return _viewTopLeft.onVScroll(event);
205         } else if (source == _vScroll2) {
206             _viewBottomRight.onVScroll(event);
207             return _viewBottomLeft.onVScroll(event);
208         }
209         return true;
210     }
211 
212     /// Callback for handling of cell selection
213     void onCellSelected(GridWidgetBase source, int col, int row) {
214         foreach(view; _views) {
215             if (source != view)
216                 view.selectCell(col + view.headerCols, row + view.headerRows, false, source, false);
217         }
218     }
219 
220     /// Callback for handling of cell double click or Enter key press
221     void onCellActivated(GridWidgetBase source, int col, int row) {
222     }
223 
224     /// Callback for handling of view scroll (top left visible cell change)
225     void onViewScrolled(GridWidgetBase source, int col, int row) {
226         if (source == _viewTopLeft) {
227             _viewTopRight.scrollTo(-1, row, source, false);
228             _viewBottomLeft.scrollTo(col, -1, source, false);
229         } else if (source == _viewTopRight) {
230             _viewTopLeft.scrollTo(-1, row, source, false);
231             _viewBottomRight.scrollTo(col, -1, source, false);
232         } else if (source == _viewBottomLeft) {
233             _viewTopLeft.scrollTo(col, -1, source, false);
234             _viewBottomRight.scrollTo(-1, row, source, false);
235         } else if (source == _viewBottomRight) {
236             _viewTopRight.scrollTo(col, -1, source, false);
237             _viewBottomLeft.scrollTo(-1, row, source, false);
238         }
239     }
240 
241 }