Marcel Taeumel uploaded a new version of Morphic to project The Trunk:
http://source.squeak.org/trunk/Morphic-mt.1582.mcz ==================== Summary ==================== Name: Morphic-mt.1582 Author: mt Time: 23 October 2019, 3:31:24.67861 pm UUID: 82af72b9-4198-4bd2-8e7e-e8733d9018c7 Ancestors: Morphic-mt.1581 Adds things for column-specific list filtering: - backgroundColor for LazyListMorph - optional filter-term indication for LazyListMorph - column highlights for multi-column lists - Character tab to cycle between columns 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. =============== Diff against Morphic-mt.1581 =============== Item was changed: Morph subclass: #LazyListMorph + instanceVariableNames: 'listItems listIcons listFilterOffsets font selectedRow selectedRows preSelectedRow listSource maxWidth columnIndex iconExtent backgroundColor showFilter' - instanceVariableNames: 'listItems listIcons listFilterOffsets font selectedRow selectedRows preSelectedRow listSource maxWidth columnIndex iconExtent' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Widgets'! !LazyListMorph commentStamp: 'mt 10/13/2019 19:44' prior: 0! 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. I will cache the maximum width of my items in maxWidth to avoid this potentially expensive and frequent computation. The following layout properties are supported: - #cellPositioning: #leftCenter [default], #center, #rightCenter - #cellInset: [default: 3@0 corner: 3@0]! Item was added: + ----- Method: LazyListMorph>>backgroundColor (in category 'accessing') ----- + backgroundColor + "Since #color is this morph's default text color, this extra property is used for the actual background color. Supports nil." + + ^ backgroundColor! Item was added: + ----- Method: LazyListMorph>>backgroundColor: (in category 'accessing') ----- + backgroundColor: aColor + + backgroundColor = aColor ifTrue: [^ self]. + backgroundColor := aColor. + + self changed. + "Invalidate owner because we want to fill the vertical axis in the viewport entirely." + self owner ifNotNil: [:o | o changed].! Item was changed: ----- Method: LazyListMorph>>displayFilterOn:for:in:font: (in category 'drawing') ----- displayFilterOn: canvas for: row in: drawBounds font: font "Draw filter matches if any." | fillStyle fillHeight | + self showFilter ifFalse: [^ self]. - listSource filterableList ifFalse: [^ self]. fillHeight := font height. fillStyle := self filterColor isColor ifTrue: [SolidFillStyle color: self filterColor] ifFalse: [self filterColor]. fillStyle isGradientFill ifTrue: [ fillStyle origin: drawBounds topLeft. fillStyle direction: 0@ fillHeight]. (self filterOffsets: row) do: [:offset | | highlightRectangle | highlightRectangle := ((drawBounds left + offset first first) @ drawBounds top corner: (drawBounds left + offset first last) @ (drawBounds top + fillHeight)). canvas frameAndFillRoundRect: (highlightRectangle outsetBy: 1@0) radius: (3 * RealEstateAgent scaleFactor) truncated fillStyle: fillStyle borderWidth: (1 * RealEstateAgent scaleFactor) truncated borderColor: fillStyle asColor twiceDarker. canvas drawString: offset second in: highlightRectangle font: font color: self filterTextColor].! Item was changed: ----- Method: LazyListMorph>>drawOn: (in category 'drawing') ----- drawOn: aCanvas | topRow bottomRow | + self backgroundColor ifNotNil: [:color | + aCanvas fillRectangle: (self topLeft corner: self right @ ((self owner ifNil: [self]) bottom)) color: color]. + self getListSize = 0 ifTrue: [ ^self ]. self drawPreSelectionOn: aCanvas. topRow := self topVisibleRowForCanvas: aCanvas. bottomRow := self bottomVisibleRowForCanvas: aCanvas. "Draw multi-selection." self listSource hasMultiSelection ifTrue: [ topRow to: bottomRow do: [ :row | (self listSource itemSelectedAmongMultiple: row) ifTrue: [ self drawBackgroundForMulti: row on: aCanvas ] ] ]. self drawSelectionOn: aCanvas. "Draw hovered row if preference enabled." PluggableListMorph highlightHoveredRow ifTrue: [ self listSource hoverRow > 0 ifTrue: [ self highlightHoverRow: listSource hoverRow on: aCanvas ] ]. "Draw all visible rows." topRow to: bottomRow do: [ :row | self display: (self item: row) atRow: row on: aCanvas ]. "Finally, highlight drop row for drag/drop operations.." self listSource potentialDropRow > 0 ifTrue: [ self highlightPotentialDropRow: self listSource potentialDropRow on: aCanvas ].! Item was added: + ----- Method: LazyListMorph>>showFilter (in category 'accessing') ----- + showFilter + + ^ (showFilter ~~ false and: [listSource filterableList])! Item was added: + ----- Method: LazyListMorph>>showFilter: (in category 'accessing') ----- + showFilter: aBoolean + + showFilter = aBoolean ifTrue: [^ self]. + showFilter := aBoolean. + self changed.! Item was added: + ----- Method: PluggableMultiColumnListMorph>>filterColumnColor (in category 'filtering') ----- + filterColumnColor + + ^ (Color gray: 0.85) alpha: 0.4! Item was added: + ----- Method: PluggableMultiColumnListMorph>>filterColumnIndex (in category 'filtering') ----- + filterColumnIndex + "Which column to apply the filter to?" + + | i | + i := 0. + self listMorphs + detect: [:m | i := i + 1. m backgroundColor notNil] + ifNone: [i := 0]. + ^ i! Item was added: + ----- Method: PluggableMultiColumnListMorph>>filterList:columnIndex:matching: (in category 'filtering') ----- + filterList: columns columnIndex: index matching: aPattern + "A matching row has a match in at least one column." + + | frontMatching substringMatching rowCount columnCount tmp | + aPattern ifEmpty: [^ columns]. + columns ifEmpty: [^ columns]. + + rowCount := columns first size. + rowCount = 0 ifTrue: [^ columns]. + columnCount := columns size. + + frontMatching := Array new: columnCount. + 1 to: columnCount do: [:c | frontMatching at: c put: OrderedCollection new]. + substringMatching := Array new: columnCount. + 1 to: columnCount do: [:c | substringMatching at: c put: OrderedCollection new]. + + modelToView := Dictionary new. + viewToModel := Dictionary new. + tmp := OrderedCollection new. + + 1 to: rowCount do: [:rowIndex | + | match foundPos | + match := false. + foundPos := self + filterListItem: ((columns at: index) at: rowIndex) + matching: aPattern. + foundPos = 1 + ifTrue: [ + 1 to: columnCount do: [:colIndex | + (frontMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)]. + modelToView at: rowIndex put: frontMatching first size. + viewToModel at: frontMatching first size put: rowIndex] + ifFalse: [foundPos > 1 ifTrue: [ + 1 to: columnCount do: [:colIndex | + (substringMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)]. + tmp add: rowIndex; add: substringMatching first size]] + ]. + + tmp pairsDo: [:modelIndex :viewIndex | + modelToView at: modelIndex put: viewIndex + frontMatching first size. + viewToModel at: viewIndex + frontMatching first size put: modelIndex]. + + ^ (1 to: columnCount) collect: [:colIndex | + (frontMatching at: colIndex), (substringMatching at: colIndex)] + + + + + + + + + + + + + + ! Item was changed: ----- Method: PluggableMultiColumnListMorph>>filterList:matching: (in category 'filtering') ----- filterList: columns matching: aPattern "A matching row has a match in at least one column." | frontMatching substringMatching rowCount columnCount tmp | aPattern ifEmpty: [^ columns]. columns ifEmpty: [^ columns]. + "Enable column-specific filtering." + self filterColumnIndex in: [:index | + index > 0 ifTrue: [^ self filterList: columns columnIndex: index matching: aPattern]]. + rowCount := columns first size. rowCount = 0 ifTrue: [^ columns]. columnCount := columns size. frontMatching := Array new: columnCount. 1 to: columnCount do: [:c | frontMatching at: c put: OrderedCollection new]. substringMatching := Array new: columnCount. 1 to: columnCount do: [:c | substringMatching at: c put: OrderedCollection new]. modelToView := Dictionary new. viewToModel := Dictionary new. tmp := OrderedCollection new. 1 to: rowCount do: [:rowIndex | | match foundPos | match := false. foundPos := 0. 1 to: columnCount do: [:colIndex | match := match or: [(foundPos := (self filterListItem: ((columns at: colIndex) at: rowIndex) matching: aPattern)+colIndex) > colIndex]]. match & (foundPos = 2) "means front match in first column" ifTrue: [ 1 to: columnCount do: [:colIndex | (frontMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)]. modelToView at: rowIndex put: frontMatching first size. viewToModel at: frontMatching first size put: rowIndex] ifFalse: [match ifTrue: [ 1 to: columnCount do: [:colIndex | (substringMatching at: colIndex) add: ((columns at: colIndex) at: rowIndex)]. tmp add: rowIndex; add: substringMatching first size]] ]. tmp pairsDo: [:modelIndex :viewIndex | modelToView at: modelIndex put: viewIndex + frontMatching first size. viewToModel at: viewIndex + frontMatching first size put: modelIndex]. ^ (1 to: columnCount) collect: [:colIndex | (frontMatching at: colIndex), (substringMatching at: colIndex)] ! Item was added: + ----- Method: PluggableMultiColumnListMorph>>highlightNextColumn (in category 'filtering') ----- + highlightNextColumn + + | i currentColumn nextColumn | + i := self filterColumnIndex. + i = 0 ifTrue: [self listMorphs do: [:m | m showFilter: false]]. + + currentColumn := self listMorphs at: (i max: 1). + nextColumn := self listMorphs at: i \\ self listMorphs size + 1. + + currentColumn + showFilter: false; + backgroundColor: nil. + + nextColumn + showFilter: true; + backgroundColor: self filterColumnColor.! Item was added: + ----- Method: PluggableMultiColumnListMorph>>highlightNoColumn (in category 'filtering') ----- + highlightNoColumn + + self listMorphs do: [:m | + m showFilter: true; backgroundColor: nil].! Item was added: + ----- Method: PluggableMultiColumnListMorph>>removeFilter (in category 'filtering') ----- + removeFilter + + self highlightNoColumn. + super removeFilter. + ! Item was added: + ----- Method: PluggableMultiColumnListMorph>>specialKeyPressed: (in category 'filtering') ----- + specialKeyPressed: asciiValue + "Use the [Tab] key to filter specific columns." + + ^ asciiValue = Character tab asciiValue + ifTrue: [self highlightNextColumn] + ifFalse: [super specialKeyPressed: asciiValue].! Item was changed: ----- Method: PluggableMultiColumnListMorph>>updateColumns (in category 'updating') ----- updateColumns "The number of columns must match the number of list morphs." | columnsChanged | columnsChanged := self columnCount ~= listMorphs size. [self columnCount < listMorphs size] whileTrue: [ listMorphs removeLast delete]. [self columnCount > listMorphs size] whileTrue: [ listMorphs addLast: self createListMorph. self scroller addMorphBack: listMorphs last]. listMorphs doWithIndex: [:listMorph :columnIndex | listMorph columnIndex: columnIndex; + color: self textColor; cellPositioning: (self cellPositioningAtColumn: columnIndex); cellInset: (self cellInsetAtColumn: columnIndex); hResizing: (self hResizingAtColumn: columnIndex); spaceFillWeight: (self spaceFillWeightAtColumn: columnIndex)]. columnsChanged ifTrue: [self setListParameters].! |
Best, Marcel
|
In reply to this post by commits-2
Very nice, looking forward to trying it out. Thanks! On Wed, Oct 23, 2019 at 8:31 AM <[hidden email]> wrote: Marcel Taeumel uploaded a new version of Morphic to project The Trunk: |
Looks great! Some ideas:
- 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:
Would it be possible to display such filters as by giving them a grey background color?
- 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"? - Wouldn't it be more convenient if you could also use the cursor to focus a column? :)
Best, Christoph Von: Squeak-dev <[hidden email]> im Auftrag von Chris Muller <[hidden email]>
Gesendet: Freitag, 25. Oktober 2019 01:36:31 An: squeak dev Cc: [hidden email] Betreff: Re: [squeak-dev] The Trunk: Morphic-mt.1582.mcz Very nice, looking forward to trying it out.
Thanks!
On Wed, Oct 23, 2019 at 8:31 AM <[hidden email]> wrote:
Marcel Taeumel uploaded a new version of Morphic to project The Trunk:
Carpe Squeak!
|
Hi Christoph. > I find it confusing that filters from columns that are not currently selected are not displayed, but still active. Columns have no filter (term). The entire list has the filter. Your notion of a column "being active" is interesting. The filter changes only when typing letters. The user can hit tab to either configure that next filter action or look at the matching terms in a specific column. There is no new "being active" mode. It's still just "all items" or "filtered items". It could well be that two different columns would yield the same filter result. > Would it be possible to display such filters as by giving them a grey background color? That would require a new color (or property) to be spread across all existing UI themes. I suppose. While that would be possible, I don't think it would improve things substantially. Just my two cents. :-) > Wouldn't it be more convenient if you could also use the cursor to focus a column? :) I suppose not. You are talking about actual selection. There is no actual selection of the column. Let's better not mix those things up unless we can really provide column selection for the model. So make it visually and interactively different from actual selection. For now. :-) It's no spreadsheet. I suppose the number of columns is usually small. Cycle-through should work. > I would like no column to be selected again. Sounds like a good idea. Would you also reset the current filter if you cycle through columns? > Looks great! Some ideas. Keep them coming. :-) Watch out for trade-offs vs. coherent/sound new features. Column filtering is definitely a trade-off not to be pushed too far. Try looking at other widget libraries in other systems to learn about what users expect these days... Best, Marcel
|
Hi Marcel,
yes, you're absolutely right, over-designing should be strictly avoided :)
I admit I didn't yet study your whole code, so my comments only represent opinions from an end-user. From my point of view, it is just a bit weird that the list contains filters you cannot see directly. Maybe it would be better to reset the filter if the column selection was changed? Or alternatively, directly apply the latest filter to the next selected column?
> Try looking at other widget libraries in other systems to learn about what users expect these days... Any special recommendations? :-) While Squeak does not always have the most fancy look, I believe I never met another UI that provides similar productivity ... :D
Best,
Christoph
Von: Squeak-dev <[hidden email]> im Auftrag von Taeumel, Marcel
Gesendet: Samstag, 2. November 2019 13:26:01 An: John Pfersich via Squeak-dev Cc: [hidden email] Betreff: Re: [squeak-dev] The Trunk: Morphic-mt.1582.mcz Hi Christoph.
> I find it confusing that filters from columns that are not currently selected are not displayed, but still active.
Columns have no filter (term). The entire list has the filter. Your notion of a column "being active" is interesting. The filter changes
only when typing letters. The user can hit tab to either configure that next filter action or look at the matching terms in a specific column. There is no new "being active" mode. It's still just "all items" or "filtered items". It could well be that two different
columns would yield the same filter result.
> Would it be possible to display such filters as by giving them a grey background color?
That would require a new color (or property) to be spread across all existing UI themes. I suppose. While that would be possible, I don't think it would improve things substantially.
Just my two cents. :-)
> Wouldn't it be more convenient if you could also use the cursor to focus a column? :)
I suppose not. You are talking about actual selection. There is no actual selection of the column. Let's better not mix those things up unless we can really provide column selection
for the model. So make it visually and interactively different from actual selection. For now. :-)
It's no spreadsheet. I suppose the number of columns is usually small. Cycle-through should work.
> I would like
no column to be selected again.
Sounds like a good idea. Would you also reset the current filter if you cycle through columns?
> Looks great! Some ideas.
Keep them coming. :-) Watch out for trade-offs vs. coherent/sound new features. Column filtering is definitely a trade-off not to be pushed too far. Try looking at other widget libraries
in other systems to learn about what users expect these days...
Best,
Marcel
Carpe Squeak!
|
Free forum by Nabble | Edit this page |