<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>