{"id":25284721,"url":"https://github.com/ircama/construct-gallery","last_synced_at":"2025-04-06T15:13:56.754Z","repository":{"id":79246244,"uuid":"604974803","full_name":"Ircama/construct-gallery","owner":"Ircama","description":"wxPython Widgets extending functionalities of construct-editor","archived":false,"fork":false,"pushed_at":"2024-01-09T04:48:57.000Z","size":467,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-12T20:52:18.341Z","etag":null,"topics":["ble","bleak","bluetooth","construct","construct-editor","plugin","wxpython"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ircama.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-02-22T07:09:59.000Z","updated_at":"2025-01-24T07:27:30.000Z","dependencies_parsed_at":"2023-12-29T11:30:53.037Z","dependency_job_id":"674d617c-66b8-42de-80b6-395b462e988d","html_url":"https://github.com/Ircama/construct-gallery","commit_stats":{"total_commits":15,"total_committers":1,"mean_commits":15.0,"dds":0.0,"last_synced_commit":"c98f4636766bdda8fc30196422ee58e98f15c59a"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ircama%2Fconstruct-gallery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ircama%2Fconstruct-gallery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ircama%2Fconstruct-gallery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ircama%2Fconstruct-gallery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ircama","download_url":"https://codeload.github.com/Ircama/construct-gallery/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247500470,"owners_count":20948880,"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":["ble","bleak","bluetooth","construct","construct-editor","plugin","wxpython"],"created_at":"2025-02-12T20:52:21.379Z","updated_at":"2025-04-06T15:13:56.736Z","avatar_url":"https://github.com/Ircama.png","language":"Python","readme":"# construct-gallery\n\n[![PyPI](https://img.shields.io/pypi/v/construct-gallery.svg?maxAge=2592000)](https://pypi.org/project/construct-gallery)\n[![Python Versions](https://img.shields.io/pypi/pyversions/construct-gallery.svg)](https://pypi.org/project/construct-gallery/)\n[![PyPI download month](https://img.shields.io/pypi/dm/construct-gallery.svg)](https://pypi.python.org/pypi/construct-gallery/)\n[![GitHub license](https://img.shields.io/badge/license-CC--BY--NC--SA--4.0-blue)](https://raw.githubusercontent.com/ircama/construct-gallery/master/LICENSE)\n\n__Development and testing tool for construct, including widgets extending the functionalities of *construct-editor*__\n\n*construct-gallery* is a GUI to interactively develop and test [construct](https://construct.readthedocs.io/en/latest/) data structures, dynamically parsing and building sample data which can be catalogued in an editable gallery and also stored in Pickle archives for later test re-execution. It also offers widgets that can be integrated in other Python programs.\n\nThe *construct* format shall be developed in a Python program through any IDE or editor. While editing and after loading the program to *construct-gallery*, it can be checked and also dynamically reloaded if modified meanwhile.\n\nWhen also [bleak](https://bleak.readthedocs.io/en/latest/) is installed, the GUI includes a [BLE](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) Advertisement monitoring tool. Advertisements are logged in their reception sequence, automatically labeled with related MAC address.\n\n*construct-gallery* is based on [wxPython](https://www.wxpython.org/) and [construct-editor](https://github.com/timrid/construct-editor): specifically, it relies on the excellent editing widgets provided by the *construct-editor* module and offers a superset of features compared with its standard [GUI](https://github.com/timrid/construct-editor/blob/main/construct_editor/main.py). *construct* is a powerful, declarative, symmetrical parser and builder for binary data.\n\n## Main functionalities\n\n- Ready-to-use, cross-platform GUI\n- Python API widgets for all functionalities\n- The tool allows defining a gallery of *construct* formats, optionally associated to samples, which can be dynamically added, edited or predefined.\n- Hex bytes can be collected into a gallery of samples, then renamed and reordered.\n- Samples are listed in the left panel, shown as hex bytes in the central panel and then parsed to browsable *construct* structures in the right panel.\n- Data can be saved to files in [pickle format](https://docs.python.org/3/library/pickle.html). Archives can be subsequently reloaded and appended to the current samples. They can also be inspected with `python3 -mpickle archive-file-name.pickle`.\n- The Python error management is wrapped into a GUI panel.\n- A Python shell button allows opening an inspector shell, which also provides a special *Help* with related submenu (or by pressing F9).\n- All panels allow a context menu (invoked with the right click of the mouse) with a number of special functions.\n  - The left menu panel allows renaming labels and changing attribute labels. Also, by double-clicking an unused area of the left panel, new frames can be added and then labelled; subsequently, specific attributes can be associated. Samples can be repositioned, or deleted.\n  - The hex editor (central panel) allows any kind of editing and copy/paste. Sequences of bytes can be pasted [in a number of different formats](https://github.com/timrid/construct-editor/pull/17#issuecomment-1367582581). Also, a special checkbox enables pasting Python expressions. Debugging tools are also provided (invoked with the right click of the mouse after selecting a sequence of bytes), to insert or convert bytes into a wide set of numeric forms as well as strings; these debug panels can be used to quickly check the most appropriate conversion method for a sequence of bytes.\n\nThe included BLE scanner predefines the *construct-gallery* configuration in order to manage MAC addresses, listen for BLE advertisements and log them sequentially; the monitor engine can be started and stopped through buttons and the *BleakScannerConstruct* API also allows an auto-start option.\n\n- A filter button can be used to enter a specific MAC address to restrict logging, a portion of it or a sequence of addresses, as well as BLE local names.\n- When starting the BLE reception, a debug window is opened in background, with the possibility to control the debug level and clear the produced data.\n- Active and passive scanning modes are both managed by the *BleakScannerConstruct* API through the *bleak_scanner_kwargs* parameter. If passive mode is enabled (e.g., `bleak_scanner_kwargs={'scanning_mode': 'passive'}`), the *BleakScannerConstruct* widget automatically detects whether [bluez](https://github.com/bluez/bluez) is used by the *bleak* backend (e.g., Linux) and configures it appropriately; in this case, the component uses [hcitool](https://github.com/bluez/bluez/wiki/hcitool) and requires *sudo* access to this command. Also, the `--experimental` flag in BlueZ [is needed](https://bluez-cheat-sheet.readthedocs.io/en/latest/) to enable the Advertising Monitor.\n\n## Example of basic usage\n\nSave the following example program to a file named for instance *constr.py*:\n\n```python\nfrom construct import *\n\nconstruct_format = Struct(\n    \"temperature\" / Int16sl,\n    \"counter\" / Int8ul\n)\n```\n\nLoad it with *construct-gallery*:\n\n```bash\npython3 -m construct_gallery constr.py\n```\n\nPaste the following bytes to the central hex panel of *construct-gallery*:\n\n```\n14 00 0c\n```\n\nYou can use all the available tools of *construct-gallery* to edit the digits and test the results. Notice the various plugins that enrich the functionalities of *construct-editor*.\n\nYou can keep *construct-gallery* running while editing *constr.py* with your preferred editor or IDE; paste for instance the following code, replacing the previous one, then save:\n\n```python\nfrom construct import *\nimport construct_editor.core.custom as custom\n\ncustom.add_custom_adapter(ExprAdapter, \"Int16ul_x100\", custom.AdapterObjEditorType.String)\nInt16ul_x100 = ExprAdapter(Int16ul, obj_ / 100, lambda obj, ctx: int(float(obj) * 100))\n\nconstruct_format = GreedyRange(\n    Struct(\n        \"temperature\" / Int16ul_x100,\n        \"counter\" / Int8ul,\n    )\n)\n```\n\nNote: when using [tunnels](https://construct.readthedocs.io/en/latest/api/adapters.html#construct.ExprAdapter), you also need to [declare the adapter](https://github.com/timrid/construct-editor/blob/b4c63dcea1a057cbcc7106b2d58c8bb4d8503e3b/construct_editor/core/custom.py#L53) for correct rendering in *construct-editor*.\n\nPress \"Reload construct module\" in *construct-gallery*: you will see the updated structure.\n\nPast the following bytes to the central hex panel of *construct-gallery*:\n\n```\n02 08 0c 70 08 0d de 08 0e 4c 09 0f ba 09 10\n```\n\nThe appropriately parsed data will be available in the right panel of *construct-gallery*, where values can be edited, producing the dynamical update of the byte sequence in the central panel.\n\nSelect some bytes in the central panel, press the right key of the mouse: a context menu is shown, with a set of editing and debugging tools. \n\n## Advanced usage\n\nOther than the previously described basic mode, *construct-gallery*  offers advanced configurations that allow you to predefine a gallery of samples with different formats and options. The data structure defined with *construct-gallery* can be used to provide optional attributes to the *construct* format.\n\n*construct-gallery* is able to read two kinds of formats inside the Python program:\n\n- the basic one, consisting of a single gallery element (the one previously exemplified):\n\n  ```\n  construct_format = \u003cConstruct structure\u003e\n  ```\n\n- and a gallery of multiple elements:\n\n  ```\n  gallery_descriptor = \u003cdictionary or ordered dictionary of GalleryItem elements\u003e\n  ```\n\nNotice that *construct_format* and *gallery_descriptor* are default names of variables which can be changed through the `-f`/`-F` options, or via API (ref. *gallery_descriptor_var* and *construct_format_var* parameters of `ConstructGallery()`).\n\nWhen using the *construct_format* mode, in order to provide basic structures for testing, *construct-gallery* automatically creates *Bytes*, *Characters* and *UTF-8 String* galleries (if you run the previous example, you can see them).\n\nThe *gallery_descriptor* mode allows you to define custom galleries. To classify the custom gallery elements, *gallery_descriptor* adopts an enriched `GalleryItem()` data model [initially defined in *construct-editor*](https://github.com/timrid/construct-editor/blob/b4c63dcea1a057cbcc7106b2d58c8bb4d8503e3b/construct_editor/gallery/__init__.py#L7-L10), which can be imported with `from construct_gallery import GalleryItem`.\n\nThe *gallery_descriptor* mode can be:\n\n- a dictionary of `\"item name\": GalleryItem()` (*key: value* pairs). Example:\n\n  ```python\n  from construct_gallery import GalleryItem\n\n  gallery_descriptor = {\n      \"Item 1\": GalleryItem(\n      ...\n      ),\n      \"Item 2\": GalleryItem(\n      ...\n      )\n  }\n  ```\n\n- an ordered dictionary of `\"item name\": GalleryItem()` (*key: value* pairs). Example:\n\n  ```python\n  from construct_gallery import GalleryItem\n  from collections import OrderedDict\n\n  gallery_descriptor = (\n      OrderedDict(\n          [\n              (\n                  \"Item 1\",\n                  GalleryItem(\n                      ...\n                  ),\n              ),\n              (\n                  \"Item 2\",\n                  GalleryItem(\n                      ...\n                  ),\n              ),\n          ]\n      )\n  )\n  ```\n\nAs mentioned, *construct_gallery* enhances the *GalleryItem* data model, introducing additional attributes:\n\n```python\nimport dataclasses\nimport typing as t\n\n@dataclasses.dataclass\nclass GalleryItem:\n    construct: \"cs.Construct[t.Any, t.Any]\"\n    clear_log: bool = False\n    contextkw: t.Dict[str, t.Any] = dataclasses.field(default_factory=dict)\n    ordered_sample_bytes: t.OrderedDict[str, bytes] = dataclasses.field(default_factory=dict)\n    ordered_sample_bin_ref: t.OrderedDict[str, dict] = dataclasses.field(default_factory=dict)\n    ref_key_descriptor: t.Dict[str, dict] = dataclasses.field(default_factory=dict)\n```\n\nThe `construct` attribute is mandatory and must be referred to a `construct` definition.\n\n*GalleryItem* can collect `ordered_sample_bytes`, which is a dictionary or ordered dictionary of bytes; this is the simplest mode and consists of a collection of `\"label\": bytes` key-value pairs.\n\nThe `clear_log` attribute is optional: when set to `True`, all related samples are deleted each time a `construct` is changed in the gallery through the GUI; otherwise, new samples are added at the bottom of the sample list.\n\nAll other attributes available with *gallery_descriptor* (*contextkw*, *ordered_sample_bin_ref*, *ref_key_descriptor*) are described later.\n\nExample of *GalleryItem* using the basic dictionary format of the `ordered_sample_bytes` samples:\n\n```python\nimport construct as cs\nfrom construct_gallery import GalleryItem\n\nGalleryItem(\n    construct=cs.Struct(\n        \"Int16ub\" / cs.Int16ub,\n        \"Int8ub\" / cs.Int8ub\n    ),\n    clear_log=True,\n    ordered_sample_bytes={  # dictionary format\n        \"A number\": bytes.fromhex(\"04 d2 7b\"),\n        \"All 1\": bytes.fromhex(\"00 01 01\"),\n        \"All 0\": bytes(2 + 1),\n    },\n)\n```\n\nUsing the previously described *constr.py* example to test the *gallery_descriptor* format, paste the following code, then save:\n\n```python\nfrom collections import OrderedDict\nfrom construct import *\nimport construct_editor.core.custom as custom\nfrom construct_gallery import GalleryItem\n\ncustom.add_custom_adapter(ExprAdapter, \"Int16ul_x100\", custom.AdapterObjEditorType.String)\nInt16ul_x100 = ExprAdapter(Int16ul, obj_ / 100, lambda obj, ctx: int(float(obj) * 100))\n\ngallery_descriptor = {\n    \"Basic example\": GalleryItem(\n        construct=Struct(\n            \"temperature\" / Int16sl,\n            \"counter\" / Int8ul\n        ),\n        clear_log=True,\n        ordered_sample_bytes={\n            'Numbers 20 and 12': bytes.fromhex(\"14 00 0c\"),\n            'Numbers 21 and 13': bytes.fromhex(\"15 00 0d\"),\n        }\n    ),\n    \"More complex example\": GalleryItem(\n        construct=GreedyRange(\n            Struct(\n                \"temperature\" / Int16ul_x100,\n                \"counter\" / Int8ul,\n            )\n        ),\n        clear_log=True,\n        ordered_sample_bytes=OrderedDict(  # OrderedDict format\n            [\n                ('Ten numbers', bytes.fromhex(\n                    \"02 08 0c 70 08 0d de 08 0e 4c 09 0f ba 09 10\")),\n                ('All 1', bytes.fromhex(\n                    \"64 00 01 64 00 01 64 00 01 64 00 00 64 00 01\")),\n                ('All 0', bytes(8 + 4 + 2 + 1)),\n            ]\n        )\n    ),\n}\n```\n\n### Using keyword arguments\n\nAs `construct` accepts keyword arguments passed through the [`_params`](https://construct.readthedocs.io/en/latest/basics.html#hidden-context-entries) entry in order to provide metadata or additional information to the structure, also *construct-editor* and *construct-gallery* support them.\n\n```python\n# How construct uses keyword arguments:\nfrom construct import *\n\nconstr = Struct(\n    \"my_counter\" / Int8ul,\n    'my_string' / Computed(this._params.my_string),\n    'my_digit' / Computed(this._params.my_digit),\n)\n\nprint(constr.parse(b'\\x01', my_string=\"Hello\", my_digit=2))\n```\n*construct-gallery* can use the same `contextkw` global form defined by *construct-editor*, or up to three variables (reference, key and description) inside each *GalleryItem*.\n\n`contextkw` is a dictionary of \"*key: value*\" pairs of items to be passed to `construct` as arguments.\n\nIn the previous sample, the following code produces the same result:\n\n```python\ncontextkw = {\n    'my_string': \"Hello\",\n    'my_digit': 2,\n}\n\nprint(constr.parse(b'\\x01', **contextkw))  # unpack dictionary to keyword arguments\n```\n\nIn the following example, *my_string* is directly used inside the *construct* format, while *decimals* is passed to *ExprAdapter*; both access these variables via the *_params* entry:\n\n```python\nfrom construct import *\nimport construct_editor.core.custom as custom\nfrom construct_gallery import GalleryItem\n\ncustom.add_custom_adapter(ExprAdapter, \"ExprAdapter\", custom.AdapterObjEditorType.String)\nInt16sl_Dec = ExprAdapter(\n    Int16ul,\n    lambda obj, ctx: obj / int(ctx._params.decimals),\n    lambda obj, ctx: int(float(obj) * int(ctx._params.decimals))\n)\n\ngallery_descriptor = {\n    \"Basic example\": GalleryItem(\n        construct=Struct(\n            \"temperature\" / Int16sl_Dec,\n            \"counter\" / Int8ul,\n            'my_string' / Computed(this._params.my_string),\n        ),\n        clear_log=True,\n        ordered_sample_bytes={\n            'Numbers 20.5 and 12': bytes.fromhex(\"14 50 0c\"),\n            'Numbers 21.4 and 13': bytes.fromhex(\"98 53 0d\"),\n        },\n        contextkw={\n            'my_string': \"one\",\n            'decimals': 1000,\n        }\n    )\n}\n```\n\nIn the previous form, which uses the *ordered_sample_bytes* attribute, the keywords defined in *contextkw* are globally available for all samples of the *GalleryItem*.\n\nIn addition, *construct_gallery* allows using the *ordered_sample_bin_ref* attribute, which associates a reference (see \"reference\" in the following example, with customizable key label) to each byte sequence (\"binary\", fixed key):\n\n```python\nfrom collections import OrderedDict\n\nordered_sample_bin_ref=OrderedDict(\n    [\n        (\n            \"One\",\n            {\n                \"binary\": b'\\x00\\x01',\n                \"reference\": \"AA\",\n            },\n        ),\n        (\n            \"Two\",\n            {\n                \"binary\": b'\\x00\\x02',\n                \"reference\": \"BB\",\n            },\n        ),\n    ]\n)\n```\n\nEach reference can be mapped to other two values through the `ref_key_descriptor` attribute:\n\n```python\nref_key_descriptor={\n    \"AA\": {\n        \"key\": \"aaaaaaaa\",\n        \"description\": \"first\",\n    },\n    \"BB\": {\n        \"key\": \"bbbbbbbb\",\n        \"description\": \"second\",\n    }\n}\n```\n\nAll these three values (reference, key, description) are available through the *_params* entry and their labels must be preliminarily set using `-R`, `-K` and `-D` options, or using the *reference_label*, *key_label* and *description_label* parameters of the *ConstructGallery()* API. All are strings. *reference* and *key* shall be only valued with hex values. *description* allows free format.\n\nSpecifically, `-R` (*reference_label*) is always mandatory. `-K` and `-D`(*key_label* and *description_label*) are required when key and description values are also needed. As an example, the first might be mapped to the MAC address, the second to an encryption hex string and the third to an optional description text.\n\nThe following is a typical structure of the *gallery_descriptor* variable when using *ordered_sample_bin_ref* and *ref_key_descriptor* in *GalleryItem()*:\n\n```python\nfrom collections import OrderedDict\nfrom construct_gallery import GalleryItem\n\ngallery_descriptor = {\n    \"item\": GalleryItem(\n        construct=...,\n        clear_log=True,\n        ordered_sample_bin_ref=OrderedDict(\n            ...\n        ),\n        ref_key_descriptor={\n            ...\n        }\n    ),\n    ...: ...,\n}\n```\n\n`ordered_sample_bin_ref` is an ordered dictionary of ordered dictionaries; the form is a collection of `\"label\": dict_item` key-value elements, where *dict_item* is in turn a collection of `\"binary\": bytes, \"reference\": string` elements. The key *\"binary\"* is fixed. The label *reference* can be customized through the *reference_label* parameter; for instance `reference_label=\"MAC address\"`.\n\n`ref_key_descriptor` is a dictionary of *\"reference\": { key, description }*.\n\nIn `ref_key_descriptor`, the actual name of \"key\" is determined by the `key_label` parameter, by substituting spaces with underscores and uppercase letters with lowercase. Examples: if `key_label=\"Bindkey\"`, then the key will be `\"bindkey\"`. If `reference_label=\"MAC address\"`, then the reference will be `\"mac_address\"`. Same for the *description* label (ref. `description_label`).\n\nWhen using the API, the *gallery_descriptor* parameter is used to load the related structure; in addition, *ordered_sample_bin_ref* and *ref_key_descriptor* are available to separately load *ordered_sample_bin_ref* and *ref_key_descriptor* data, if *gallery_descriptor* does not include them.\n\nThe following diagram describes the relationship among the various attributes:\n\n```mermaid\nerDiagram\n    gallery_descriptor {\n        %% \"construct\" selector, upper list box\n        string gallery_item_label PK \"label\"\n        class GalleryItem\n    }\n    \"GalleryItem\" {\n        Construct construct\n        boolean clear_log \"(optional)\"\n        dict ordered_sample_bytes \"(optional)\"\n        dict ordered_sample_bin_ref \"(optional)\"\n        dict ref_key_descriptor \"(optional)\"\n    }\n    ordered_sample_bytes {\n        string ordered_sample_bytes_label PK \"label\"\n        bytes bytes\n    }\n    ordered_sample_bin_ref {\n        string ordered_sample_bin_ref_label PK \"label\"\n        bytes binary \"fixed, cannot be renamed\"\n        hex reference \"can be renamed\"\n    }\n    ref_key_descriptor {\n        hex reference PK \"(optional, can be renamed)\"\n        hex key \"(optional, can be renamed)\"\n        string description \"(optional, can be renamed)\"\n    }\n    gallery_descriptor ||--o{ GalleryItem : contains\n    GalleryItem ||--o{ ordered_sample_bytes : contains\n    GalleryItem ||--o{ ordered_sample_bin_ref : contains\n    ordered_sample_bin_ref ||--|| ref_key_descriptor : \"reference is associated to\"\n```\n\nUsing the previously described *constr.py* example to test the advanced usage of the *gallery_descriptor* format, paste the following code, then save:\n\n```python\nfrom collections import OrderedDict\nfrom construct import *\nimport construct_editor.core.custom as custom\nfrom construct_gallery import GalleryItem\n\ncustom.add_custom_adapter(ExprAdapter, \"ExprAdapter\", custom.AdapterObjEditorType.String)\nInt16sl_Dec = ExprAdapter(\n    Int16ul,\n    lambda obj, ctx: obj / int(ctx._params.decimals),\n    lambda obj, ctx: int(float(obj) * int(ctx._params.decimals))\n)\n\ngallery_descriptor = {\n    \"ordered_sample_bytes using dictionaries\": GalleryItem(\n        construct=Struct(\n            \"temperature\" / Int16sl_Dec,\n            \"counter\" / Int8ul,\n            'my_string' / Computed(this._params.my_string),\n            'decimals' / Computed(this._params.decimals),\n        ),\n        clear_log=True,\n        ordered_sample_bytes={\n            'Numbers 20.5 and 12': bytes.fromhex(\"14 50 0c\"),\n            'Numbers 21.4 and 13': bytes.fromhex(\"98 53 0d\"),\n        },\n        contextkw={\n            'my_string': \"one\",\n            'decimals': 1000,\n        }\n    ),\n    \"ordered_sample_bytes using ordered dictionaries\": GalleryItem(\n        construct=GreedyRange(\n            Struct(\n                \"temperature\" / Int16sl_Dec,\n                \"counter\" / Int8ul,\n            )\n        ),\n        clear_log=True,\n        ordered_sample_bytes=OrderedDict(  # OrderedDict format\n            [\n                ('Ten numbers', bytes.fromhex(\n                    \"cd 00 0c d8 00 0d e3 00 0e ee 00 0f f9 00 10\")),\n                ('All 1', bytes.fromhex(\n                    \"64 00 01 64 00 01 64 00 01 64 00 01 64 00 01\")),\n                ('All 0', bytes(8 + 4 + 2 + 1)),\n            ]\n        ),\n        contextkw={\n            'decimals': 10,\n        }\n    ),\n    \"ordered_sample_bin_ref\": GalleryItem(\n        construct=GreedyRange(\n            Struct(\n                \"temperature\" / Int16sl_Dec,\n                \"counter\" / Int8ul,\n                'key' / Computed(this._params.key),\n                'reference' / Computed(this._root._.reference),\n                'decimals' / Computed(this._params.decimals)\n            )\n        ),\n        clear_log=True,\n        ordered_sample_bin_ref=OrderedDict(\n            [\n                (\n                    \"Ten numbers\",\n                    {\n                        \"binary\": bytes.fromhex(\"34 0a 11 a3 0a 12 12 0b 13 81 0b 14 f0 0b 15\"),\n                        \"reference\": \"AA:BB:CC:DD:EE:FF\",\n                    },\n                ),\n                (\n                    \"All 2\",\n                    {\n                        \"binary\": bytes.fromhex(\"c8 00 02 c8 00 02 c8 00 02 c8 00 02 c8 00 02\"),\n                        \"reference\": \"AA:BB:CC:DD:EE:FF\",\n                    },\n                ),\n                (\n                    \"All 3\",\n                    {\n                        \"binary\": bytes.fromhex(\"1e 00 03 1e 00 03 1e 00 03 1e 00 03 1e 00 03\"),\n                        \"reference\": \"00:11:22:33:44:55\",\n                    },\n                ),\n            ]\n        ),\n        ref_key_descriptor={\n            \"AA:BB:CC:DD:EE:FF\": {\n                \"key\": \"aaaaaaaa\",\n                \"decimals\": \"100\",\n            },\n            \"00:11:22:33:44:55\": {\n                \"key\": \"bbbbbbbb\",\n                \"decimals\": \"10\",\n            }\n        }\n    ),\n}\n```\n\nRun it with the following command:\n\n```bash\npython3 -m construct_gallery -R reference -K key -D decimals constr.py\n```\n\nVerify all buttons and context menus.\n\n## Command-line parameters\n\n```\nusage: construct_gallery [-h] [-R--reference_label REFERENCE_LABEL] [-K KEY_LABEL] [-D DESCRIPTION_LABEL] [-M] [-m] [-g]\n                         [-F GALLERY_DESCRIPTOR_VAR] [-f CONSTRUCT_FORMAT_VAR] [-b] [-c]\n                         [CONSTRUCT_MODULE]\n\nRun as python3 -m construct_gallery ...\n\npositional arguments:\n  CONSTRUCT_MODULE      construct Python module pathname.\n\noptions:\n  -h, --help            show this help message and exit\n  -R--reference_label REFERENCE_LABEL\n                        \"reference_label\" string.\n  -K KEY_LABEL, --key_label KEY_LABEL\n                        \"key_label\" string\n  -D DESCRIPTION_LABEL, --description_label DESCRIPTION_LABEL\n                        \"description_label\" string.\n  -M, --not_detect_svc_data\n                        Only used with -b/--bleak option. Do not detect service data with -b option and detect manufacturer data. Default\n                        is to detect service data and not to detect manufacturer data.\n  -m, --detect_manuf_data\n                        Only used with -b/--bleak option. Detect both manufacturer and service data with -b option. Default is not to\n                        detect manufacturer data and only detect service data.\n  -g, --gallery         ConstructGallery demo (default)\n  -F GALLERY_DESCRIPTOR_VAR, --gallery_descriptor GALLERY_DESCRIPTOR_VAR\n                        Custom \"gallery_descriptor\" variable name.\n  -f CONSTRUCT_FORMAT_VAR, --construct_format CONSTRUCT_FORMAT_VAR\n                        Custom \"construct_format\" variable name.\n  -b, --bleak           BleakScannerConstruct test app.\n  -c, --config          ConfigEditorPanel demo.\n\nconstruct_gallery utility\n```\n\nParameters `-b` with related `-m` and `-M` are only available when *bleak* is installed.\n\n### Error exit codes\n\n2: invalid command line parameter\n\n## Modules and widgets\n\nThe following Python modules are included:\n\n- `construct_gallery.py`, providing the `ConstructGallery()` class.\n\n  This module implements a GUI editor to parse and build an editable and ordered list of binary data via a gallery of predefined [construct](https://construct.readthedocs.io/en/latest/) data structures. It can be directly used in GUI programs, or can be further extended with `bleak_scanner_construct.py`.\n\n- `config_editor.py`, providing the `ConfigEditorPanel()` class (widget).\n\n  This widget implements an editing GUI composed by a form including multiple byte structures, each one related to its own *construct* data model.\n  \n  The structure of this form is described by the \"editing_structure\" parameter.\n\n- `bleak_scanner_construct.py`, providing the `BleakScannerConstruct()` class.\n\n  The component implements a [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) (BLE) GUI client to log, browse, test and edit [BLE advertisements](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy#Advertising_and_discovery).\n  \n  This module extends `construct_gallery.py`, offering a skeleton of BLE Advertiser scanner.\n  \n  [bleak](https://bleak.readthedocs.io/en/latest/) is needed (`pip3 install bleak`)\n\n*construct-gallery* also includes a number of *construct-editor* plugins, which are used by `ConstructGallery()` and `BleakScannerConstruct()`, but they can be separately reused on projects based on *construct-editor*.\n\n- plugins offering additional options to the context menu of the *construct-editor* HexEditorGrid (invoked with the right click of the mouse):\n  - `allow_python_expr_plugin.py`, providing a toggle named \"Allow pasting Python expression\" to the context menu, which enables pasting Python expressions from the clipboard\n  - `decimal_convert_plugin.py`, adding \"Convert to decimal\" to the context menu, decoding selected bytes to various possibilities of numeric formats\n  - `string_convert_plugin.py`, adding \"Convert to UTF-8 string\" to the context menu, decoding selected bytes to UTF-8 strings\n  - `edit_plugin.py`, enabling \"Edit UTF-8 text\" and \"Edit bytes\" options on the context-menu of the central hex editor panel.\n- `wx_logging_plugin.py`, providing a debug GUI panel in background.\n- `pyshell_plugin.py`, activating a Python shell button that allows opening a PyShell frame (PyShell is a GUI-based python shell), which also includes a special *Help* with related submenu (that can be invoked also via F9).\n\n## Setup\n\n### Installation\n\nCheck that the [Python](https://www.python.org/) version is 3.8 or higher (`python3 -V`), then install *construct-gallery* with the following command:\n\n```shell\npython3 -m pip install construct-gallery\n```\n\nIf the `bleak_scanner_construct.py` BLE module is also needed:\n\n```shell\npython3 -m pip install bleak\n```\n\nWith Raspberry Pi, *bleak* will install *dbus-fast*, which needs to build the python *wheel* (related compilation takes some time).\n\nPrerequisite component: [construct-editor](https://github.com/timrid/construct-editor). *construct-editor* is automatically installed with the package, while *bleak* requires manual installation.\n\nAdditional prerequisites for Raspberry Pi:\n\n```\nsudo apt-get install -y libgtk-3-dev\npython3 -m pip install attrdict\n```\n\nWith Python 3.11 replace *attrdict* with *attrdict3*:\n\n```\npython3 -m pip uninstall attrdict\npython3 -m pip install attrdict3\n```\n\nThe C compiler is needed too.\n\nAlternatively to the above-mentioned installation method, the following steps allow installing the latest version from GitHub.\n\n- Optional preliminary configuration (if not already done):\n\n  ```shell\n  # Install Python anf Git\n  sudo apt-get update\n  sudo apt-get -y upgrade\n  sudo add-apt-repository universe # this is only needed if \"sudo apt install python3-pip\" fails\n  sudo apt-get update\n  sudo apt install -y python3-pip\n  python3 -m pip install --upgrade pip\n  sudo apt install -y git\n  ```\n\n- Run this command to install *construct-gallery* from GitHub:\n\n```shell\n  python3 -m pip install git+https://github.com/Ircama/construct-gallery\n```\n\nTo uninstall:\n\n```shell\npython3 -m pip uninstall -y construct-gallery\n```\n\n## API Documentation\n\n### ConstructGallery\n\n```python\nimport wx\nfrom construct_gallery import ConstructGallery\n\nframe = wx.Frame()\n...\n\ncg = ConstructGallery(\n    frame,                           # Parent frame\n    load_menu_label=\"Gallery Data\",  # Label of the clear samples button\n    clear_label=\"Gallery\",           # Label of the clear gallery button\n\n    reference_label=None,    # Needed reference label when using ordered_sample_bin_ref in GalleryItem\n    key_label=None,          # Key label when using ordered_sample_bin_ref\n    description_label=None,  # Description label when using ordered_sample_bin_ref\n\n    added_data_label=\"\",          # Label of an optional trailer to each added record\n    load_pickle_file=None,        # Pickle file to be loaded at startup\n\n    gallery_descriptor=None,      # gallery_descriptor parameter (see below)\n    ordered_sample_bin_ref=None,  # ordered_sample_bin_ref variable, when non-included in gallery_descriptor\n    ref_key_descriptor=None,      # ref_key_descriptor variable, when non-included in gallery_descriptor\n\n    default_gallery_selection=0,  # Number of the selected element in the construct gallery\n\n    gallery_descriptor_var=None,  # Name of the searched gallery_descriptor variable in the imported program (default is \"gallery_descriptor\")\n    construct_format_var=None,    # Name of the searched construct_format variable in the imported program (default is \"construct_format\")\n\n    col_name_width=None,          # Width of the first column (\"name\")\n    col_type_width=None,          # Width of the second column (\"type\")\n    col_value_width=None,         # Width of the third column (\"value\"),\n\n    run_shell_plugin=True,        # Activate the shell plugin by default\n    run_hex_editor_plugins=True   # Activate the hex editor plugins by default\n)\n...\n```\n\nThe *gallery_descriptor* parameter can be:\n\n- a module (including *construct_format* or *gallery_descriptor*), which is imported at runtime; the button \"Reload construct module\" appears in this case, to enable the dynamic reloading of the module;\n- a variable (either a simple *construct* form or a *gallery_descriptor* form).\n\nOther than adding *ordered_sample_bin_ref* and *ref_key_descriptor* to each *GalleryItem* of *gallery_descriptor*, they can be separately and globally set with the *ordered_sample_bin_ref* and *ref_key_descriptor parameters* of ConstructGallery():\n\n- `ordered_sample_bin_ref` is an ordered dictionary (`OrderedDict`) of samples that are valid for all construct gallery elements, so independent of specific elements in the *gallery_descriptor*. Notice that, if a reference is used, *reference_label*, *key_label* and *description_label* must be set in *ConstructGallery*.\n- `ref_key_descriptor` is a dictionary of key and descriptor for each reference. This is valid for all construct gallery elements.\n\nAnother possibility to load the configuration (or to replace a default configuration) is through a Pickle file created by the \"Save to file\" button of *construct_gallery*; use *load_pickle_file* for this.\n\nComplete program:\n\n```python\nimport wx\nimport construct as cs\nfrom construct_gallery import ConstructGallery, GalleryItem\nfrom collections import OrderedDict\n\napp = wx.App(False)\nframe = wx.Frame(\n    None, title=\"ConstructGalleryFrame\", size=(1000, 600))\nframe.CreateStatusBar()\n\ngallery_descriptor = {\n    \"Signed little endian int (16, 8)\": GalleryItem(\n        construct=cs.Struct(\n            \"Int16sl\" / cs.Int16sl,\n            \"Int8sl\" / cs.Int8sl\n        ),\n        clear_log=True,\n        ordered_sample_bytes=OrderedDict(  # OrderedDict format\n            [\n                ('A number', bytes.fromhex(\"01 02 03\")),\n                ('All 1', bytes.fromhex(\"01 00 01\")),\n                ('All 0', bytes(2 + 1)),\n            ]\n        )\n    ),\n    \"Unsigned big endian int (16, 8)\": GalleryItem(\n        construct=cs.Struct(\n            \"Int16ub\" / cs.Int16ub,\n            \"Int8ub\" / cs.Int8ub\n        ),\n        clear_log=True,\n        ordered_sample_bytes={  # dictionary format\n            \"A number\": bytes.fromhex(\"04 d2 7b\"),\n            \"All 1\": bytes.fromhex(\"00 01 01\"),\n            \"All 0\": bytes(2 + 1),\n        },\n    )\n}\n\nConstructGallery(frame, gallery_descriptor=gallery_descriptor)\nframe.Show(True)\napp.MainLoop()\n```\n\n### construct-gallery package\n\n```mermaid\nclassDiagram\n    BleakScannerConstruct --|\u003e ConstructGallery\n    ConstructGallery \"1\" *-- \"1\" WxConstructHexEditor\n\n    class BleakScannerConstruct{\n        filter_hint_mac\n        filter_hint_name\n        reference_label=\"MAC address\"\n        load_menu_label=\"Log Data and Configuration\"\n        clear_label=\"Log Data\"\n        added_data_label=\"Logging data\"\n        logging_plugin=True\n        auto_ble_start=False\n        bleak_scanner_kwargs\n\n        bleak_advertising(device, advertisement_data)\n        on_application_close()\n        add_packet_frame(data, reference, label, append_label, discard_duplicates, date_separator, duplicate_separator)\n    }\n\n    class ConstructGallery{\n        parent\n\n        load_menu_label=\"Gallery Data\",\n        clear_label=\"Gallery\",\n        reference_label=None,\n        key_label=None,\n        description_label=None,\n        added_data_label=\"\",\n        load_pickle_file=None,\n        gallery_descriptor=None,\n        ordered_sample_bin_ref=None,\n        ref_key_descriptor=None,\n        default_gallery_selection=0,\n        gallery_descriptor_var=None,\n        construct_format_var=None,\n        col_name_width=None,\n        col_type_width=None,\n        col_value_width=None,\n        run_shell_plugin=True,\n        run_hex_editor_plugins=True\n\n        add_data(data, reference, label, append_label, discard_duplicates, date_separator, duplicate_separator)\n    }\n```\n\n### construct-editor package\n\n```mermaid\nclassDiagram\n    WxConstructHexEditor --|\u003e wxPanel\n\n    class WxConstructHexEditor{\n        parent\n        construct\n        contextkw\n        binary\n    }\n\nnote   \"...\n        ...\n        ...\"\n```\n\n### ConfigEditorPanel\n\nThis widget implements an editing GUI composed by a form including multiple structures, each one related to its own *construct* data model. All is described by the \"editing_structure\" dictionary.\n\nThe object returned by \"ConfigEditorPanel\" can be used to read the edited structure via the included \"editor_panel\" array, like with the following:\n\n```python\nfrom construct_gallery import ConfigEditorPanel\n...\nconfig_editor_panel = ConfigEditorPanel(...)\n...\napp.MainLoop()\n\nfor char in config_editor_panel.editor_panel:\n    print(\"Edited value:\", config_editor_panel.editor_panel[char].binary)\n```\n\nTest it with `python3 -m construct_gallery -c`.\n\nThe example below also describes the data model of the \"editing_structure\" structure.\n\n```python\nimport wx\nfrom construct_gallery import ConfigEditorPanel\nfrom construct_editor.core.model import IntegerFormat\nimport construct as cs\n\nediting_structure = {\n    0: {  # This must be numeric; multiple numbers can be included. The word \"Characteristic 0\" is written to the left side, as the first line.\n        \"name\": \"A string\",  # The bold name \"A string\" is written to the left side, as the second line.\n        \"binary\": b\"My string\",  # the \"bytes\" window is hidden by the fault and can be expanded through the GUI\n        \"construct\": cs.Struct(\n            \"My string\" / cs.GreedyString(\"utf8\"),\n        ),\n        \"read_only\": True,  # (boolean) False or True. If True, \"(read only)\" is written to the left side, as the third line.\n        \"size\": 130,  # Number of vertical pixels to show (if smaller than the minimum requested size, a scroll bar appears).\n        \"IntegerFormat\": IntegerFormat.Hex,  # Default format for integers: IntegerFormat.Hex or IntegerFormat.Dec\n    },\n}\n\napp = wx.App(False)\nframe = wx.Frame(\n    None, title=\"ConfigEditorPanelFrame demo\", size=(1000, 300))\nframe.CreateStatusBar()\nmain_panel = ConfigEditorPanel(\n    frame,\n    editing_structure=editing_structure,\n    name_size=180,\n    type_size=160,\n    value_size=200\n)\nframe.Show(True)\napp.MainLoop()\nfor char in main_panel.editor_panel:\n    editing_structure[char][\n        \"new_binary\"] = main_panel.editor_panel[char].binary\nfor i in editing_structure:\n    print(i, editing_structure[i])\n```\n\n```mermaid\nclassDiagram\n    WxConstructHexEditor \"*\" --* \"1\" ConfigEditorPanel\n    scrolledScrolledPanel \"1\" \u003c|-- \"1\" ConfigEditorPanel\n\n    class ConfigEditorPanel{\n        editor_panel\n    }\n```\n\n### BleakScannerConstruct\n\nTest it with `python3 -m construct_gallery -b`.\n\n```python\nfrom construct_gallery import BleakScannerConstruct\n\nbc = BleakScannerConstruct(\n    frame,\n    filter_hint_mac=None,\n    filter_hint_name=None,\n    reference_label=\"MAC address\",\n    load_menu_label=\"Log Data and Configuration\",\n    clear_label=\"Log Data\",\n    added_data_label=\"Logging data\",\n    logging_plugin=True,\n        #  (same arguments as ConstructGallery)\n)\n```\n\nOptionally, `bleak_scanner_kwargs` allows defining a dictionary of arguments passed to *BleakScanner* in the form: `BleakScanner(detection_callback, **bleak_scanner_kwargs)`.\n\nThe intended way to use this class is to create a subclass that overrides the *bleak_advertising()* method (which does nothing in the parent class). The overridden method shall detect valid advertisements and call `self.add_packet_frame()` to log data to the gallery samples of *construct-gallery*. *logging* can be used to log debugging information to *wx_logging_plugin*.\n\nExample:\n\n```python\nfrom construct_gallery import BleakScannerConstruct\nimport logging\n\n\nclass MyScanner(BleakScannerConstruct):\n    def bleak_advertising(self, device, advertisement_data):\n        format_label = ...get_label_from...advertisement_data\n        adv_data = ...get_adv_data_from...advertisement_data\n        error = ...get_error_from...advertisement_data\n        if error:\n            logging.warning(\n                \"mac: %s. %s advertisement: %s. RSSI: %s\",\n                device.address, format_label, advertisement_data, device.rssi)\n            return\n        if adv_data:\n            self.add_packet_frame(\n                data=adv_data,\n                reference=device.address,\n                append_label=format_label\n            )\n        logging.info(\n            \"mac: %s. %s advertisement: %s. RSSI: %s\",\n            device.address, format_label, advertisement_data, device.rssi)\n```\n\n*BleakScannerConstruct* does all the logic to perform asynchronous processing of BLE advertising via the *BleakScanner* method of BLE, including the management of a thread which can be started and stopped through GUI buttons. *add_packet_frame()* calls *add_data()* of *construct-gallery*.\n\n```python\nbleak_advertising(self, device, advertisement_data)\n```\n\n- device: [BLEDevice](https://bleak.readthedocs.io/en/latest/api/index.html?highlight=BLEDevice#bleak.backends.device.BLEDevice) and related [source code](https://bleak.readthedocs.io/en/latest/_modules/bleak/backends/device.html?highlight=BLEDevice#)\n- advertisement_data: [AdvertisementData](https://bleak.readthedocs.io/en/latest/backends/index.html?highlight=AdvertisementData#bleak.backends.scanner.AdvertisementData) and related [source code](https://bleak.readthedocs.io/en/latest/_modules/bleak/backends/scanner.html)\n\nComplete BLE logger program.\n\n```python\nimport wx\nimport logging\nimport construct as cs\nfrom construct_gallery import GalleryItem, BleakScannerConstruct\n\nadv_format = cs.Struct(\n    \"type\" / cs.Int8ub,\n    \"length\" / cs.Int8ub,\n    \"bytes\" / cs.Array(cs.this.length, cs.Byte),\n)\n\nclass BleConstruct(BleakScannerConstruct):\n    def bleak_advertising(self, device, advertisement_data):\n        try:\n            adv_data = advertisement_data.manufacturer_data[0x004C]\n            ibeacon = adv_format.parse(adv_data)\n            self.add_packet_frame(\n                data=adv_data,\n                reference=device.address\n            )\n        except Exception:\n            logging.warning(\n                \"mac: %s. advertisement: %s. RSSI: %s\",\n                device.address, advertisement_data, device.rssi)\n\napp = wx.App(False)\nframe = wx.Frame(\n    None, title=\"BleakScannerConstructFrame\", size=(1000, 600)\n)\nframe.CreateStatusBar()\nBleConstruct(\n    frame,\n    gallery_descriptor = {\"advertisements\": GalleryItem(construct=adv_format)}\n)\nframe.Show(True)\napp.MainLoop()\n```\n\n## Plugins\n\n- plugins offering additional options for the context menu of the *construct-editor* HexEditorGrid (invoked with the right click of the mouse):\n  - `allow_python_expr_plugin.py`\n  - `decimal_convert_plugin.py`\n  - `string_convert_plugin.py`\n  - `edit_plugin.py`\n\n- PyShell plugin `pyshell_plugin.py`, adding a button to activate a PyShell frame (PyShell is a GUI-based python shell).\n\n- `wx_logging_plugin.py`, providing a debug GUI panel in background.\n- `pyshell_plugin.py`, activating a Python shell button that allows opening an inspector shell, which also includes a special *Help* with related submenu (that can be invoked also via F9). \n\nThe following example shows how to add the first three plugins to *HexEditorGrid*. It is based on the [Getting started (as Widgets)](https://github.com/timrid/construct-editor#getting-started-as-widgets) of the *construct-editor* module:\n\n```python\nimport wx\nimport construct as cs\nfrom construct_editor.wx_widgets import WxConstructHexEditor\nimport construct_editor.wx_widgets.wx_hex_editor\n\nfrom construct_gallery import decimal_convert_plugin\nfrom construct_gallery import string_convert_plugin\nfrom construct_gallery import allow_python_expr_plugin\nfrom construct_gallery import edit_plugin\n\nclass HexEditorGrid(  # add plugins to HexEditorGrid\n    string_convert_plugin.HexEditorGrid,\n    decimal_convert_plugin.HexEditorGrid,\n    allow_python_expr_plugin.HexEditorGrid,\n    edit_plugin.HexEditorGrid,\n    construct_editor.wx_widgets.wx_hex_editor.HexEditorGrid\n):\n    def build_context_menu(self):\n        menus = super().build_context_menu()\n        menus.insert(-5, None)  # add a horizontal line before the two plugins\n        return menus\n\n\nconstr = cs.Struct(\n    \"a\" / cs.Int16sb,\n    \"b\" / cs.Int16sb,\n)\nb = bytes([0x12, 0x34, 0x56, 0x78])\n\n# monkey-patch HexEditorGrid\nconstruct_editor.wx_widgets.wx_hex_editor.HexEditorGrid = HexEditorGrid\n\napp = wx.App(False)\nframe = wx.Frame(None, title=\"Construct Hex Editor\", size=(1000, 200))\neditor_panel = WxConstructHexEditor(frame, construct=constr, binary=b)\nframe.Show(True)\napp.MainLoop()\n```\n\nThe following example shows all plugins, including a way to close the *PyShellPlugin* when the application terminates:\n\n```python\nimport wx\nimport construct as cs\nfrom construct_editor.wx_widgets import WxConstructHexEditor\nimport construct_editor.wx_widgets.wx_hex_editor\nimport logging\n\nfrom construct_gallery import decimal_convert_plugin\nfrom construct_gallery import string_convert_plugin\nfrom construct_gallery import allow_python_expr_plugin\nfrom construct_gallery import edit_plugin\n\nfrom construct_gallery.pyshell_plugin import PyShellPlugin\nfrom construct_gallery.wx_logging_plugin import WxLogging\n\n\nclass HexEditorGrid(  # add plugins to HexEditorGrid\n    string_convert_plugin.HexEditorGrid,\n    decimal_convert_plugin.HexEditorGrid,\n    allow_python_expr_plugin.HexEditorGrid,\n    edit_plugin.HexEditorGrid,\n    construct_editor.wx_widgets.wx_hex_editor.HexEditorGrid\n):\n    def build_context_menu(self):\n        menus = super().build_context_menu()\n        menus.insert(-5, None)  # add a horizontal line before the two plugins\n        return menus\n\n\nclass ConstHexEditorPanel(wx.Panel, PyShellPlugin):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        sizer = wx.BoxSizer(wx.HORIZONTAL)  # it includes 3 vert. sizers\n\n        vsizer = wx.BoxSizer(wx.VERTICAL)  # left side sizer\n        self.py_shell(vsizer)  # Start PyShell plugin\n        sizer.Add(vsizer, 0, wx.ALL | wx.EXPAND, 2)\n\n        # monkey-patch HexEditorGrid\n        construct_editor.wx_widgets.wx_hex_editor.HexEditorGrid = HexEditorGrid\n\n        constr = cs.Struct(\n            \"a\" / cs.Int16sb,\n            \"b\" / cs.Int16sb,\n        )\n        b = bytes([0x12, 0x34, 0x56, 0x78])\n        \n        construct_hex_editor = WxConstructHexEditor(self, construct=constr, binary=b)\n        sizer.Add(construct_hex_editor, 1, wx.ALL | wx.EXPAND, 2)\n        self.SetSizer(sizer)\n        self.wx_log_window = WxLogging(self, logging.getLogger())\n        logging.warning(\"Hello World\")\n\n\n    def on_close(self, event):\n        if hasattr(self, 'pyshell') and self.pyshell:\n            self.pyshell.Destroy()\n        event.Skip()\n\n\napp = wx.App(False)\nframe = wx.Frame(None, title=\"Construct Hex Editor\", size=(1000, 200))\nmain_panel = ConstHexEditorPanel(frame)\nframe.Bind(wx.EVT_CLOSE, main_panel.on_close)\nframe.Show(True)\napp.MainLoop()\n```\n\n## Installing wxPython with Python 3.11 on Windows\n\nAt the moment of writing, the [Python wheel packaging](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#wheels) of wxPython for Python 3.11 is not yet available in [Pypi](https://pypi.org/). Follow this procedure to perform manual installation on Windows.\n\nAs a prerequisite, if not already installed, you might need to install the\n[Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145). Example:\n\n```cmd\ncurl \"https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe\" --output vc_redist.x64.exe\n```\n\nThen run `vc_redist.x64.exe` and follow the installation steps.\n\nDownload the right WHL of wxPython from the related [Windows artifacts](https://dev.azure.com/oleksis/wxPython/_build/results?buildId=132\u0026view=artifacts\u0026pathAsName=false\u0026type=publishedArtifacts):\n\nExample:\n\n```cmd\ncurl https://artprodcca1.artifacts.visualstudio.com/A09e3854d-7789-4d94-8809-7e11120c1549/40d5fac0-3bcd-4754-be87-d3ce7214bec4/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL29sZWtzaXMvcHJvamVjdElkLzQwZDVmYWMwLTNiY2QtNDc1NC1iZTg3LWQzY2U3MjE0YmVjNC9idWlsZElkLzEzMi9hcnRpZmFjdE5hbWUvd3hQeXRob24tcHkzLjExLXdpbl94NjQ1/content?format=zip --output wxPython-py3.11-win_x64.zip\npowershell -command \"Expand-Archive -LiteralPath wxPython-py3.11-win_x64.zip -DestinationPath wxPython-py3.11-win_x64\"\n```\n\nInstall wxPython WHL with the same syntax of installing a standard package:\n\n```cmd\npip install wxPython-py3.11-win_x64\\wxPython-py3.11-win_x64\\wxPython-4.2.1a1-cp311-cp311-win_amd64.whl\n```\n\n# Preview\n\nPreview of a sample usage of *construct_gallery* with all plugins:\n\n![Preview of a sample usage of construct_gallery with plugins](https://github.com/pvvx/ATC_MiThermometer/raw/master/python-interface/images/ble_browser.gif)\n\nPreview of a sample usage of *ConfigEditorPanel*:\n\n![Preview of a sample usage of ConfigEditorPanel](https://github.com/pvvx/ATC_MiThermometer/raw/master/python-interface/images/atc_mi_config.gif)\n\n\u003c!---\nINTERNAL NOTES HERE:\n- Add notes related to the -b option\n- BleakScannerConstruct API not appropriately documented\n- Document which types of adv are reported by BleakScannerConstruct and which filters when the -b option is used; mention something in the introduction too\n- Document that these tools can be used stand-alone for generic usage, but are more suitable to be integrated in specific applications\n--\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fircama%2Fconstruct-gallery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fircama%2Fconstruct-gallery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fircama%2Fconstruct-gallery/lists"}