{"id":15719590,"url":"https://github.com/patrickkidd/pkqtbridge","last_synced_at":"2025-03-30T22:44:34.781Z","repository":{"id":145976736,"uuid":"318927213","full_name":"patrickkidd/pkqtbridge","owner":"patrickkidd","description":"Mixins for pyqt/qtquick apps.","archived":false,"fork":false,"pushed_at":"2020-12-06T22:24:00.000Z","size":59,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-06T04:12:05.206Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patrickkidd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-06T01:38:20.000Z","updated_at":"2020-12-06T22:24:02.000Z","dependencies_parsed_at":"2023-03-29T04:02:15.969Z","dependency_job_id":null,"html_url":"https://github.com/patrickkidd/pkqtbridge","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickkidd%2Fpkqtbridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickkidd%2Fpkqtbridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickkidd%2Fpkqtbridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickkidd%2Fpkqtbridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patrickkidd","download_url":"https://codeload.github.com/patrickkidd/pkqtbridge/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246390857,"owners_count":20769476,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T21:56:15.832Z","updated_at":"2025-03-30T22:44:34.758Z","avatar_url":"https://github.com/patrickkidd.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pkqtbridge\n\nDeclarative Python property and QtQuick model system for document-oriented PyQt applications, where:\n\n- Document data is a hierarchy of class instances\n- Types have formal properties that are stored to disk\n- Properties have comprehensive get/set/reset event support\n- Properties have undo support.\n\n## Classes\n\n\n### Item\n\nThe basic type in the document model. Contains many `Property` objects. Has unique Id. Tracks event listeners for when properties change.\n\n```python\nclass Event(Item):\n\n    Item.registerProperties((\n        { 'attr': 'date', 'type': QDate },\n        { 'attr': 'unsure', 'default': True },\n        { 'attr': 'description' },\n        { 'attr': 'nodal', 'default': False },\n        { 'attr': 'notes' },\n        { 'attr': 'parentName' },\n        { 'attr': 'location' },\n        { 'attr': 'uniqueId' }, # 'birth', 'death', 'adopted', 'bonded', 'married', 'separated', 'divorced', 'now'\n        { 'attr': 'includeOnDiagram', 'default': False }\n    ))\n\n    def itemName(self):\n        if self.parent:\n            return \"\u003c%s\u003e: %s\" % (self.parent.itemName(), self.description())\n        else:\n            return str(self)\n\n    def write(self, chunk):\n        super().write(chunk)\n        chunk['dynamicProperties'] = {}\n        for prop in self.dynamicProperties:\n            chunk['dynamicProperties'][prop.attr] = prop.get()\n            \n    def read(self, chunk, byId):\n        super().read(chunk, byId)\n        if self.date() is not None and self.date().isNull():\n            self.setDate(None, notify=False)\n        for attr, value in chunk.get('dynamicProperties', {}).items():\n            prop = self.addDynamicProperty(attr)\n            if prop: # avoid duplicates\n                prop.set(value, notify=False)\n\n    @util.blocked\n    def onProperty(self, prop):\n        if prop.name() == 'description':\n            if not self._onHideNames:\n                self.updateDescription()\n        elif prop.name() == 'notes':\n            if not self._onHideNames:\n                self.updateNotes()\n        elif prop.name() == 'uniqueId':\n            self.updateDescription()\n        if not self.uniqueId() == 'now' and not self.addDummy:\n            super().onProperty(prop)\n            if self.parent:\n                self.parent.onEventProperty(prop)\n\n```\n\n### Property\n\nA stored value with get/set/reset handlers and events. Includes undo support via `commands.SetProperty`. Added to `Item` subclass using declarative form:\n\n```python\nclass Event(Item):\n\n    Item.registerProperties((\n        { 'attr': 'date', 'type': QDate },\n        { 'attr': 'unsure', 'default': True },\n        { 'attr': 'description' },\n        { 'attr': 'nodal', 'default': False },\n        { 'attr': 'notes' },\n        { 'attr': 'parentName' },\n        { 'attr': 'location' },\n        { 'attr': 'uniqueId' }, # 'birth', 'death', 'adopted', 'bonded', 'married', 'separated', 'divorced', 'now'\n        { 'attr': 'includeOnDiagram', 'default': False }\n    ))\n```\n\n### Document\n\nContains many items. Manages unique `Item.id` values. Contains `Layer` items.\n\n```python\nclass MyItem(Item):\n    Item.registerProperties((\n        { 'attr': 'something', 'type': int, default=-1 },\n    ))\n\ndocument = Document()\nitem1, item2 = MyItem(something=123, MyItem(something=456)\ndocument.addItems(item1, item2)\n\nassert item1.id != item2.id\nassert item1 == document.findById(item1.id)\nassert item2 == document.findById(item2.id)\n```\n\n### Layer\n\nA stored, cascading sub-set of `Property` values. Intended for quick-swapping out one subset for another, like a cascading style sheet.\n\n```python\nclass MyItem(Item):\n    Item.registerProperties((\n        { 'attr': 'something', 'type': int, 'default'=-1, 'layered': True },\n    ))\n    \ndocument = Document()\nitem = MyItem(something=234)\nlayer1 = Layer(name='Layer 1')\ndocument.addLayer(layer1, item)\nlayer1.setActive(True)\nitem.setSomething(456)\nassert item.something() == 456\n\nlayer1.setActive(False)\nassert item.something() == 234\n\nlayer1.setActive(True)\nassert item.something() == 456\n```\n\n### QObjectHelper\n\nDeclarative interface for mapping `Property`'s onto Qt properties. Useful for exposing an `Item` to QtQuick.\n\n\n```python\nclass ModelHelper(QObjectHelper):\n    \"\"\" Handle properties for a list of like Item's.\n    calls refreshAllProperties() when items and/or scene changed.\n    \"\"\"\n\n    QObjectHelper.registerQtProperties([\n        { 'attr': 'items', 'type': list },\n        { 'attr': 'scene', 'type': Scene, 'default': None },\n        { 'attr': 'blockNotify', 'type': bool, 'default': False },\n        { 'attr': 'blockUndo', 'type': bool, 'default': False },\n        { 'attr': 'addMode', 'type': bool, 'default': False },\n        { 'attr': 'dirty', 'type': bool, 'default': False }, # set automatically, reset manually\n        { 'attr': 'resetter', 'type': bool } # alternates when modelReset is called for bindings\n    ])\n\n\n    def initModelHelper(self, storage=False):\n        self._blockNotify = False\n        self._blockUndo = False\n        self._addMode = False\n        self._dirty = False\n        self._items = []\n        self._document = None\n        self._resetter = False\n\n    def get(self, attr):\n        \"\"\" Return the value. Default behavior is to return the same(attr).\n            If value is a bool then convert it to a check state.\n        \"\"\"\n        if attr == 'items':\n            return self._items\n        # and more....\n        else:\n            return super().get(attr)\n\n    def set(self, attr, value):\n        if attr == 'items':\n            if self._items:\n                for item in self._items:\n                    item.removePropertyListener(self)\n                self._items = []\n            if value not in (None, [None]):\n                if not isinstance(value, list):\n                    value = [value]\n                self._items = value\n                for item in self._items:\n                    item.addPropertyListener(self)\n            self.refreshProperty('items')\n            return\n\n        # and more...\n\n    def reset(self, attr):\n        if attr == 'items':\n            self.set('items', [])\n            return\n        elif attr == 'document':\n            self.set('document', [])\n            return\n        elif attr == 'dirty':\n            self.set('dirty', False)\n        if self.blockUndo:\n            id = False\n        else:\n            id = commands.nextId()\n        notify = not self.blockNotify\n        for item in self._items:\n            prop = item.prop(attr)\n            if prop:\n                item.prop(attr).reset(notify=notify, undo=id)=\n```\n\n(See [modelhelper.py](qtbridge/modelhelper.py) for full example)\n\n### ModelHelper\n\nBridge mixin for QAbstractItemModel + QObjectHelper.\nCalls refreshAllProperties() when items and/or document changed.\n\n### QmlWidgetHelper\n\nConvenience mixin to help find qml items from python. Very helpful in unit testing QQuickWidget from Python.\nDeclarative interface for `QQuickWidget`.\n\n```python\nclass QmlSomething(QQuickWidget, QmlWidgetHelper):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self._sizeHint = QSize()\n        Layout = QVBoxLayout(self)\n        Layout.setContentsMargins(0, 0, 0, 0)\n        self.initQmlWidgetHelper('qml/Something.qml')\n\nsomething = QmlSomething()\nassert something.findItem('doneButton').property('hasActiveFocus) == something.itemProp('doneButton', 'hasActiveFocus')\n```\n\n### conftest.PKQtBot\n\nBug fixes and additional features for `qtbot` pytest plugin.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickkidd%2Fpkqtbridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatrickkidd%2Fpkqtbridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickkidd%2Fpkqtbridge/lists"}