-
-
Notifications
You must be signed in to change notification settings - Fork 354
/
FTTableContainerMorph.class.st
411 lines (332 loc) · 12.3 KB
/
FTTableContainerMorph.class.st
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
"
I am a Morph that contain visible rows in a FTTableMorph.
Description
------------------
I am the main Morph of the FastTable that is responsible of displaying all the rows of a Table.
My owner need to be a FTTableMorph and I will use his dataSource to display the needed informations.
Public API and Key Messages
-----------------
- #updateAllRows
- #updateExposedRows
- #ipdateHeaderRow
Internal Representation and Key Implementation Points.
----------------
Instance Variables
exposedRows: A dictionary of index/row with all the exposed rows.
headerRow: When not nil contains the header row of the container.
needsRefreshExposedRows: A boolean that is true if the container need a refresh.
The method #drawOn: is responsible of my rendering.
"
Class {
#name : #FTTableContainerMorph,
#superclass : #Morph,
#instVars : [
'needsRefreshExposedRows',
'headerRow',
'exposedRows'
],
#category : #'Morphic-Widgets-FastTable'
}
{ #category : #accessing }
FTTableContainerMorph class >> rowLeftMargin [
"I'm keeping a small margin beween the list and the begining of a row, to enhance visibility."
^ 1
]
{ #category : #drawing }
FTTableContainerMorph >> addResizeSplitters [
| columnWidths nextColumn delta |
columnWidths := self calculateColumnWidths.
nextColumn := self left.
delta := FTColumnResizerMorph resizerWidth / 2.
self table columns overlappingPairsWithIndexDo: [ :leftColumn :rightColumn :index |
nextColumn := nextColumn + (columnWidths at: index) + self table intercellSpacing x.
self addMorph: ((FTColumnResizerMorph
container: self
left: (FTDisplayColumn column: leftColumn width: (columnWidths at: index))
right: (FTDisplayColumn column: rightColumn width: (columnWidths at: (index + 1))))
bounds: ((nextColumn - delta)@(self top) extent: delta@(self height));
color: Color transparent;
yourself) ]
]
{ #category : #private }
FTTableContainerMorph >> calculateColumnWidths [
"do three runs
- first collect defined columnwidth that fit
- collect remaining undefined columnwidth
- return if all fit
or collect and distribute remaining width"
| undefinedColumnWidths widths remainingWidth |
remainingWidth := self table bounds width.
widths := self table columns
collect: [ :c |
| columnWidth |
columnWidth := c acquireWidth: remainingWidth.
remainingWidth := remainingWidth - columnWidth.
columnWidth ].
"all fit - finish"
undefinedColumnWidths := widths count: #isZero.
undefinedColumnWidths isZero
ifTrue: [ widths size > 1 ifTrue: [ "Set the remaining space to the last column" widths at: widths size put: widths last + remainingWidth ].
^ widths ].
"collect and distribute remaining space"
^ widths collect: [ :c | c = 0 ifTrue: [ remainingWidth / undefinedColumnWidths ] ifFalse: [ c ] ]
]
{ #category : #private }
FTTableContainerMorph >> calculateExactVisibleRows [
"Answer the rows to show in list - with possible fraction"
| visibleRows |
visibleRows := self height / (self table rowHeight + self table intercellSpacing y).
^ headerRow ifNotNil: [ visibleRows - 1 ] ifNil: [ visibleRows ]
]
{ #category : #private }
FTTableContainerMorph >> calculateMaxVisibleRows [
"Answer the maximal number of rows to shown in list"
^ self calculateExactVisibleRows ceiling
]
{ #category : #private }
FTTableContainerMorph >> calculateMinVisibleRows [
"Answer the minimal fully visible number of rows to shown in list"
^ self calculateExactVisibleRows floor
]
{ #category : #private }
FTTableContainerMorph >> calculateStartIndexWhenShowing: visibleRows [
"Answer the first row to show when showing visibleRows rows.
This works in case we are exceeding the available rows to show"
| currentIndex startIndex oldIndex |
currentIndex := self table showIndex.
currentIndex + visibleRows - 1 > self table numberOfRows ifTrue: [
currentIndex := self table numberOfRows - visibleRows + 2].
startIndex := currentIndex max: 1.
oldIndex := self table showIndex.
self table basicMoveShowIndexTo: startIndex.
self table announceScrollChangedFrom: oldIndex to: self table showIndex.
^startIndex
]
{ #category : #private }
FTTableContainerMorph >> calculateVisibleRows [
"Answer the rows to show in list.
Ensures we show the maximum amount possible"
^ self calculateMaxVisibleRows min: self table numberOfRows
]
{ #category : #testing }
FTTableContainerMorph >> canRefreshValues [
^ self needsRefreshExposedRows and: [ self table isNotNil and: [ self table hasDataSource ] ]
]
{ #category : #updating }
FTTableContainerMorph >> changed [
self table ifNil: [ ^ self ].
self setNeedsRefreshExposedRows.
super changed
]
{ #category : #drawing }
FTTableContainerMorph >> clipSubmorphs [
^ true
]
{ #category : #private }
FTTableContainerMorph >> createResizableHeaderWith: aMorph between: leftColumn and: rightColumn [
"Create a wrapper morph with a resizable morph et the left (so we bind two columns).
This morph will be completely transparent in all terms... it acts just as a container."
^ Morph new
color: Color transparent;
clipSubmorphs: true;
layoutPolicy: FTRowLayout new;
bounds: aMorph bounds;
addMorphBack: (FTColumnResizerMorph
container: self
left: leftColumn
right: rightColumn);
addMorphBack: aMorph;
yourself
]
{ #category : #initialization }
FTTableContainerMorph >> defaultColor [
^Color transparent
]
{ #category : #drawing }
FTTableContainerMorph >> drawOn: canvas [
| x y cellWidth cellHeight rowsToDisplay rowSubviews highligtedIndexes primarySelectionIndex |
super drawOn: canvas.
self canRefreshValues ifFalse: [ ^ self ]. "Nothing to update yet"
x := self left + self class rowLeftMargin.
y := self top.
cellWidth := self width - self class rowLeftMargin.
cellHeight := self table rowHeight rounded.
highligtedIndexes := self table selectedIndexes , self table highlightedIndexes.
primarySelectionIndex := self table selectedIndex.
"For some superweird reason, calling #calculateExposedRows here instead in #changed (where
it should be called) is 10x faster. Since the whole purpose of this component is speed, for
now I'm calling it here and adding the #setNeedRecalculateRows mechanism.
History, please forgive me."
self updateAllRows.
rowsToDisplay := self exposedRows.
rowSubviews := OrderedCollection new: rowsToDisplay size + 1.
headerRow
ifNotNil: [ headerRow bounds: (self left @ y extent: self width @ cellHeight).
y := y + cellHeight + self table intercellSpacing y.
rowSubviews add: headerRow ].
rowsToDisplay
keysAndValuesDo: [ :rowIndex :row |
| visibleHeight |
visibleHeight := cellHeight min: self bottom - y.
row bounds: (x @ y extent: cellWidth @ visibleHeight).
y := y + visibleHeight + self table intercellSpacing y.
rowSubviews add: row.
(self table selectionModeStrategy
selectablesToHighlightFromRow: row
at: rowIndex
withHighlightedIndexes: highligtedIndexes
andPrimaryIndex: primarySelectionIndex) keysAndValuesDo: [ :morph :isPrimary | morph selectionColor: (self table colorForSelection: isPrimary) ] ].
"We should notify existing rows about deletion and new rows about insertion.
It is required to correctly manage stepping animation of cells"
submorphs
do: [ :each |
each
privateOwner: nil;
outOfWorld: self world ].
submorphs := rowSubviews asArray.
submorphs do: [ :each | each intoWorld: self world ].
self table isResizable ifTrue: [ self addResizeSplitters ].
needsRefreshExposedRows := false
]
{ #category : #drawing }
FTTableContainerMorph >> drawOnAthensCanvas: anAthensCanvas [
self drawOnCanvasWrapperFor: anAthensCanvas
]
{ #category : #private }
FTTableContainerMorph >> exposedRows [
"Answer a dictionary of rowIndex->row pairs"
^ exposedRows
]
{ #category : #accessing }
FTTableContainerMorph >> firstVisibleRowIndex [
^ self exposedRows
ifNotEmpty: [ :rows | rows keys first ]
ifEmpty: [ 0 ]
]
{ #category : #accessing }
FTTableContainerMorph >> headerRow [
^ headerRow
]
{ #category : #initialization }
FTTableContainerMorph >> initialize [
super initialize.
needsRefreshExposedRows := false
]
{ #category : #testing }
FTTableContainerMorph >> isRowIndexExceding: rowIndex [
| headerPresentModificator nextRowIndexByPosition heightWithSpacing |
headerPresentModificator := headerRow ifNotNil: [ 1 ] ifNil: [ 0 ].
nextRowIndexByPosition := rowIndex - self table showIndex + 1 + headerPresentModificator.
heightWithSpacing := self table rowHeight + self table intercellSpacing y.
^ (nextRowIndexByPosition * heightWithSpacing) > self height
]
{ #category : #testing }
FTTableContainerMorph >> isRowIndexFullyVisible: rowIndex [
"Answer if a row is *fully* visible. That means row is completely visible (there is
not hidden part)"
^ (self isRowIndexVisible: rowIndex)
and: [ (self isRowIndexExceding: rowIndex) not ]
]
{ #category : #testing }
FTTableContainerMorph >> isRowIndexVisible: rowIndex [
self exposedRows ifNil: [ ^ false ].
^ self exposedRows includesKey: rowIndex
]
{ #category : #accessing }
FTTableContainerMorph >> lastVisibleRowIndex [
^ self exposedRows
ifNotEmpty: [ :rows | rows keys last ]
ifEmpty: [ 0 ]
]
{ #category : #private }
FTTableContainerMorph >> needsRefreshExposedRows [
^ needsRefreshExposedRows
]
{ #category : #geometry }
FTTableContainerMorph >> outerBounds [
^ self bounds
]
{ #category : #accessing }
FTTableContainerMorph >> rowAndColumnIndexContainingPoint: aPoint [
"answer a tuple containing { rowIndex. columnNumber } to be used for menus, etc.
(check senders for references)"
self exposedRows keysAndValuesDo: [ :rowIndex :row |
(row bounds containsPoint: aPoint) ifTrue: [
row submorphs withIndexDo: [ :each :columnIndex |
(each bounds containsPoint: aPoint)
ifTrue: [ ^ { rowIndex. columnIndex } ] ] ] ].
^ {nil. nil}
]
{ #category : #accessing }
FTTableContainerMorph >> rowIndexContainingPoint: aPoint [
self exposedRows keysAndValuesDo: [ :rowIndex :row |
(row bounds containsPoint: aPoint)
ifTrue: [ ^ rowIndex ] ].
^ nil
]
{ #category : #private }
FTTableContainerMorph >> setNeedsRefreshExposedRows [
needsRefreshExposedRows := true
]
{ #category : #accessing }
FTTableContainerMorph >> table [
^ self owner
]
{ #category : #updating }
FTTableContainerMorph >> updateAllRows [
self table isShowColumnHeaders
ifTrue: [ self updateHeaderRow ]
ifFalse: [ headerRow := nil ].
self updateExposedRows.
]
{ #category : #updating }
FTTableContainerMorph >> updateExposedRows [
| visibleRows numberOfColumns columns columnWidths startIndex |
self canRefreshValues ifFalse: [ ^ self ].
visibleRows := self calculateMaxVisibleRows.
startIndex := self calculateStartIndexWhenShowing: visibleRows.
numberOfColumns := self table numberOfColumns.
columns := self table columns.
columnWidths := self calculateColumnWidths.
exposedRows := SmallDictionary new.
startIndex to: ((startIndex + visibleRows - 1) min: self table numberOfRows) do: [ :rowIndex |
| row |
row := FTTableRowMorph table: self table.
1 to: numberOfColumns do: [ :columnIndex | | cell |
cell := (self table dataSource
cellColumn: (columns at: columnIndex)
row: rowIndex).
cell width: (columnWidths at: columnIndex).
row addMorphBack: cell ].
row privateOwner: self.
exposedRows at: rowIndex put: row ]
]
{ #category : #updating }
FTTableContainerMorph >> updateHeaderRow [
"Recalculates the header row if they are defined.
Please, note that If one of the headers is nil, I assume all are nil and I return.
This is probably not the best approach, but like that I enforce people defines at least
a default if they want headers."
| columnHeaders columnWidths |
self canRefreshValues ifFalse: [ ^ self ].
headerRow := nil.
columnHeaders := Array new: self table numberOfColumns.
columnWidths := self calculateColumnWidths.
self table columns withIndexDo: [ :each :index | | headerCell columnWidth|
columnWidth := columnWidths at: index.
headerCell := self table dataSource headerColumn: each.
headerCell ifNil: [ ^ self ].
headerCell
color: self table headerColor;
width: columnWidth.
columnHeaders at: index put: headerCell.
FTDisplayColumn column: each width: columnWidth ].
headerRow := (FTTableRowMorph table: self table)
privateOwner: self;
addAllMorphs: columnHeaders;
yourself
]
{ #category : #accessing }
FTTableContainerMorph >> visibleRowMorphAtIndex: index [
^ self exposedRows at: index
]