forked from icza/gowut
-
Notifications
You must be signed in to change notification settings - Fork 0
/
table.go
424 lines (349 loc) · 9.88 KB
/
table.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// Copyright (C) 2013 Andras Belicza. All rights reserved.
//
// 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, see <http://www.gnu.org/licenses/>.
// Table component interface and implementation.
package gwu
// Table interface defines a container which lays out its children
// using a configurable, flexible table.
// The size of the table grows dynamically, on demand. However,
// if table size is known or can be guessed before/during building it,
// it is recommended to call EnsureSize to minimize reallocations
// in the background.
//
// Default style class: "gwu-Table"
type Table interface {
// Table is a TableView.
TableView
// EnsureSize ensures that the table will have at least the specified
// rows, and at least the specified columns in rows whose index is < rows.
EnsureSize(rows, cols int)
// EnsureCols ensures that the table will have at least the specified
// cols at the specified row.
// This implicitly includes that the table must have at least (row+1) rows.
// If the table have less than (row+1) rows, empty rows will be added first.
EnsureCols(row, cols int)
// CompsCount returns the number of components added to the table.
CompsCount() int
// CompAt returns the component at the specified row and column.
// Returns nil if row or column are invalid.
CompAt(row, col int) Comp
// CompIdx returns the row and column of the specified component in the table.
// (-1, -1) is returned if the component is not added to the table.
CompIdx(c Comp) (row, col int)
// RowFmt returns the row formatter of the specified table row.
// If the table does not have a row specified by row, nil is returned.
RowFmt(row int) CellFmt
// CellFmt returns the cell formatter of the specified table cell.
// If the table does not have a cell specified by row and col,
// nil is returned.
CellFmt(row, col int) CellFmt
// Add adds a component to the table.
// Return value indicates if the component was added successfully.
// Returns false if row or col is negative.
Add(c Comp, row, col int) bool
// RowSpan returns the row span of the specified table cell.
// -1 is returned if the table does not have a cell specified by row and col.
RowSpan(row, col int) int
// SetRowSpan sets the row span of the specified table cell.
// If the table does not have a cell specified by row and col,
// this is a no-op.
SetRowSpan(row, col, rowSpan int)
// ColSpan returns the col span of the specified table cell.
// -1 is returned if the table does not have a cell specified by row and col.
ColSpan(row, col int) int
// SetColSpan sets the col span of the specified table cell.
// If the table does not have a cell specified by row and col,
// this is a no-op.
SetColSpan(row, col, colSpan int)
// Trim trims all the rows: removes trailing cells that has nil component
// by making the rows shorter.
// This comes handy for example if the table contains cells where colspan > 1 is set;
// by calling this method we can ensure no empty cells will be rendered at the end of such rows.
Trim()
// TrimRow trims the specified row: removes trailing cells that has nil value
// by making the row shorter.
TrimRow(row int)
}
// cellIdx type specifies a cell by its row and col indices.
type cellIdx struct {
row, col int // Row and col indices of the cell.
}
// Table implementation.
type tableImpl struct {
tableViewImpl // TableView implementation
comps [][]Comp // Components added to the table. Structure: comps[rowIdx][colIdx]
rowFmts map[int]*cellFmtImpl // Lazily initialized row formatters of the rows
cellFmts map[cellIdx]*cellFmtImpl // Lazily initialized cell formatters of the cells
}
// NewTable creates a new Table.
// Default horizontal alignment is HADefault,
// default vertical alignment is VADefault.
func NewTable() Table {
c := &tableImpl{tableViewImpl: newTableViewImpl()}
c.Style().AddClass("gwu-Table")
c.SetCellSpacing(0)
c.SetCellPadding(0)
return c
}
func (c *tableImpl) Remove(c2 Comp) bool {
row, col := c.CompIdx(c2)
if row < 0 {
return false
}
c2.setParent(nil)
c.comps[row][col] = nil
return true
}
func (c *tableImpl) ByID(id ID) Comp {
if c.id == id {
return c
}
for _, rowComps := range c.comps {
for _, c2 := range rowComps {
if c2 == nil {
continue
}
if c2.ID() == id {
return c2
}
if c3, isContainer := c2.(Container); isContainer {
if c4 := c3.ByID(id); c4 != nil {
return c4
}
}
}
}
return nil
}
func (c *tableImpl) Clear() {
// Clear row formatters
if c.rowFmts != nil {
c.rowFmts = nil
}
// Clear cell formatters
if c.cellFmts != nil {
c.cellFmts = nil
}
for _, rowComps := range c.comps {
for _, c2 := range rowComps {
if c2 != nil {
c2.setParent(nil)
}
}
}
c.comps = nil
}
func (c *tableImpl) EnsureSize(rows, cols int) {
c.ensureRows(rows)
// Ensure column count in each row
for i := 0; i < rows; i++ {
c.EnsureCols(i, cols)
}
}
func (c *tableImpl) EnsureCols(row, cols int) {
c.ensureRows(row + 1)
rowComps := c.comps[row]
if cols > len(rowComps) {
c.comps[row] = append(rowComps, make([]Comp, cols-len(rowComps))...)
}
}
// EnsureRows ensures that the table will have at least the specified rows.
func (c *tableImpl) ensureRows(rows int) {
if rows > len(c.comps) {
c.comps = append(c.comps, make([][]Comp, rows-len(c.comps))...)
}
}
func (c *tableImpl) CompsCount() (count int) {
for _, rowComps := range c.comps {
for _, c2 := range rowComps {
if c2 != nil {
count++
}
}
}
return
}
func (c *tableImpl) CompAt(row, col int) Comp {
if row < 0 || col < 0 || row >= len(c.comps) {
return nil
}
rowComps := c.comps[row]
if col >= len(rowComps) {
return nil
}
return rowComps[col]
}
func (c *tableImpl) CompIdx(c2 Comp) (int, int) {
for row, rowComps := range c.comps {
for col, c3 := range rowComps {
if c3 == nil {
continue
}
if c2.Equals(c3) {
return row, col
}
}
}
return -1, -1
}
func (c *tableImpl) RowFmt(row int) CellFmt {
if row < 0 || row >= len(c.comps) {
return nil
}
if c.rowFmts == nil {
c.rowFmts = make(map[int]*cellFmtImpl)
}
rf := c.rowFmts[row]
if rf == nil {
rf = newCellFmtImpl()
c.rowFmts[row] = rf
}
return rf
}
func (c *tableImpl) CellFmt(row, col int) CellFmt {
if row < 0 || col < 0 || row >= len(c.comps) || col >= len(c.comps[row]) {
return nil
}
if c.cellFmts == nil {
c.cellFmts = make(map[cellIdx]*cellFmtImpl)
}
ci := cellIdx{row, col}
cf := c.cellFmts[ci]
if cf == nil {
cf = newCellFmtImpl()
c.cellFmts[ci] = cf
}
return cf
}
func (c *tableImpl) Add(c2 Comp, row, col int) bool {
c2.makeOrphan()
// Quick check of row and col
if row < 0 || col < 0 {
return false
}
if row >= len(c.comps) {
c.ensureRows(row + 1)
}
if col >= len(c.comps[row]) {
c.EnsureCols(row, col+1)
}
rowComps := c.comps[row]
// Remove component if there is already one at the specified row and column:
if rowComps[col] != nil {
rowComps[col].setParent(nil)
}
rowComps[col] = c2
c2.setParent(c)
return true
}
func (c *tableImpl) RowSpan(row, col int) int {
cf := c.CellFmt(row, col)
if cf == nil {
return -1
}
return cf.iAttr("rowspan")
}
func (c *tableImpl) SetRowSpan(row, col, rowSpan int) {
cf := c.CellFmt(row, col)
if cf == nil {
return
}
if rowSpan < 2 {
cf.setAttr("rowspan", "") // Delete attribute
} else {
cf.setIAttr("rowspan", rowSpan)
}
}
func (c *tableImpl) ColSpan(row, col int) int {
cf := c.CellFmt(row, col)
if cf == nil {
return -1
}
return cf.iAttr("colspan")
}
func (c *tableImpl) SetColSpan(row, col, colSpan int) {
cf := c.CellFmt(row, col)
if cf == nil {
return
}
if colSpan < 2 {
cf.setAttr("colspan", "") // Delete attribute
} else {
cf.setIAttr("colspan", colSpan)
}
}
func (c *tableImpl) Trim() {
for row := range c.comps {
c.TrimRow(row)
}
}
func (c *tableImpl) TrimRow(row int) {
if row < 0 || row >= len(c.comps) {
return
}
rowComps := c.comps[row]
ci := cellIdx{row: row, col: len(rowComps) - 1} // Create a reusable cell index
for ; ci.col >= 0 && rowComps[ci.col] == nil; ci.col-- {
// Cell is about to "disappear", remove its formatter (if any)
delete(c.cellFmts, ci)
}
// ci.col now points to a non-nil component (or is -1)
c.comps[row] = rowComps[:ci.col+1]
}
func (c *tableImpl) Render(w Writer) {
w.Write(strTableOp)
c.renderAttrsAndStyle(w)
c.renderEHandlers(w)
w.Write(strGT)
// Create a reusable cell index
ci := cellIdx{}
for row, rowComps := range c.comps {
c.renderRowTr(row, w)
for col, c2 := range rowComps {
ci.row, ci.col = row, col
c.renderTd(ci, w)
if c2 != nil {
c2.Render(w)
}
}
}
w.Write(strTableCl)
}
// renderRowTr renders the formatted HTML TR tag for the specified row.
func (c *tableImpl) renderRowTr(row int, w Writer) {
var defha = c.halign // default halign of the table
var defva = c.valign // default valign of the table
if rf := c.rowFmts[row]; rf == nil {
c.renderTr(w)
} else {
// If rf does not specify alignments, it means alignments must not be overridden,
// default alignments of the table must be used!
ha, va := rf.halign, rf.valign
if ha == HADefault {
ha = defha
}
if va == VADefault {
va = defva
}
rf.renderWithAligns(strTROp, ha, va, w)
}
}
// renderTd renders the formatted HTML TD tag for the specified cell.
func (c *tableImpl) renderTd(ci cellIdx, w Writer) {
if cf := c.cellFmts[ci]; cf == nil {
w.Write(strTD)
} else {
cf.render(strTDOp, w)
}
}