Skip to content

View

View module. Uses the Textual framework.

App

The App class is the base of the Interface. To start the application, it's initialized as the app object.

MainApp

Bases: App

Class for the main app. Gets called when the app is started.

Attributes:

Name Type Description
CSS_PATH str

Path to external design file.

SCREENS dict

Installs Screen classes.

Source code in src/ibl_explorer/view.py
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
class MainApp(App):
    """Class for the main app.
    Gets called when the app is started.

    Attributes:
        CSS_PATH (str): Path to external design file.
        SCREENS (dict): Installs Screen classes.
    """
    CSS_PATH = "global.tcss"
    SCREENS = {"load_screen": LoadScreen(), "main_screen": MainScreen(), "status_screen": StatusScreen(),
               "search_dialog": SearchDialog(), "list_dialog": ListDialog(),
               "dataset_settings_dialog": DatasetSettingsDialog(), "diagram_settings_dialog": DiagramSettingsDialog()}
    TITLE = "IBL Explorer"

    def on_mount(self):  # Displays LoadScreen() first
        """Pushes LoadScreen when class is mounted."""
        self.push_screen('load_screen')

on_mount()

Pushes LoadScreen when class is mounted.

Source code in src/ibl_explorer/view.py
567
568
569
def on_mount(self):  # Displays LoadScreen() first
    """Pushes LoadScreen when class is mounted."""
    self.push_screen('load_screen')

Screens

The Screens are subclasses of the textual.screen class and are used to show collections of widgets.

MainScreen

MainScreen

Bases: Screen

Central Screen of the application.

Source code in src/ibl_explorer/view.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
class MainScreen(Screen):
    """Central Screen of the application.


    """
    BINDINGS = [("1", "toggle_dark", "Toggle dark mode"), ("2", "app.push_screen('status_screen')", "Status"),
                ("CTRL+C", "quit", "Quit"), ]  # Bindings shown in the footer
    BORDER_TITLE = "IBL Explorer"  # Title in the Header

    _dataset_view = reactive("No Data")  # Data to be shown in dataset widget
    _uuid = ""  # Used for view_copy_uuid button
    _selected_datasets = reactive([("Position", "Name", "Description", "Source")])  # Table header
    _selected_key: str = ""  # Key of the last clicked dataset in the table

    def action_toggle_dark(self):
        """Action callable with \"toggle_dark\", sets the theme to light/dark."""
        app.dark = not app.dark

    def update_dataset_view(self, data: str) -> None:
        """Updates the data in the Dataset View widget.

        Args:
            data (str): New Data.
        """
        self._dataset_view = data
        self.query_one(Pretty).update(self._dataset_view)

    def add_dataset(self, dataset: dict) -> None:
        """Adds a new dataset to the table widget.

        Data format:
            Data format for row, wants tuple.\
            (Position, Name, Description, Source)


        Args:
            dataset (dict): Dataset from one.describe_dataset().
                Hardcoded, should be modular.
        """
        data = dataset
        try:
            row = ("", data["name"], data["description"], "OneConnector")
            table = self.query_one("#table", DataTable)
            table.add_row(*row, key=data["id"], height=None)
        except Exception as e:
            app.notify("Exception: " + str(e), severity="warning")

    def select_dataset(self, value: str):
        """Change settings of dataset in table widget, not working.

        Args:
            value (str): Position value.
        """
        table = self.query_one("#table", DataTable)
        try:
            table.update_cell(self._selected_key, "0", value)
        except:
            self.notify("Something went wrong", severity="alert")

    def remove_dataset(self):
        """Remove dataset from table widget."""
        table = self.query_one("#table", DataTable)
        try:
            table.remove_row(self._selected_key)
        except Exception as e:
            app.notify("No Dataset selected", severity="warning")

    def build_table(self, table: DataTable, datasets: list) -> DataTable:
        """Build a table from existing DataTable object.

        Args:
            table (DataTable): DataTable object.
            datasets (list): Header and Table values.

        Returns:
            DataTable: DataTable object.
        """
        table.show_row_labels = True
        table.cursor_type = "row"  # Select whole row instead of cell
        table.zebra_stripes = True
        table.add_columns(*datasets[0])
        table.add_rows(datasets[1:])
        return table

    def on_data_table_row_selected(self, message: DataTable.RowSelected) -> None:
        """Message handler, safes key of the row that was last selected in the Table widget.

        Args:
            message (DataTable.RowSelected): Message create.
        """
        self._selected_key = message.row_key.value

    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Event handler for button press.
        Matches action to the button by id.

        Args:
            event (Button.Pressed): Event created by button press.
        """
        if event.button.id == "main_search":
            app.get_screen("search_dialog").fetch_elements()  # Get screen object from app object
            app.push_screen("search_dialog", self.check_quit)
        elif event.button.id == "main_list":
            app.get_screen("list_dialog").fetch_elements()
            app.push_screen("list_dialog", self.check_quit)
        elif event.button.id == "view_set_dataset":
            self.add_dataset(self._dataset_view)
        elif event.button.id == "main_remove_dataset":
            self.remove_dataset()
        elif event.button.id == "main_dataset_settings":
            app.get_screen("dataset_settings_dialog").fetch_elements()
            app.push_screen("dataset_settings_dialog", self.check_quit)  # Pushes screen and handler for returned values
        elif event.button.id == "main_draw_diagram":
            """Try of implementation of diagram."""
            table = self.query_one("#table",
                                   DataTable)  # Fetch DataTable widget from current instance of class with id table
            x = table.rows
            log(x)
            model.draw_diagram([["a60425e9-c5ab-4827-88a3-79b4eb68f989", "trials"],
                                ["9295f7a4-dd1b-440e-8ad0-53223aebab81", "trials"]], ["Test", "a", "b"])
        elif event.button.id == "view_copy_uuid":
            self._uuid = self._dataset_view["id"]
            log(self._uuid)

    def check_quit(self, data: list) -> None:
        """Handler for screen data, not Working."""
        match data[0]:
            case "lists":
                pass
            case "search":
                pass
            case "dataset_settings":
                pass
            case "diagram_settings":
                log("check_quit")
                log(data[1])

    def compose(self) -> ComposeResult:
        """Generates ComposeResult from widgets.

        Returns:
            ComposeResult object.
        """
        yield Header(id="Header")

        boxTree = DataTree("", id="treeView", classes="box")
        boxTree.border_title = "Data Tree"
        boxTree.root.expand()
        yield boxTree

        boxDataset = Pretty(self._dataset_view, classes="box", id="viewDataset")
        boxDataset.border_title = "View Dataset"
        yield boxDataset

        with Container(classes="buttonBar"):
            yield Button(label="Add Dataset", id="view_set_dataset")
            yield Button(label="Copy UUID", id="view_copy_uuid")

        with Container(classes="box", id="diagramData") as boxDiagram:
            boxDiagram.border_title = "Diagram Data"
            with Container(classes="diagram_data_container"):
                table = DataTable(id="table")
                yield self.build_table(table, self._selected_datasets)

        with Container(classes="buttonBar"):
            yield Button("List", id="main_list")
            yield Button("Search", id="main_search")

        with Container(classes="buttonBar"):
            yield Button(label="Select Dataset", id="main_dataset_settings")
            yield Button(label="Remove", id="main_remove_dataset")
            yield Horizontal(Button(label="Diagram Settings", id="main_diagram_settings"),
                Button(label="Draw Diagram", id="main_draw_diagram"), classes="buttonRight")

        yield Footer(id="Footer")

action_toggle_dark()

Action callable with "toggle_dark", sets the theme to light/dark.

Source code in src/ibl_explorer/view.py
226
227
228
def action_toggle_dark(self):
    """Action callable with \"toggle_dark\", sets the theme to light/dark."""
    app.dark = not app.dark

add_dataset(dataset)

Adds a new dataset to the table widget.

Data format

Data format for row, wants tuple. (Position, Name, Description, Source)

Parameters:

Name Type Description Default
dataset dict

Dataset from one.describe_dataset(). Hardcoded, should be modular.

required
Source code in src/ibl_explorer/view.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def add_dataset(self, dataset: dict) -> None:
    """Adds a new dataset to the table widget.

    Data format:
        Data format for row, wants tuple.\
        (Position, Name, Description, Source)


    Args:
        dataset (dict): Dataset from one.describe_dataset().
            Hardcoded, should be modular.
    """
    data = dataset
    try:
        row = ("", data["name"], data["description"], "OneConnector")
        table = self.query_one("#table", DataTable)
        table.add_row(*row, key=data["id"], height=None)
    except Exception as e:
        app.notify("Exception: " + str(e), severity="warning")

build_table(table, datasets)

Build a table from existing DataTable object.

Parameters:

Name Type Description Default
table DataTable

DataTable object.

required
datasets list

Header and Table values.

required

Returns:

Name Type Description
DataTable DataTable

DataTable object.

Source code in src/ibl_explorer/view.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def build_table(self, table: DataTable, datasets: list) -> DataTable:
    """Build a table from existing DataTable object.

    Args:
        table (DataTable): DataTable object.
        datasets (list): Header and Table values.

    Returns:
        DataTable: DataTable object.
    """
    table.show_row_labels = True
    table.cursor_type = "row"  # Select whole row instead of cell
    table.zebra_stripes = True
    table.add_columns(*datasets[0])
    table.add_rows(datasets[1:])
    return table

check_quit(data)

Handler for screen data, not Working.

Source code in src/ibl_explorer/view.py
336
337
338
339
340
341
342
343
344
345
346
347
def check_quit(self, data: list) -> None:
    """Handler for screen data, not Working."""
    match data[0]:
        case "lists":
            pass
        case "search":
            pass
        case "dataset_settings":
            pass
        case "diagram_settings":
            log("check_quit")
            log(data[1])

compose()

Generates ComposeResult from widgets.

Returns:

Type Description
ComposeResult

ComposeResult object.

Source code in src/ibl_explorer/view.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def compose(self) -> ComposeResult:
    """Generates ComposeResult from widgets.

    Returns:
        ComposeResult object.
    """
    yield Header(id="Header")

    boxTree = DataTree("", id="treeView", classes="box")
    boxTree.border_title = "Data Tree"
    boxTree.root.expand()
    yield boxTree

    boxDataset = Pretty(self._dataset_view, classes="box", id="viewDataset")
    boxDataset.border_title = "View Dataset"
    yield boxDataset

    with Container(classes="buttonBar"):
        yield Button(label="Add Dataset", id="view_set_dataset")
        yield Button(label="Copy UUID", id="view_copy_uuid")

    with Container(classes="box", id="diagramData") as boxDiagram:
        boxDiagram.border_title = "Diagram Data"
        with Container(classes="diagram_data_container"):
            table = DataTable(id="table")
            yield self.build_table(table, self._selected_datasets)

    with Container(classes="buttonBar"):
        yield Button("List", id="main_list")
        yield Button("Search", id="main_search")

    with Container(classes="buttonBar"):
        yield Button(label="Select Dataset", id="main_dataset_settings")
        yield Button(label="Remove", id="main_remove_dataset")
        yield Horizontal(Button(label="Diagram Settings", id="main_diagram_settings"),
            Button(label="Draw Diagram", id="main_draw_diagram"), classes="buttonRight")

    yield Footer(id="Footer")

on_button_pressed(event)

Event handler for button press. Matches action to the button by id.

Parameters:

Name Type Description Default
event Pressed

Event created by button press.

required
Source code in src/ibl_explorer/view.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def on_button_pressed(self, event: Button.Pressed) -> None:
    """Event handler for button press.
    Matches action to the button by id.

    Args:
        event (Button.Pressed): Event created by button press.
    """
    if event.button.id == "main_search":
        app.get_screen("search_dialog").fetch_elements()  # Get screen object from app object
        app.push_screen("search_dialog", self.check_quit)
    elif event.button.id == "main_list":
        app.get_screen("list_dialog").fetch_elements()
        app.push_screen("list_dialog", self.check_quit)
    elif event.button.id == "view_set_dataset":
        self.add_dataset(self._dataset_view)
    elif event.button.id == "main_remove_dataset":
        self.remove_dataset()
    elif event.button.id == "main_dataset_settings":
        app.get_screen("dataset_settings_dialog").fetch_elements()
        app.push_screen("dataset_settings_dialog", self.check_quit)  # Pushes screen and handler for returned values
    elif event.button.id == "main_draw_diagram":
        """Try of implementation of diagram."""
        table = self.query_one("#table",
                               DataTable)  # Fetch DataTable widget from current instance of class with id table
        x = table.rows
        log(x)
        model.draw_diagram([["a60425e9-c5ab-4827-88a3-79b4eb68f989", "trials"],
                            ["9295f7a4-dd1b-440e-8ad0-53223aebab81", "trials"]], ["Test", "a", "b"])
    elif event.button.id == "view_copy_uuid":
        self._uuid = self._dataset_view["id"]
        log(self._uuid)

on_data_table_row_selected(message)

Message handler, safes key of the row that was last selected in the Table widget.

Parameters:

Name Type Description Default
message RowSelected

Message create.

required
Source code in src/ibl_explorer/view.py
296
297
298
299
300
301
302
def on_data_table_row_selected(self, message: DataTable.RowSelected) -> None:
    """Message handler, safes key of the row that was last selected in the Table widget.

    Args:
        message (DataTable.RowSelected): Message create.
    """
    self._selected_key = message.row_key.value

remove_dataset()

Remove dataset from table widget.

Source code in src/ibl_explorer/view.py
271
272
273
274
275
276
277
def remove_dataset(self):
    """Remove dataset from table widget."""
    table = self.query_one("#table", DataTable)
    try:
        table.remove_row(self._selected_key)
    except Exception as e:
        app.notify("No Dataset selected", severity="warning")

select_dataset(value)

Change settings of dataset in table widget, not working.

Parameters:

Name Type Description Default
value str

Position value.

required
Source code in src/ibl_explorer/view.py
259
260
261
262
263
264
265
266
267
268
269
def select_dataset(self, value: str):
    """Change settings of dataset in table widget, not working.

    Args:
        value (str): Position value.
    """
    table = self.query_one("#table", DataTable)
    try:
        table.update_cell(self._selected_key, "0", value)
    except:
        self.notify("Something went wrong", severity="alert")

update_dataset_view(data)

Updates the data in the Dataset View widget.

Parameters:

Name Type Description Default
data str

New Data.

required
Source code in src/ibl_explorer/view.py
230
231
232
233
234
235
236
237
def update_dataset_view(self, data: str) -> None:
    """Updates the data in the Dataset View widget.

    Args:
        data (str): New Data.
    """
    self._dataset_view = data
    self.query_one(Pretty).update(self._dataset_view)

LoadScreen

LoadScreen This Screen isn't visible in the current Version of the application. It probably needs to be handled by a worker/ in a separate thread.

Bases: Screen

Loading Screen, pushes MainScreen when model is finished initializing.

Source code in src/ibl_explorer/view.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class LoadScreen(Screen):
    """Loading Screen, pushes MainScreen when model is finished initializing."""
    _mouseLoad = r"""
                  ..----.._    _
                .' .--.    "-.(O)_
    '-.__.-'"'=:|  ,  _)_ \__ . c\'-..
                 ''------'---''---'-"
    """

    def compose(self) -> ComposeResult:
        yield Static(self._mouseLoad, id="loadMouse")  # Will be replaced by an animation (maybe).

        with Container(classes="load"):
            yield Static("Loading", classes="loadText", id="loadTitle")  # The bit that says Loading XY
            yield Static("", classes="loadText")  # More info

        yield Static("Please Wait", id="loadWait")  # Always shows "Please Wait" in a box

    def on_mount(self) -> None:
        global model
        model = ModelLogic()
        app.push_screen("main_screen")

StatusScreen

StatusScreen Supposed to display the current status of the sources, currently hardcoded for presentation.

Bases: Screen

Status Screen, shows the status of the sources and some information on the program.

Source code in src/ibl_explorer/view.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class StatusScreen(Screen):
    """Status Screen, shows the status of the sources and some information on the program."""
    BINDINGS = [("1", "toggle_dark", "Toggle dark mode"), ("2", "app.pop_screen", "Return to Main"),
        ("CTRL+C", "quit", "Quit"), ]

    text1 = r"""
    Version: Alpha / Unuseable

    GUI for easy visualization of datasets, mainly built to run with the IBL ONE API.
    """
    text2 = r"""
    Project for the practical course "Open Neural Data" by Dr. Robert Schmidt,
    summer semester 2024 at the Ruhr-University Bochum.


    Copyright (C) [2024] [Fabian Stegmaier]

    This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
    """

    _observerState = [[{"name": "ONE API - Alyx", "state": "Initialized", "messages": [],
        "description": "Connector for the ONE API, using the Alyx fileformat", }],
        [{"name": "Line Diagram", "state": "", "messages": [], "description": "A line diagram.", }]]

    def action_toggle_dark(self):
        app.dark = not app.dark

    def _display_State(self, sel: int) -> list:
        """Method for displaying state of model classes.
        Returns list of collapsible widgets.

        Args:
            sel (int): 0 is data and 1 is diagrams.
        """
        return_widgets = []
        for source in self._observerState[sel]:  # Iterate over sources
            message_text = f"""**{source['description']}**"""
            message_text += """\


"""
            for message in source["messages"]:  # Iterate over messages
                message_text += f"""{str(message)}"""
                message_text += """\


"""
            print(message_text)
            return_widgets.append(
                Collapsible(Markdown(message_text), collapsed=True, title=(source["name"] + ": " + source["state"])))
        return return_widgets

    def compose(self) -> ComposeResult:
        yield Header(id="Header")

        yield Label("IBL Explorer - Dev")  # Banner with title

        with Container(classes="textBox"):  # Two descriptive texts
            yield Static(self.text1, classes="text")
            yield Static(self.text2, classes="text")

        with TabbedContent(classes="tabbedBox"):  # Tabs with the Sources and their status
            with TabPane("Data Sources"):
                with Horizontal():
                    for widget in self._display_State(0):  # Iterate over list of sources in form of collapsible
                        yield widget
            with TabPane("Diagrams"):
                with Horizontal():
                    for widget in self._display_State(1):  # Iterate over list of sources in form of collapsible
                        yield widget

        yield Footer(id="Footer")

The Dialogs are generated by calling get_dialog with the dialog name and are built dynamically.

NOTE: For further modularization, there should only be one Dialog class which then generates everything dynamically.

SearchDialog

SearchDialog

Bases: ModalScreen

Modal Screen that displays the search dialog.

Note

Documentation here is exemplary for the rest of the Dialogs.

Source code in src/ibl_explorer/view.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
class SearchDialog(ModalScreen):
    """Modal Screen that displays the search dialog.

    Note:
        Documentation here is exemplary for the rest of the Dialogs.
    """
    search_elements = reactive(
        [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])

    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Event handler for button pressed.
            Closes or dismisses screen. Dismiss can return data to handler.

        Args:
            event (Button.Pressed): Button event.
        """
        if event.button.id == "dialog_close":
            app.pop_screen()
        elif event.button.id == "dialog_search":
            self.dismiss("OK")

    def fetch_elements(self) -> None:
        """Fetches list with configuration and saves it to search_elements

        Example:
            search_elements = [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])
        """
        log("Fetching Search Elements")
        self.search_elements = model.get_dialog("search")

    def compose(self) -> None:
        """Generates ComposeResult from widgets.
        Iterate over search_elements and creates widgets from it.
        """
        with Container(classes="dialog"):
            with Container(classes="box") as box:
                box.border_title = "Search"
                for widget in self.search_elements:  # Iterate over search_elements and create widgets from it
                    with Horizontal(classes="dialog_row"):
                        yield Static(widget[0], classes="dialog_name")
                        match widget[1]:
                            case "input":
                                yield Input(placeholder=widget[2], classes="dialog_input")
                            case "select":
                                yield Select.from_values(widget[2], classes="dialog_input")
                            case "bool":
                                yield Switch(value=widget[2], classes="dialog_bool")
                    if len(widget[3]) > 0:
                        yield Static(widget[3], classes="dialog_description")
            with Horizontal(classes="buttonBar"):
                yield Button(label="Close", id="dialog_close")
                yield Horizontal(Button(label="Show", id="dialog_search"), classes="buttonRight")

compose()

Generates ComposeResult from widgets. Iterate over search_elements and creates widgets from it.

Source code in src/ibl_explorer/view.py
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
def compose(self) -> None:
    """Generates ComposeResult from widgets.
    Iterate over search_elements and creates widgets from it.
    """
    with Container(classes="dialog"):
        with Container(classes="box") as box:
            box.border_title = "Search"
            for widget in self.search_elements:  # Iterate over search_elements and create widgets from it
                with Horizontal(classes="dialog_row"):
                    yield Static(widget[0], classes="dialog_name")
                    match widget[1]:
                        case "input":
                            yield Input(placeholder=widget[2], classes="dialog_input")
                        case "select":
                            yield Select.from_values(widget[2], classes="dialog_input")
                        case "bool":
                            yield Switch(value=widget[2], classes="dialog_bool")
                if len(widget[3]) > 0:
                    yield Static(widget[3], classes="dialog_description")
        with Horizontal(classes="buttonBar"):
            yield Button(label="Close", id="dialog_close")
            yield Horizontal(Button(label="Show", id="dialog_search"), classes="buttonRight")

fetch_elements()

Fetches list with configuration and saves it to search_elements

Example

search_elements = [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])

Source code in src/ibl_explorer/view.py
412
413
414
415
416
417
418
419
def fetch_elements(self) -> None:
    """Fetches list with configuration and saves it to search_elements

    Example:
        search_elements = [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])
    """
    log("Fetching Search Elements")
    self.search_elements = model.get_dialog("search")

on_button_pressed(event)

Event handler for button pressed. Closes or dismisses screen. Dismiss can return data to handler.

Parameters:

Name Type Description Default
event Pressed

Button event.

required
Source code in src/ibl_explorer/view.py
400
401
402
403
404
405
406
407
408
409
410
def on_button_pressed(self, event: Button.Pressed) -> None:
    """Event handler for button pressed.
        Closes or dismisses screen. Dismiss can return data to handler.

    Args:
        event (Button.Pressed): Button event.
    """
    if event.button.id == "dialog_close":
        app.pop_screen()
    elif event.button.id == "dialog_search":
        self.dismiss("OK")

ListDialog

ListDialog

Bases: ModalScreen

Modal Screen, that displays the list dialog.

Source code in src/ibl_explorer/view.py
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
class ListDialog(ModalScreen):
    """Modal Screen, that displays the list dialog."""
    search_elements = reactive(
        [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "dialog_close":
            app.pop_screen()
        elif event.button.id == "dialog_search":
            self.dismiss("OK")

    def fetch_elements(self) -> None:
        log("Fetching Search Elements")
        self.search_elements = model.get_dialog("lists")

    def compose(self) -> None:
        with Container(classes="dialog"):
            with Container(classes="box") as box:
                box.border_title = "List"
                for widget in self.search_elements:
                    with Horizontal(classes="dialog_row"):
                        yield Static(widget[0], classes="dialog_name")
                        match widget[1]:
                            case "input":
                                yield Input(placeholder=widget[2], classes="dialog_input")
                            case "select":
                                yield Select.from_values(widget[2], classes="dialog_input")
                            case "bool":
                                yield Switch(value=widget[2], classes="dialog_bool")
                    if len(widget[3]) > 0:
                        yield Static(widget[3], classes="dialog_description")
            with Horizontal(classes="buttonBar"):
                yield Button(label="Close", id="dialog_close")
                yield Horizontal(Button(label="Show", id="dialog_search"), classes="buttonRight")

DatasetSettingsDialog

DatasetSttingsDialog

Bases: ModalScreen

Modal Screen that displays the Dataset Settings.

Source code in src/ibl_explorer/view.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
class DatasetSettingsDialog(ModalScreen):
    """Modal Screen that displays the Dataset Settings."""
    search_elements = reactive(
        [["Input", "input", "Test", ""], ["Select", "select", [1, 2, 3], ""], ["Bool", "bool", False, ""]])
    _selection = [""]

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "dialog_close":
            app.pop_screen()
        elif event.button.id == "dialog_search":
            self.dismiss(["dataset_settings", self._selection])

    def on_select_changed(self, event: Select.Changed) -> None:
        self._selection[0] = event.value

    def fetch_elements(self) -> None:
        self.search_elements = model.get_dialog("dataset_settings")

    def compose(self) -> None:
        with Container(classes="dialog"):
            with Container(classes="box") as box:
                box.border_title = "List"
                for widget in self.search_elements:
                    with Horizontal(classes="dialog_row"):
                        yield Static(widget[0], classes="dialog_name")
                        match widget[1]:
                            case "input":
                                yield Input(placeholder=widget[2], classes="dialog_input")
                            case "select":
                                yield Select.from_values(widget[2], classes="dialog_input")
                            case "bool":
                                yield Switch(value=widget[2], classes="dialog_bool")
                    if len(widget[3]) > 0:
                        yield Static(widget[3], classes="dialog_description")
            with Horizontal(classes="buttonBar"):
                yield Button(label="Close", id="dialog_close")
                yield Horizontal(Button(label="Apply", id="dialog_search"), classes="buttonRight")

DiagramSettingsDialog

Bases: ModalScreen

Modal Screen that displays the Diagram Settings.

Source code in src/ibl_explorer/view.py
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
class DiagramSettingsDialog(ModalScreen):
    """Modal Screen that displays the Diagram Settings."""
    search_elements = reactive([["Input", "input", "Test"], ["Select", "select", [1, 2, 3]], ["Bool", "bool", False]])

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "dialog_close":
            self.dismiss(None)
            print("Closing dialog")
        elif event.button.id == "dialog_search":
            self.dismiss("OK")

    def compose(self) -> None:
        with Container(classes="dialog"):
            with Container(classes="box") as box:
                box.border_title = "Diagram Settings"
                for widget in self.search_elements:
                    with Horizontal(classes="dialog_row"):
                        yield Static(widget[0], classes="dialog_name")
                        match widget[1]:
                            case "input":
                                yield Input(placeholder=widget[2], classes="dialog_input")
                            case "select":
                                yield Select.from_values(widget[2], classes="dialog_input")
                            case "bool":
                                yield Switch(value=widget[2], classes="dialog_bool")
                    if len(widget[3]) > 0:
                        yield Static(widget[3], classes="dialog_description")
            with Horizontal(classes="buttonBar"):
                yield Button(label="Close", id="dialog_close")
                yield Horizontal(Button(label="Show", id="dialog_search"), classes="buttonRight")

Widgets

DataTree

Bases: Tree

Tree widget which parses json and generates a Tree from it

Source code in src/ibl_explorer/view.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class DataTree(Tree):
    """Tree widget which parses json and generates a Tree from it"""
    _tree_data = reactive({"Test": {"Test1": ""}})

    def action_fetch_tree_data(self) -> None:
        """Fetch data in dict/json format and save it to _tree_data."""
        self._tree_data = model.display_list("list_datasets")

    def tree_data(self, data_t: dict) -> None:
        """Takes data and builds a tree from it.

        Args:
            data_t (dict): Data in dict/json format.
        """

        def add_node(name: str, node: TreeNode, data: dict, path: str = "") -> None:
            """Recursively adds a node to the tree.

            Args:
                name (str): Name of the node.
                node (TreeNode): Parent node.
                data (object): Data associated with the node.
                path (str, optional): Path to the node, later accessible via node.data. and used for View Data.
            """
            _path: str = path
            if len(_path) > 0:
                _path += ("/" + name)
            else:
                _path = name

            if isinstance(data, dict):
                node.set_label(name)
                node.data = _path
                for key, value in data.items():
                    new_node = node.add("")
                    add_node(key, new_node, value, _path)
            elif isinstance(data, list):
                node.set_label(name)
                node.data = _path
                for index, value in enumerate(data):
                    new_node = node.add("")
                    add_node(str(index), new_node, value, _path)
            else:
                node.allow_expand = False
                if name:
                    label = name
                else:
                    label = data
                node.set_label(label)
                node.data = _path

        for tree_name in data_t.keys():
            self.root.label = tree_name
            for k, i in data_t[tree_name].items():  # Iterates over items and adds nodes with them
                t_node = self.root.add("JSON")
                add_node(k, t_node, i)

    def action_tree_list(self, selected_list: str, eid: str = "", collection: str = "") -> None:
        """Not used. Preparation for ListDialog."""
        tree = self.query_one(Tree)
        tree.clear()
        self.action_fetch_tree_data()
        self.tree_data(self._tree_data, tree)
        tree.root.expand()

    def action_tree_search(self, selected_list, eid, collection) -> dict:
        """Not used. Preparation for SearchDialog."""
        tree = self.query_one(Tree)
        self.tree_data(model.display_list(selected_list, eid, collection), tree)
        tree.root.expand()

    def on_tree_node_selected(self, event) -> None:
        """Event handler for TreeNodeSelected event.
        Retrieves the file-path and updates view data with it.

        Args:
            event (TreeNode.TreeNodeSelected): Selected Tree Node.
        """
        log(event.node.data)
        log(model.get_description(event.node.data))
        app.get_screen('main_screen').update_dataset_view(model.get_description(event.node.data))

    def on_mount(self) -> None:
        self.action_fetch_tree_data()
        self.tree_data(self._tree_data)

action_fetch_tree_data()

Fetch data in dict/json format and save it to _tree_data.

Source code in src/ibl_explorer/view.py
129
130
131
def action_fetch_tree_data(self) -> None:
    """Fetch data in dict/json format and save it to _tree_data."""
    self._tree_data = model.display_list("list_datasets")

action_tree_list(selected_list, eid='', collection='')

Not used. Preparation for ListDialog.

Source code in src/ibl_explorer/view.py
182
183
184
185
186
187
188
def action_tree_list(self, selected_list: str, eid: str = "", collection: str = "") -> None:
    """Not used. Preparation for ListDialog."""
    tree = self.query_one(Tree)
    tree.clear()
    self.action_fetch_tree_data()
    self.tree_data(self._tree_data, tree)
    tree.root.expand()

Not used. Preparation for SearchDialog.

Source code in src/ibl_explorer/view.py
190
191
192
193
194
def action_tree_search(self, selected_list, eid, collection) -> dict:
    """Not used. Preparation for SearchDialog."""
    tree = self.query_one(Tree)
    self.tree_data(model.display_list(selected_list, eid, collection), tree)
    tree.root.expand()

on_tree_node_selected(event)

Event handler for TreeNodeSelected event. Retrieves the file-path and updates view data with it.

Parameters:

Name Type Description Default
event TreeNodeSelected

Selected Tree Node.

required
Source code in src/ibl_explorer/view.py
196
197
198
199
200
201
202
203
204
205
def on_tree_node_selected(self, event) -> None:
    """Event handler for TreeNodeSelected event.
    Retrieves the file-path and updates view data with it.

    Args:
        event (TreeNode.TreeNodeSelected): Selected Tree Node.
    """
    log(event.node.data)
    log(model.get_description(event.node.data))
    app.get_screen('main_screen').update_dataset_view(model.get_description(event.node.data))

tree_data(data_t)

Takes data and builds a tree from it.

Parameters:

Name Type Description Default
data_t dict

Data in dict/json format.

required
Source code in src/ibl_explorer/view.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def tree_data(self, data_t: dict) -> None:
    """Takes data and builds a tree from it.

    Args:
        data_t (dict): Data in dict/json format.
    """

    def add_node(name: str, node: TreeNode, data: dict, path: str = "") -> None:
        """Recursively adds a node to the tree.

        Args:
            name (str): Name of the node.
            node (TreeNode): Parent node.
            data (object): Data associated with the node.
            path (str, optional): Path to the node, later accessible via node.data. and used for View Data.
        """
        _path: str = path
        if len(_path) > 0:
            _path += ("/" + name)
        else:
            _path = name

        if isinstance(data, dict):
            node.set_label(name)
            node.data = _path
            for key, value in data.items():
                new_node = node.add("")
                add_node(key, new_node, value, _path)
        elif isinstance(data, list):
            node.set_label(name)
            node.data = _path
            for index, value in enumerate(data):
                new_node = node.add("")
                add_node(str(index), new_node, value, _path)
        else:
            node.allow_expand = False
            if name:
                label = name
            else:
                label = data
            node.set_label(label)
            node.data = _path

    for tree_name in data_t.keys():
        self.root.label = tree_name
        for k, i in data_t[tree_name].items():  # Iterates over items and adds nodes with them
            t_node = self.root.add("JSON")
            add_node(k, t_node, i)