# TableModelWithSearch

- `Download example`

A more advanced example of Key-Value Coding. This uses a custom `NSArrayController`.

## Sources

### FilteringArrayController.py

    #
    #  FilteringArrayController.py
    #  TableModelWithSearch
    #
    #  Created by Bill Bumgarner on Sun Apr 04 2004.
    #  Copyright (c) 2004 __MyCompanyName__. All rights reserved.
    #

    import re

    import objc
    from Cocoa import NSArrayController
    from objc import super

    kLiteralSearch = "Literal Search"
    kRegularExpressionSearch = "Regular Expression Search"

    def regexForSearchString(searchString, searchType):
        if not searchString:
            return None

        searchString = searchString.strip()
        if searchType == kLiteralSearch:
            searchString = re.escape(searchString.strip()) + r"(?i)"
        return re.compile(searchString)

    def dictValueFilter(dicts, regex):
        for dct in dicts:
            for value in dct.values():
                if regex.search(value):
                    yield dct
                    break

    class FilteringArrayController(NSArrayController):
        searchString = None
        lastRegex = None
        searchType = kLiteralSearch

        def arrangeObjects_(self, objects):
            supermethod = super(FilteringArrayController, self).arrangeObjects_
            try:
                regex = regexForSearchString(self.searchString, self.searchType)
            except:  # noqa: E722, B001
                regex = self.lastRegex
            self.lastRegex = regex
            if regex is None:
                return supermethod(objects)
            return supermethod(list(dictValueFilter(objects, regex)))

        @objc.IBAction
        def performSearch_(self, sender):
            self.searchString = sender.stringValue()
            self.rearrangeObjects()

        @objc.IBAction
        def changeSearchType_(self, searchType):
            self.lastRegex = None
            self.searchString = None
            self.searchType = searchType
            self.rearrangeObjects()

### TableModelWithSearchAppDelegate.py

    import pwd

    from Foundation import NSMutableArray, NSObject

    def getPasswords():
        a = NSMutableArray()
        for pw in pwd.getpwall():
            a.append(
                {
                    "name": pw.pw_name,
                    "password": pw.pw_passwd,
                    "uid": str(pw.pw_uid),
                    "gid": str(pw.pw_gid),
                    "gecos": pw.pw_gecos,
                    "home_dir": pw.pw_dir,
                    "shell": pw.pw_shell,
                }
            )

        return a

    class TableModelWithSearchAppDelegate(NSObject):
        def passwords(self):
            if not hasattr(self, "_cachedpasswords"):
                self._cachedpasswords = getPasswords()
            return self._cachedpasswords

### ToolbarCreator.py

    #
    #  ToolbarCreator.py
    #  TableModelWithSearch
    #
    #  Created by Bill Bumgarner on Sun Apr 04 2004.
    #  Copyright (c) 2004 __MyCompanyName__. All rights reserved.
    #

    import objc
    from Cocoa import (
        NSMenu,
        NSMenuItem,
        NSObject,
        NSToolbar,
        NSToolbarCustomizeToolbarItemIdentifier,
        NSToolbarFlexibleSpaceItemIdentifier,
        NSToolbarItem,
        NSToolbarPrintItemIdentifier,
        NSToolbarSeparatorItemIdentifier,
        NSToolbarSpaceItemIdentifier,
    )
    from FilteringArrayController import kLiteralSearch, kRegularExpressionSearch

    kToolbarIdentifier = "TableModel Toolbar Identifier"
    kSearchFieldItemIdentifier = "TableModel Search Field Identifier"

    class ToolbarCreator(NSObject):
        filteringArrayController = objc.IBOutlet()
        searchField = objc.IBOutlet()
        window = objc.IBOutlet()

        def awakeFromNib(self):
            self.toolbarItemCache = {}

            # create toolbar containing search field
            toolbar = NSToolbar.alloc().initWithIdentifier_(kToolbarIdentifier)
            toolbar.setDelegate_(self)
            toolbar.setAllowsUserCustomization_(True)
            toolbar.setAutosavesConfiguration_(True)

            searchFieldItem = NSToolbarItem.alloc().initWithItemIdentifier_(
                kSearchFieldItemIdentifier
            )
            self.searchFieldItem = searchFieldItem
            searchFieldItem.setLabel_("Search")
            searchFieldItem.setPaletteLabel_("Search Field")
            searchFieldItem.setToolTip_("Search")
            searchFieldItem.setView_(self.searchField)
            searchFieldItem.setMinSize_(self.searchField.bounds().size)
            maxSize = self.searchField.bounds().size
            maxSize.width = maxSize.width + 150
            searchFieldItem.setMaxSize_(maxSize)

            self.toolbarItemCache[kSearchFieldItemIdentifier] = searchFieldItem

            self.window.setToolbar_(toolbar)

            cellMenu = NSMenu.alloc().initWithTitle_("Search Menu")
            # note, bottom up!
            for v in [kRegularExpressionSearch, kLiteralSearch]:
                item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
                    v, "changeSearchType:", ""
                )
                item.setRepresentedObject_(v)
                item.setTarget_(self)
                cellMenu.insertItem_atIndex_(item, 0)
            self.searchField.cell().setSearchMenuTemplate_(cellMenu)
            # this better be the kLiteralSearch menu item
            self.changeSearchType_(item)

        @objc.IBAction
        def changeSearchType_(self, sender):
            obj = sender.representedObject()
            self.searchField.cell().setPlaceholderString_(obj)
            self.searchField.setStringValue_("")
            self.filteringArrayController.changeSearchType_(obj)

        def toolbarDefaultItemIdentifiers_(self, aToolbar):
            return [
                kSearchFieldItemIdentifier,
                NSToolbarFlexibleSpaceItemIdentifier,
                NSToolbarSeparatorItemIdentifier,
                NSToolbarCustomizeToolbarItemIdentifier,
            ]

        def toolbarAllowedItemIdentifiers_(self, aToolbar):
            return [
                kSearchFieldItemIdentifier,
                NSToolbarFlexibleSpaceItemIdentifier,
                NSToolbarSpaceItemIdentifier,
                NSToolbarSeparatorItemIdentifier,
                NSToolbarPrintItemIdentifier,
                NSToolbarCustomizeToolbarItemIdentifier,
            ]

        def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(
            self, toolbar, itemIdentifier, flag
        ):
            newItem = NSToolbarItem.alloc().initWithItemIdentifier_(itemIdentifier)
            item = self.toolbarItemCache[itemIdentifier]

            newItem.setLabel_(item.label())
            newItem.setPaletteLabel_(item.paletteLabel())
            if item.view():
                newItem.setView_(item.view())
            else:
                newItem.setImage_(item.image())

            newItem.setToolTip_(item.toolTip())
            newItem.setTarget_(item.target())
            newItem.setAction_(item.action())
            newItem.setMenuFormRepresentation_(item.menuFormRepresentation())

            if newItem.view():
                newItem.setMinSize_(item.minSize())
                newItem.setMaxSize_(item.maxSize())

            return newItem

### main.py

    #
    #  __main__.py
    #  TableModelWithSearch
    #
    #  Created by Bob Ippolito on Sun Apr 04 2004.
    #  Copyright (c) 2004 Bob Ippolito. All rights reserved.
    #

    # import classes required to start application
    import FilteringArrayController  # noqa: F401
    import TableModelWithSearchAppDelegate  # noqa: F401
    import ToolbarCreator  # noqa: F401
    from PyObjCTools import AppHelper

    # start the event loop
    AppHelper.runEventLoop(argv=[])

### setup.py

    """
    Script for building the example, alternative to the Xcode project

    Usage:
        python3 setup.py py2app
    """
    from setuptools import setup

    setup(
        name="TableModelWithSearch",
        app=["main.py"],
        data_files=["English.lproj"],
        setup_requires=["py2app", "pyobjc-framework-Cocoa"],
    )