As a followup, attached is a changeset for Pharo8 with a simple
implementation of horizontal scrolling. I tried to keep it minimal.
It adds startColumnIndex var to FTTableContainerMorph which is being set
when FTTableContainerMorph>>adjustToHorizontalScrollBarValue: is triggered.
The main change is in #calculateColumnWidths - it starts distributing
space width from the startColumnIndex to ensure this area gets most
visibility. If startColumnIndex is 0, it reverts to old (current) behavior
- trying to draw columns starting from the first one as space permits.
The scrolling thus jumps to a beginning of the column and I'm not
particularly proud of the thing, but it is better then nothing and suits my
immediate needs. So may be it is worth a look for those who are interested.
To try one can use an example:
FTExamples exampleTableHorizontalScroll
Or even set trialHSB := true in FTTableMorth>>initialize (like I have to
enable it everywhere by default)
(as a side note the list is very quiet, compared to what it used to be
years ago)
пн, 4 нояб. 2019 г. в 14:14, Danil Osipchuk <[email protected]>:
> Hi all
>
> I wonder what is happening to the horizontal scrolling in FastTable, the
> stub is there for years (I've checked the latest 8.0).
> I need it rather badly, could someone advise what is the hindrance there?
> I would try doing it myself, but I've never was doing much of UI
> programming and only recently returned to Pharo again, so have to catch up
> (a lot of things changed for good).
>
> How difficult would be to adopt vertical scrolling approach (which seems
> to be the same problem already solved to layperson )
>
>
> horizontalScrollBarValue: aNumber
> trialHSB ifNotNil: [
> self inform: 'FTTableMorph>>#horizontalScrollBarValue: ', aNumber
> printString , String cr,
> 'Does nothing yet.' , String cr, 'Need some advise here.'
> ].
>
> regards,
> Danil
>
'From Pharo8.0.0 of 7 November 2019 [Build information: Pharo-8.0.0+build.953.sha.febfc2d7be79760a59defe4725cd61822c8ddcc2 (64 Bit)] on 7 November 2019 at 7:26:53.320268 pm'!
Morph subclass: #FTTableContainerMorph
instanceVariableNames: 'needsRefreshExposedRows headerRow exposedRows startColumnIndex'
classVariableNames: ''
poolDictionaries: ''
category: 'Morphic-Widgets-FastTable'!
!FTTableContainerMorph commentStamp: 'DanilOsipchuk 11/7/2019 16:26' prior: 0!
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.
startColumnIndex: An integer - first column to start drawing when scrolling horizontally, nil/0 - old behaviour (no scrolling)
The method #drawOn: is responsible of my rendering.!
FTTableContainerMorph subclass: #FTTableContainerRowNotHomogeneousMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Morphic-Widgets-FastTable'!
!FTFunctionWithField methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 18:29'!
resizeWidget
| topLeft bottomRight |
self flag: #pharoTodo. "Maybe it could be cool to let the user choose if the field need to be at the top or the bottom."
topLeft := table bounds left @ (table bounds bottom - self fieldHeigh - table horizontalScrollBarHeight ).
bottomRight := (table bounds right - table verticalScrollBarWidth) @ (table bottom - table horizontalScrollBarHeight).
field bounds: (topLeft corner: bottomRight)! !
!FTFilterFunctionWithAction methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 18:30'!
resizeButton
| topLeft bottomRight |
self flag: #pharoTodo. "Maybe it could be cool to let the user choose if the field need to be at the top or the bottom."
bottomRight := (table bounds right - table verticalScrollBarWidth) @ (table bottom - table horizontalScrollBarHeight).
topLeft := (bottomRight x - actionButton width) @ (table bounds bottom - self fieldHeigh - table horizontalScrollBarHeight).
actionButton bounds: (topLeft corner: bottomRight)! !
!FTFilterFunctionWithAction methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 18:30'!
resizeField
| topLeft bottomRight |
self flag: #pharoTodo. "Maybe it could be cool to let the user choose if the field need to be at the top or the bottom."
topLeft := table bounds left @ (table bounds bottom - self fieldHeigh - table horizontalScrollBarHeight).
bottomRight := (table bounds right - table verticalScrollBarWidth - (actionButton width + 5))
@ (table bottom - table horizontalScrollBarHeight).
field bounds: (topLeft corner: bottomRight)! !
!FTTableContainerMorph methodsFor: 'initialization' stamp: 'DanilOsipchuk 11/7/2019 15:11'!
initialize
super initialize.
needsRefreshExposedRows := false.
startColumnIndex :=0.! !
!FTTableContainerMorph methodsFor: 'updating' stamp: 'DanilOsipchuk 11/7/2019 13:50'!
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.
(self exposedColumnsRange: columnWidths) 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 ]! !
!FTTableContainerMorph methodsFor: 'updating' stamp: 'DanilOsipchuk 11/7/2019 16:02'!
adjustToHorizontalScrollBarValue: aNumber
| newStartColumnIndex |
newStartColumnIndex := (self table numberOfColumns * aNumber) rounded
min: self table numberOfColumns
max: 1 .
newStartColumnIndex ~= self startColumnIndex
ifTrue: [
self startColumnIndex: newStartColumnIndex.
self changed ]
! !
!FTTableContainerMorph methodsFor: 'updating' stamp: 'DanilOsipchuk 11/7/2019 15:41'!
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."
| columns columnHeaders columnWidths |
self canRefreshValues ifFalse: [ ^ self ].
headerRow := nil.
columns := self table columns.
columnHeaders := OrderedCollection new.
columnWidths := self calculateColumnWidths.
(self exposedColumnsRange: columnWidths) do: [ :index | | column headerCell columnWidth|
column := columns at: index.
columnWidth := columnWidths at: index.
headerCell := self table dataSource headerColumn: column.
headerCell ifNil: [ ^ self ].
headerCell
color: self table headerColor;
width: columnWidth.
columnHeaders addLast: headerCell.
FTDisplayColumn column: column width: columnWidth ].
headerRow := (FTTableHeaderRowMorph table: self table)
privateOwner: self;
addAllMorphs: columnHeaders;
yourself! !
!FTTableContainerMorph methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 16:13'!
calculateColumnWidths
"do three runs
- first collect defined columnwidth that fit
- collect remaining undefined columnwidth
- return if all fit
or collect and distribute remaining width.
DanilOsipchuk: the method was adjusted to distribute space starting from startColumnIndex
to enable horizontal scrolling when columns do not fit the window,
see #columnOrderOfWidthDistribution"
| undefinedColumnWidths widths remainingWidth |
remainingWidth := self table bounds width.
widths := Array new: self table numberOfColumns withAll: 0.
self columnOrderOfWidthDistribution do: [ :idx || column columnWidth |
column := self table columns at: idx.
columnWidth := column acquireWidth: remainingWidth.
widths at: idx put: columnWidth.
remainingWidth := remainingWidth - columnWidth ].
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"
self columnOrderOfWidthDistribution do: [ :idx |
(widths at: idx) = 0 ifTrue: [ widths at: idx put: (remainingWidth / undefinedColumnWidths) ] ].
^widths! !
!FTTableContainerMorph methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 16:07'!
columnOrderOfWidthDistribution
"returns column indexes ordered by priority to available width"
| idxToLast idxToFirstReversed |
self startColumnIndex isZero "a special case implementing old behaviour -- all columns as they are present"
ifTrue: [ ^(1 to: self table numberOfColumns) ].
"new behaviour intended to garantee the visibility of columns around startColumnIndex
first give width starting from startColumnIndex towards end,
then from previous available column towards beginning"
idxToLast := startColumnIndex to: self table numberOfColumns.
idxToFirstReversed := startColumnIndex>1 ifTrue: [startColumnIndex-1 to: 1 by: -1] ifFalse: [#()].
^idxToLast,idxToFirstReversed! !
!FTTableContainerMorph methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 16:01'!
exposedColumnsRange: columnWidths
"Return a subset of indexes for columns which are to be drawn.
startColumnIndex=0 means old behaviour -- try all of them
otherwise we select only indexes of columns having non-zero width"
^self startColumnIndex isZero
ifTrue: [1 to: self table numberOfColumns]
ifFalse: [(1 to: columnWidths size) select: [ :idx | (columnWidths at: idx)>0 ] ]
! !
!FTTableContainerMorph methodsFor: 'accessing' stamp: 'DanilOsipchuk 11/7/2019 16:00'!
startColumnIndex
startColumnIndex ifNil: [ startColumnIndex := 0 ].
^startColumnIndex! !
!FTTableContainerMorph methodsFor: 'accessing' stamp: 'DanilOsipchuk 11/7/2019 15:53'!
startColumnIndex: anObject
startColumnIndex := anObject! !
!FTTableMorph methodsFor: 'private' stamp: 'DanilOsipchuk 11/7/2019 14:50'!
horizontalScrollBarValue: aNumber
trialHSB ifNotNil: [
self container adjustToHorizontalScrollBarValue: aNumber.
].! !
FTTableContainerMorph subclass: #FTTableContainerRowNotHomogeneousMorph
instanceVariableNames: ''
classVariableNames: ''
package: 'Morphic-Widgets-FastTable'!
Morph subclass: #FTTableContainerMorph
instanceVariableNames: 'needsRefreshExposedRows headerRow exposedRows startColumnIndex'
classVariableNames: ''
package: 'Morphic-Widgets-FastTable'!