<div dir="ltr">Very nice, looking forward to trying it out.<div><br></div><div>Thanks!</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Oct 23, 2019 at 8:31 AM <<a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Marcel Taeumel uploaded a new version of Morphic to project The Trunk:<br>
<a href="http://source.squeak.org/trunk/Morphic-mt.1582.mcz" rel="noreferrer" target="_blank">http://source.squeak.org/trunk/Morphic-mt.1582.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Morphic-mt.1582<br>
Author: mt<br>
Time: 23 October 2019, 3:31:24.67861 pm<br>
UUID: 82af72b9-4198-4bd2-8e7e-e8733d9018c7<br>
Ancestors: Morphic-mt.1581<br>
<br>
Adds things for column-specific list filtering:<br>
<br>
- backgroundColor for LazyListMorph<br>
- optional filter-term indication for LazyListMorph<br>
- column highlights for multi-column lists<br>
- Character tab to cycle between columns<br>
<br>
Note that I opted to not use the [space] key for column toggling because (a) we have to widget-focus cycling via [tab] at the moment and (b) the space character might be a valuable filter term.<br>
<br>
=============== Diff against Morphic-mt.1581 ===============<br>
<br>
Item was changed:<br>
Morph subclass: #LazyListMorph<br>
+ instanceVariableNames: 'listItems listIcons listFilterOffsets font selectedRow selectedRows preSelectedRow listSource maxWidth columnIndex iconExtent backgroundColor showFilter'<br>
- instanceVariableNames: 'listItems listIcons listFilterOffsets font selectedRow selectedRows preSelectedRow listSource maxWidth columnIndex iconExtent'<br>
classVariableNames: ''<br>
poolDictionaries: ''<br>
category: 'Morphic-Widgets'!<br>
<br>
!LazyListMorph commentStamp: 'mt 10/13/2019 19:44' prior: 0!<br>
The morph that displays the list in a PluggableListMorph. It is "lazy" because it will only request the list items that it actually needs to display.<br>
<br>
I will cache the maximum width of my items in maxWidth to avoid this potentially expensive and frequent computation.<br>
<br>
The following layout properties are supported:<br>
- #cellPositioning: #leftCenter [default], #center, #rightCenter<br>
- #cellInset: [default: 3@0 corner: 3@0]!<br>
<br>
Item was added:<br>
+ ----- Method: LazyListMorph>>backgroundColor (in category 'accessing') -----<br>
+ backgroundColor<br>
+ "Since #color is this morph's default text color, this extra property is used for the actual background color. Supports nil."<br>
+ <br>
+ ^ backgroundColor!<br>
<br>
Item was added:<br>
+ ----- Method: LazyListMorph>>backgroundColor: (in category 'accessing') -----<br>
+ backgroundColor: aColor<br>
+ <br>
+ backgroundColor = aColor ifTrue: [^ self].<br>
+ backgroundColor := aColor.<br>
+ <br>
+ self changed.<br>
+ "Invalidate owner because we want to fill the vertical axis in the viewport entirely."<br>
+ self owner ifNotNil: [:o | o changed].!<br>
<br>
Item was changed:<br>
----- Method: LazyListMorph>>displayFilterOn:for:in:font: (in category 'drawing') -----<br>
displayFilterOn: canvas for: row in: drawBounds font: font<br>
"Draw filter matches if any."<br>
<br>
| fillStyle fillHeight |<br>
+ self showFilter ifFalse: [^ self].<br>
- listSource filterableList ifFalse: [^ self].<br>
<br>
fillHeight := font height.<br>
fillStyle := self filterColor isColor<br>
ifTrue: [SolidFillStyle color: self filterColor]<br>
ifFalse: [self filterColor].<br>
fillStyle isGradientFill ifTrue: [<br>
fillStyle origin: drawBounds topLeft.<br>
fillStyle direction: 0@ fillHeight].<br>
<br>
(self filterOffsets: row) do: [:offset |<br>
| highlightRectangle |<br>
highlightRectangle := ((drawBounds left + offset first first) @ drawBounds top<br>
corner: (drawBounds left + offset first last) @ (drawBounds top + fillHeight)).<br>
canvas<br>
frameAndFillRoundRect: (highlightRectangle outsetBy: 1@0)<br>
radius: (3 * RealEstateAgent scaleFactor) truncated<br>
fillStyle: fillStyle<br>
borderWidth: (1 * RealEstateAgent scaleFactor) truncated<br>
borderColor: fillStyle asColor twiceDarker.<br>
canvas<br>
drawString: offset second<br>
in: highlightRectangle<br>
font: font<br>
color: self filterTextColor].!<br>
<br>
Item was changed:<br>
----- Method: LazyListMorph>>drawOn: (in category 'drawing') -----<br>
drawOn: aCanvas<br>
<br>
| topRow bottomRow |<br>
+ self backgroundColor ifNotNil: [:color |<br>
+ aCanvas fillRectangle: (self topLeft corner: self right @ ((self owner ifNil: [self]) bottom)) color: color].<br>
+ <br>
self getListSize = 0 ifTrue: [ ^self ].<br>
<br>
self drawPreSelectionOn: aCanvas.<br>
<br>
topRow := self topVisibleRowForCanvas: aCanvas.<br>
bottomRow := self bottomVisibleRowForCanvas: aCanvas.<br>
<br>
"Draw multi-selection."<br>
self listSource hasMultiSelection ifTrue: [<br>
topRow to: bottomRow do: [ :row |<br>
(self listSource itemSelectedAmongMultiple: row) ifTrue: [<br>
self drawBackgroundForMulti: row on: aCanvas ] ] ].<br>
self drawSelectionOn: aCanvas.<br>
<br>
"Draw hovered row if preference enabled."<br>
PluggableListMorph highlightHoveredRow ifTrue: [<br>
self listSource hoverRow > 0 ifTrue: [<br>
self highlightHoverRow: listSource hoverRow on: aCanvas ] ].<br>
<br>
"Draw all visible rows."<br>
topRow to: bottomRow do: [ :row |<br>
self display: (self item: row) atRow: row on: aCanvas ].<br>
<br>
"Finally, highlight drop row for drag/drop operations.."<br>
self listSource potentialDropRow > 0 ifTrue: [<br>
self highlightPotentialDropRow: self listSource potentialDropRow on: aCanvas ].!<br>
<br>
Item was added:<br>
+ ----- Method: LazyListMorph>>showFilter (in category 'accessing') -----<br>
+ showFilter<br>
+ <br>
+ ^ (showFilter ~~ false and: [listSource filterableList])!<br>
<br>
Item was added:<br>
+ ----- Method: LazyListMorph>>showFilter: (in category 'accessing') -----<br>
+ showFilter: aBoolean<br>
+ <br>
+ showFilter = aBoolean ifTrue: [^ self].<br>
+ showFilter := aBoolean.<br>
+ self changed.!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>filterColumnColor (in category 'filtering') -----<br>
+ filterColumnColor<br>
+ <br>
+ ^ (Color gray: 0.85) alpha: 0.4!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>filterColumnIndex (in category 'filtering') -----<br>
+ filterColumnIndex<br>
+ "Which column to apply the filter to?"<br>
+ <br>
+ | i |<br>
+ i := 0.<br>
+ self listMorphs<br>
+ detect: [:m | i := i + 1. m backgroundColor notNil]<br>
+ ifNone: [i := 0].<br>
+ ^ i!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>filterList:columnIndex:matching: (in category 'filtering') -----<br>
+ filterList: columns columnIndex: index matching: aPattern<br>
+ "A matching row has a match in at least one column."<br>
+ <br>
+ | frontMatching substringMatching rowCount columnCount tmp |<br>
+ aPattern ifEmpty: [^ columns].<br>
+ columns ifEmpty: [^ columns].<br>
+ <br>
+ rowCount := columns first size.<br>
+ rowCount = 0 ifTrue: [^ columns].<br>
+ columnCount := columns size.<br>
+ <br>
+ frontMatching := Array new: columnCount.<br>
+ 1 to: columnCount do: [:c | frontMatching at: c put: OrderedCollection new].<br>
+ substringMatching := Array new: columnCount.<br>
+ 1 to: columnCount do: [:c | substringMatching at: c put: OrderedCollection new].<br>
+ <br>
+ modelToView := Dictionary new.<br>
+ viewToModel := Dictionary new.<br>
+ tmp := OrderedCollection new.<br>
+ <br>
+ 1 to: rowCount do: [:rowIndex |<br>
+ | match foundPos |<br>
+ match := false.<br>
+ foundPos := self<br>
+ filterListItem: ((columns at: index) at: rowIndex)<br>
+ matching: aPattern.<br>
+ foundPos = 1<br>
+ ifTrue: [<br>
+ 1 to: columnCount do: [:colIndex |<br>
+ (frontMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)].<br>
+ modelToView at: rowIndex put: frontMatching first size.<br>
+ viewToModel at: frontMatching first size put: rowIndex]<br>
+ ifFalse: [foundPos > 1 ifTrue: [<br>
+ 1 to: columnCount do: [:colIndex |<br>
+ (substringMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)].<br>
+ tmp add: rowIndex; add: substringMatching first size]]<br>
+ ].<br>
+ <br>
+ tmp pairsDo: [:modelIndex :viewIndex |<br>
+ modelToView at: modelIndex put: viewIndex + frontMatching first size.<br>
+ viewToModel at: viewIndex + frontMatching first size put: modelIndex].<br>
+ <br>
+ ^ (1 to: columnCount) collect: [:colIndex |<br>
+ (frontMatching at: colIndex), (substringMatching at: colIndex)]<br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ !<br>
<br>
Item was changed:<br>
----- Method: PluggableMultiColumnListMorph>>filterList:matching: (in category 'filtering') -----<br>
filterList: columns matching: aPattern<br>
"A matching row has a match in at least one column."<br>
<br>
| frontMatching substringMatching rowCount columnCount tmp |<br>
aPattern ifEmpty: [^ columns].<br>
columns ifEmpty: [^ columns].<br>
<br>
+ "Enable column-specific filtering."<br>
+ self filterColumnIndex in: [:index |<br>
+ index > 0 ifTrue: [^ self filterList: columns columnIndex: index matching: aPattern]].<br>
+ <br>
rowCount := columns first size.<br>
rowCount = 0 ifTrue: [^ columns].<br>
columnCount := columns size.<br>
<br>
frontMatching := Array new: columnCount.<br>
1 to: columnCount do: [:c | frontMatching at: c put: OrderedCollection new].<br>
substringMatching := Array new: columnCount.<br>
1 to: columnCount do: [:c | substringMatching at: c put: OrderedCollection new].<br>
<br>
modelToView := Dictionary new.<br>
viewToModel := Dictionary new.<br>
tmp := OrderedCollection new.<br>
<br>
1 to: rowCount do: [:rowIndex |<br>
| match foundPos |<br>
match := false.<br>
foundPos := 0.<br>
1 to: columnCount do: [:colIndex |<br>
match := match or: [(foundPos := (self<br>
filterListItem: ((columns at: colIndex) at: rowIndex)<br>
matching: aPattern)+colIndex) > colIndex]].<br>
match & (foundPos = 2) "means front match in first column"<br>
ifTrue: [<br>
1 to: columnCount do: [:colIndex |<br>
(frontMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)].<br>
modelToView at: rowIndex put: frontMatching first size.<br>
viewToModel at: frontMatching first size put: rowIndex]<br>
ifFalse: [match ifTrue: [<br>
1 to: columnCount do: [:colIndex |<br>
(substringMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)].<br>
tmp add: rowIndex; add: substringMatching first size]]<br>
].<br>
<br>
tmp pairsDo: [:modelIndex :viewIndex |<br>
modelToView at: modelIndex put: viewIndex + frontMatching first size.<br>
viewToModel at: viewIndex + frontMatching first size put: modelIndex].<br>
<br>
^ (1 to: columnCount) collect: [:colIndex |<br>
(frontMatching at: colIndex), (substringMatching at: colIndex)]<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>highlightNextColumn (in category 'filtering') -----<br>
+ highlightNextColumn<br>
+ <br>
+ | i currentColumn nextColumn |<br>
+ i := self filterColumnIndex.<br>
+ i = 0 ifTrue: [self listMorphs do: [:m | m showFilter: false]].<br>
+ <br>
+ currentColumn := self listMorphs at: (i max: 1).<br>
+ nextColumn := self listMorphs at: i \\ self listMorphs size + 1.<br>
+ <br>
+ currentColumn<br>
+ showFilter: false;<br>
+ backgroundColor: nil.<br>
+ <br>
+ nextColumn<br>
+ showFilter: true;<br>
+ backgroundColor: self filterColumnColor.!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>highlightNoColumn (in category 'filtering') -----<br>
+ highlightNoColumn<br>
+ <br>
+ self listMorphs do: [:m |<br>
+ m showFilter: true; backgroundColor: nil].!<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>removeFilter (in category 'filtering') -----<br>
+ removeFilter<br>
+ <br>
+ self highlightNoColumn.<br>
+ super removeFilter.<br>
+ !<br>
<br>
Item was added:<br>
+ ----- Method: PluggableMultiColumnListMorph>>specialKeyPressed: (in category 'filtering') -----<br>
+ specialKeyPressed: asciiValue<br>
+ "Use the [Tab] key to filter specific columns."<br>
+ <br>
+ ^ asciiValue = Character tab asciiValue<br>
+ ifTrue: [self highlightNextColumn]<br>
+ ifFalse: [super specialKeyPressed: asciiValue].!<br>
<br>
Item was changed:<br>
----- Method: PluggableMultiColumnListMorph>>updateColumns (in category 'updating') -----<br>
updateColumns<br>
"The number of columns must match the number of list morphs."<br>
<br>
| columnsChanged |<br>
columnsChanged := self columnCount ~= listMorphs size.<br>
<br>
[self columnCount < listMorphs size]<br>
whileTrue: [<br>
listMorphs removeLast delete].<br>
<br>
[self columnCount > listMorphs size]<br>
whileTrue: [<br>
listMorphs addLast: self createListMorph.<br>
self scroller addMorphBack: listMorphs last].<br>
<br>
listMorphs doWithIndex: [:listMorph :columnIndex |<br>
listMorph<br>
columnIndex: columnIndex;<br>
+ color: self textColor;<br>
cellPositioning: (self cellPositioningAtColumn: columnIndex);<br>
cellInset: (self cellInsetAtColumn: columnIndex);<br>
hResizing: (self hResizingAtColumn: columnIndex);<br>
spaceFillWeight: (self spaceFillWeightAtColumn: columnIndex)].<br>
<br>
columnsChanged ifTrue: [self setListParameters].!<br>
<br>
<br>
</blockquote></div>