<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
<div id="divtagdefaultwrapper" style="font-size:12pt;color:#000000;font-family:Calibri,Helvetica,sans-serif;" dir="ltr">
<p>Looks great! Some ideas:</p>
<p><br>
</p>
<p>- I find it confusing that filters from columns that are not currently selected are not displayed, but still active. In this example, I filtered the left column for "tex" and then selected the right one. Now there is no visual indication of the left filter:</p>
<p><br>
</p>
<p><img size="136784" contenttype="image/png" id="img339071" style="max-width: 99.9%; user-select: none;" contextid="img60989" tabindex="0" src="cid:17e775c3-8a76-46c9-9596-4e080bfb8df5"><br>
</p>
<p><br>
</p>
<p>Would it be possible to display such filters as by giving them a grey background color?</p>
<p><br>
</p>
<p>- If I pressed Tab again on the screenshot above, I would like no column to be selected again. Why not implement tab cycling along the series "all columns, col1, col2, ..., coln, all columns"?</p>
<p>- Wouldn't it be more convenient if you could also use the cursor to focus a column? :)</p>
<p><br>
</p>
<p>Best,</p>
<p>Christoph</p>
<div id="Signature">
<div name="divtagdefaultwrapper" style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:; margin:0">
<div><font size="2" color="#808080"></font></div>
</div>
</div>
</div>
<hr style="display:inline-block;width:98%" tabindex="-1">
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif" style="font-size:11pt" color="#000000"><b>Von:</b> Squeak-dev <squeak-dev-bounces@lists.squeakfoundation.org> im Auftrag von Chris Muller <asqueaker@gmail.com><br>
<b>Gesendet:</b> Freitag, 25. Oktober 2019 01:36:31<br>
<b>An:</b> squeak dev<br>
<b>Cc:</b> packages@lists.squeakfoundation.org<br>
<b>Betreff:</b> Re: [squeak-dev] The Trunk: Morphic-mt.1582.mcz</font>
<div> </div>
</div>
<div>
<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>
</div>
</body>
</html>