<body><div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000">
<img src="cid:03332b89-810e-4018-9c8a-0f3e8d2547a1" width="auto"></img><div><br></div><div>Best,</div><div>Marcel</div><div class="mb_sig"></div><blockquote class="history_container" type="cite" style="border-left-style:solid;border-width:1px; margin-top:20px; margin-left:0px;padding-left:10px;">
<p style="color: #AAAAAA; margin-top: 10px;">Am 23.10.2019 15:31:47 schrieb commits@source.squeak.org <commits@source.squeak.org>:</p><div style="font-family:Arial,Helvetica,sans-serif">Marcel Taeumel uploaded a new version of Morphic to project The Trunk:<br>http://source.squeak.org/trunk/Morphic-mt.1582.mcz<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=""><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></div></blockquote>
</div></body>