# Ready-to-use widgets

As a complement to the `guidata.dataset` module which focus on automatic GUI generation for data sets editing and display, the `guidata.widgets` module provides a set of ready-to-use widgets for building interactive GUIs.

Note

Most of the widgets originally come from the Spyder project (Copyright © Spyder Project Contributors, MIT-licensed). They were adapted to be used outside of the Spyder IDE and to be compatible with guidata internals.

## Python console

    from guidata.configtools import get_icon
    from guidata.env import execenv
    from guidata.qthelpers import qt_app_context
    from guidata.widgets.console import Console

    def test_console():
        """Test Console widget."""
        with qt_app_context(exec_loop=True):
            widget = Console(debug=False, multithreaded=True)
            widget.resize(800, 600)
            widget.setWindowTitle("Console")
            widget.setWindowIcon(get_icon("guidata.svg"))
            widget.show()
            execenv.print("OK")

    if __name__ == "__main__":
        test_console()

## Code editor

    from guidata.configtools import get_icon
    from guidata.env import execenv
    from guidata.qthelpers import qt_app_context
    from guidata.widgets import codeeditor

    def test_codeeditor():
        """Test Code editor."""
        with qt_app_context(exec_loop=True):
            widget = codeeditor.CodeEditor(language="python")
            widget.set_text_from_file(codeeditor.__file__)
            widget.resize(800, 600)
            widget.setWindowTitle("Code editor")
            widget.setWindowIcon(get_icon("guidata.svg"))
            widget.show()
            execenv.print("OK")

    if __name__ == "__main__":
        test_codeeditor()

## Array editor

    import numpy as np

    from guidata.env import execenv
    from guidata.qthelpers import exec_dialog, qt_app_context
    from guidata.widgets.arrayeditor import ArrayEditor

    def launch_arrayeditor(data, title="", xlabels=None, ylabels=None, variable_size=False):
        """Helper routine to launch an arrayeditor and return its result"""
        dlg = ArrayEditor()
        dlg.setup_and_check(
            data, title, xlabels=xlabels, ylabels=ylabels, variable_size=variable_size
        )
        exec_dialog(dlg)
        return dlg.get_value()

    def test_arrayeditor():
        """Test array editor for all supported data types"""
        with qt_app_context():
            # Variable size version
            for title, data in (
                ("string array", np.array(["kjrekrjkejr"])),
                (
                    "masked array",
                    np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]),
                ),
                ("int array", np.array([1, 2, 3], dtype="int8")),
            ):
                launch_arrayeditor(data, "[Variable size] " + title, variable_size=True)
            for title, data in (
                ("string array", np.array(["kjrekrjkejr"])),
                ("unicode array", np.array(["ñññéáíó"])),
                (
                    "masked array",
                    np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]),
                ),
                (
                    "record array",
                    np.zeros(
                        (2, 2),
                        {
                            "names": ("red", "green", "blue"),
                            "formats": (np.float32, np.float32, np.float32),
                        },
                    ),
                ),
                (
                    "record array with titles",
                    np.array(
                        [(0, 0.0), (0, 0.0), (0, 0.0)],
                        dtype=[(("title 1", "x"), "|i1"), (("title 2", "y"), ">f4")],
                    ),
                ),
                ("bool array", np.array([True, False, True])),
                ("int array", np.array([1, 2, 3], dtype="int8")),
                ("float16 array", np.zeros((5, 5), dtype=np.float16)),
            ):
                launch_arrayeditor(data, title)
            for title, data, xlabels, ylabels in (
                ("float array", np.random.rand(5, 5), ["a", "b", "c", "d", "e"], None),
                (
                    "complex array",
                    np.round(np.random.rand(5, 5) * 10)
                    + np.round(np.random.rand(5, 5) * 10) * 1j,
                    np.linspace(-12, 12, 5),
                    np.linspace(-12, 12, 5),
                ),
            ):
                launch_arrayeditor(data, title, xlabels, ylabels)

            arr = np.zeros((3, 3, 4))
            arr[0, 0, 0] = 1
            arr[0, 0, 1] = 2
            arr[0, 0, 2] = 3
            launch_arrayeditor(arr, "3D array")

            execenv.print("OK")

    if __name__ == "__main__":
        test_arrayeditor()

## Collection editor

    import datetime

    import numpy as np

    from guidata.env import execenv
    from guidata.qthelpers import qt_app_context
    from guidata.widgets.collectionseditor import CollectionsEditor

    try:
        from PIL import Image as PILImage
    except ImportError:
        # PIL is not installed
        PILImage = None

    def get_test_data():
        """Create test data."""
        testdict = {"d": 1, "a": np.random.rand(10, 10), "b": [1, 2]}
        testdate = datetime.date(1945, 5, 8)
        test_timedelta = datetime.timedelta(days=-1, minutes=42, seconds=13)

        try:
            import pandas as pd
        except (ModuleNotFoundError, ImportError):
            test_timestamp = None
            test_pd_td = None
            test_dtindex = None
            test_series = None
            test_df = None
        else:
            test_timestamp = pd.Timestamp("1945-05-08T23:01:00.12345")
            test_pd_td = pd.Timedelta(days=2193, hours=12)
            test_dtindex = pd.date_range(start="1939-09-01T", end="1939-10-06", freq="12h")
            test_series = pd.Series({"series_name": [0, 1, 2, 3, 4, 5]})
            test_df = pd.DataFrame(
                {
                    "string_col": ["a", "b", "c", "d"],
                    "int_col": [0, 1, 2, 3],
                    "float_col": [1.1, 2.2, 3.3, 4.4],
                    "bool_col": [True, False, False, True],
                }
            )

        class Foobar:
            """ """

            def __init__(self):
                self.text = "toto"
                self.testdict = testdict
                self.testdate = testdate

        foobar = Foobar()
        test_data = {
            "object": foobar,
            "module": np,
            "str": "kjkj kj k j j kj k jkj",
            "unicode": "éù",
            "list": [1, 3, [sorted, 5, 6], "kjkj", None],
            "tuple": ([1, testdate, testdict, test_timedelta], "kjkj", None),
            "dict": testdict,
            "float": 1.2233,
            "int": 223,
            "bool": True,
            "array": np.random.rand(10, 10).astype(np.int64),
            "masked_array": np.ma.array(
                [[1, 0], [1, 0]], mask=[[True, False], [False, False]]
            ),
            "1D-array": np.linspace(-10, 10).astype(np.float16),
            "3D-array": np.random.randint(2, size=(5, 5, 5)).astype(np.bool_),
            "empty_array": np.array([]),
            "date": testdate,
            "datetime": datetime.datetime(1945, 5, 8, 23, 1, 0, int(1.5e5)),
            "timedelta": test_timedelta,
            "complex": 2 + 1j,
            "complex64": np.complex64(2 + 1j),
            "complex128": np.complex128(9j),
            "int8_scalar": np.int8(8),
            "int16_scalar": np.int16(16),
            "int32_scalar": np.int32(32),
            "int64_scalar": np.int64(64),
            "float16_scalar": np.float16(16),
            "float32_scalar": np.float32(32),
            "float64_scalar": np.float64(64),
            "bool_scalar": bool,
            "bool__scalar": np.bool_(8),
            "timestamp": test_timestamp,
            "timedelta_pd": test_pd_td,
            "datetimeindex": test_dtindex,
            "series": test_series,
            "ddataframe": test_df,
            "None": None,
            "unsupported1": np.arccos,
            # Test for Issue #3518
            "big_struct_array": np.zeros(
                1000, dtype=[("ID", "f8"), ("param1", "f8", 5000)]
            ),
        }
        if PILImage is not None:
            image = PILImage.fromarray(
                np.random.randint(256, size=(100, 100)).astype(np.uint8)
            ).convert("P")
            test_data["image"] = image
        return test_data

    def test_collectionseditor():
        """Test Collections editor."""
        with qt_app_context(exec_loop=True):
            dialog = CollectionsEditor()
            dialog.setup(get_test_data())
            dialog.show()
            execenv.print("OK")

    if __name__ == "__main__":
        test_collectionseditor()

## Dataframe editor

    import numpy as np
    import pytest

    try:
        import pandas as pd
        import pandas.testing as pdt

        from guidata.widgets.dataframeeditor import DataFrameEditor
    except ImportError:
        # pandas is not installed
        pd = pdt = DataFrameEditor = None

    from guidata.env import execenv
    from guidata.qthelpers import exec_dialog, qt_app_context

    @pytest.mark.skipif(pd is None, reason="pandas is not installed")
    def test_dataframeeditor():
        """DataFrame editor test"""

        def test_edit(data, title="", parent=None):
            """Test subroutine"""
            dlg = DataFrameEditor(parent=parent)

            if dlg.setup_and_check(data, title=title):
                exec_dialog(dlg)
                return dlg.get_value()
            else:
                import sys

                sys.exit(1)

        with qt_app_context():
            df1 = pd.DataFrame(
                [
                    [True, "bool"],
                    [1 + 1j, "complex"],
                    ["test", "string"],
                    [1.11, "float"],
                    [1, "int"],
                    [np.random.rand(3, 3), "Unkown type"],
                    ["Large value", 100],
                    ["áéí", "unicode"],
                ],
                index=["a", "b", np.nan, np.nan, np.nan, "c", "Test global max", "d"],
                columns=[np.nan, "Type"],
            )
            out = test_edit(df1)
            pdt.assert_frame_equal(df1, out)

            result = pd.Series([True, "bool"], index=[np.nan, "Type"], name="a")
            out = test_edit(df1.iloc[0])
            pdt.assert_series_equal(result, out)

            df1 = pd.DataFrame(np.random.rand(100100, 10))
            out = test_edit(df1)
            pdt.assert_frame_equal(out, df1)

            series = pd.Series(np.arange(10), name=0)
            out = test_edit(series)
            pdt.assert_series_equal(series, out)

            execenv.print("OK")

    if __name__ == "__main__":
        test_dataframeeditor()

## Import wizard

    import pytest

    from guidata.env import execenv
    from guidata.qthelpers import exec_dialog, qt_app_context
    from guidata.widgets.importwizard import ImportWizard

    @pytest.fixture()
    def text():
        """Return text to test"""
        return "17/11/1976\t1.34\n14/05/09\t3.14"

    def test_importwizard(text):
        """Test"""
        with qt_app_context():
            dialog = ImportWizard(None, text)
            if exec_dialog(dialog):
                execenv.print(dialog.get_data())
            execenv.print("OK")

    if __name__ == "__main__":
        test_importwizard("17/11/1976\t1.34\n14/05/09\t3.14")

## Icon browser

The icon browser is a utility widget for browsing and exploring icon collections in directories. It is useful for developers managing icons for their applications and libraries.

Features:

- Browse icon collections with tree view for folder navigation

- Adjustable thumbnail sizes (16-256 pixels) via toolbar

- Single-click on icons to open file location in system file explorer

- Support for PNG, SVG, ICO, JPG, GIF, and BMP formats

- Responsive grid layout that adapts to window size

- Refresh action to reload current folder after external changes

Command-line usage:

    $ # Launch with folder selection dialog
    $ giconbrowser

    $ # Launch with a specific folder
    $ giconbrowser /path/to/icons

Programmatic usage:

    from __future__ import annotations

    import os.path as osp

    from guidata.env import execenv
    from guidata.qthelpers import qt_app_context
    from guidata.widgets.iconbrowser import IconBrowserWindow

    def get_guidata_icons_path() -> str:
        """Get the path to guidata's icon directory.

        Returns:
            Path to the icons directory
        """
        import guidata

        guidata_path = osp.dirname(guidata.__file__)
        icons_path = osp.join(guidata_path, "data", "icons")
        return icons_path

    def test_iconbrowser():
        """Test the icon browser widget."""
        with qt_app_context(exec_loop=not execenv.unattended):
            icons_dir = get_guidata_icons_path()
            window = IconBrowserWindow(init_folder=icons_dir)
            window.resize(900, 600)
            window.show()
            if not execenv.unattended:
                window.activateWindow()

    if __name__ == "__main__":
        test_iconbrowser()