Skip to content

himena.widgets

BackendMainWindow

Source code in src\himena\widgets\_backend.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 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
123
124
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
210
211
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
class BackendMainWindow(Generic[_W]):  # pragma: no cover
    _himena_main_window: MainWindow[_W]
    _event_loop_handler: EventLoopHandler

    def __init_subclass__(cls) -> None:
        for name in dir(BackendMainWindow):
            if not hasattr(cls, name):
                raise NotImplementedError(f"Method {name} is not implemented.")

    def _update_widget_theme(self, theme: Theme):
        """Update the theme of the main window."""

    def _main_window_rect(self) -> WindowRect:
        """Get the rect of the main window."""

    def _set_main_window_rect(self, rect: WindowRect) -> None:
        """Set the rect of the main window."""

    def _current_tab_index(self) -> int | None:
        """Get the current tab index.

        If there is no tab, return None.
        """

    def _set_current_tab_index(self, i_tab: int) -> None:
        """Update the current tab index."""

    def _tab_hash(self, i_tab: int) -> Hashable:
        """Get a hashable value of the tab at the index."""

    def _tab_hash_for_window(self, widget: _W) -> Hashable:
        """Get a hashable value of the tab containing the window."""

    def _num_tabs(self) -> int:
        """Get the number of tabs."""

    def _current_sub_window_index(self, i_tab: int) -> int | None:
        """Get the current sub window index in the given tab.

        If there is no sub window, or the tab area itself is selected, return None.
        """

    def _set_current_sub_window_index(self, i_tab: int, i_window: int | None) -> None:
        """Update the current sub window index in the given tab.

        if `i_window` is None, the tab area itself will be selected (all the windows
        will be deselected). `i_window` is asserted to be non-negative.
        """

    def _set_control_widget(self, widget: _W, control: _W | None) -> None:
        """Set the control widget for the given sub window widget.

        A control widget appears on the top-right corner of the toolbar, which will be
        used to display the state of the widget, edit the widget efficiently, etc. For
        example, a font size spinbox for a text editor widget.
        """

    def _update_control_widget(self, current: _W | None) -> None:
        """Switch the control widget to another one in the existing ones.

        If None is given, the control widget will be just hidden.
        """

    def _remove_control_widget(self, widget: _W) -> None:
        """Remove the control widget for the given sub window widget from the stack."""

    def _window_state(self, widget: _W) -> WindowState:
        """The state (min, normal, etc.) of the window."""

    def _set_window_state(
        self,
        widget: _W,
        state: WindowState,
        inst: BackendInstructions,
    ) -> None:
        """Update the state of the window.

        The BackendInstructions indicates the animation or other effects to be applied.
        """

    def _tab_title(self, i_tab: int) -> str:
        """Get the title of the tab at the index."""

    def _set_tab_title(self, i_tab: int, title: str) -> None:
        """Update the title of the tab at the index."""

    def _window_title(self, widget: _W) -> str:
        """Get the title of the window."""

    def _set_window_title(self, widget: _W, title: str) -> None:
        """Update the title of the window."""

    def _window_rect(self, widget: _W) -> WindowRect:
        """Get the rectangle relative to the tab area of the window."""

    def _set_window_rect(
        self,
        widget: _W,
        rect: WindowRect,
        inst: BackendInstructions,
    ) -> None:
        """Update the rectangle of the window.

        The BackendInstructions indicates the animation or other effects to be applied.
        """

    def _area_size(self) -> tuple[int, int]:
        """Get the size of the tab area."""

    @overload
    def _open_file_dialog(
        self,
        mode: Literal["r", "d", "w"] = "r",
        extension_default: str | None = None,
        allowed_extensions: list[str] | None = None,
        caption: str | None = None,
        start_path: Path | None = None,
    ) -> Path | None: ...
    @overload
    def _open_file_dialog(
        self,
        mode: Literal["rm"],
        extension_default: str | None = None,
        allowed_extensions: list[str] | None = None,
        caption: str | None = None,
        start_path: Path | None = None,
    ) -> list[Path] | None: ...

    def _open_file_dialog(
        self,
        mode,
        extension_default=None,
        allowed_extensions=None,
        caption=None,
        start_path=None,
    ):
        """Open a file dialog."""

    def _request_choice_dialog(
        self,
        title: str,
        message: str,
        choices: list[tuple[str, _T]],
        how: Literal["buttons", "radiobuttons"] = "buttons",
    ) -> _T | None:
        """Request a choice dialog and return the clicked text."""

    def _show_command_palette(self, kind: str) -> None:
        """Show the command palette widget of the given kind."""

    def _exit_main_window(self, confirm: bool = False) -> None:
        """Close the main window (confirm if needed)."""

    def _get_widget_list(self, i_tab: int) -> list[tuple[str, _W]]:
        """Get the list of widgets in the tab."""

    def _del_widget_at(self, i_tab: int, i_window: int) -> None:
        """Delete the `i_window`-th window in the `i_tab`-th tab."""

    def _get_tab_name_list(self) -> list[str]:
        """Get the list of tab names."""

    def _del_tab_at(self, i_tab: int) -> None:
        """Delete the `i_tab`-th tab.

        Backend does not need to close the subwindows one by one (will be done on the
        wrapper side).
        """

    def _rename_window_at(self, i_tab: int, i_window: int) -> None:
        """Start renaming the `i_window`-th window in the `i_tab`-th tab."""

    def add_widget(self, widget: _W, i_tab: int, title: str) -> _W:
        """Add a sub window containing the widget to the tab at the index.

        Return the backend widget.
        """

    def set_widget_as_preview(self, widget: _W):
        """Set the widget state as the preview mode."""

    def add_tab(self, title: str) -> None:
        """Add a empty tab with the title."""

    def add_dock_widget(
        self,
        widget: _W,
        title: str | None,
        area: DockAreaString | DockArea | None = DockArea.RIGHT,
        allowed_areas: list[DockAreaString | DockArea] | None = None,
    ) -> _W:
        """Add a dock widget containing the widget to the main window.

        Return the backend dock widget.
        """

    ### dock widgets ###
    def _dock_widget_visible(self, widget: _W) -> bool:
        """Whether the dock widget is visible."""

    def _set_dock_widget_visible(self, widget: _W, visible: bool) -> None:
        """Update the visibility of the dock widget."""

    def _dock_widget_title(self, widget: _W) -> str:
        """Get the title of the dock widget."""

    def _set_dock_widget_title(self, widget: _W, title: str) -> None:
        """Update the title of the dock widget."""

    def _del_dock_widget(self, widget: _W) -> None:
        """Delete the dock widget."""

    ### others ###
    def show(self, run: bool = False) -> None:
        """Show the main window and run the app immediately if `run` is True"""

    def _list_widget_class(
        self,
        type: str,
    ) -> tuple[list[WidgetClassTuple], type[_W]]:
        """List the available widget classes of the given type.

        The method will return (list of (widget model type, widget_class, priority),
        fallback class)
        """

    def _connect_main_window_signals(self, main_window: MainWindow[_W]):
        """Connect the signal of the backend main window to the callbacks."""

    def _connect_window_events(
        self,
        wrapper: SubWindow[_W],
        backend: _W,
    ):
        """Connect the events between the wrapper sub window and the backend widget."""

    def _update_context(self) -> None:
        """Update the application context."""

    def _clipboard_data(self) -> ClipboardDataModel | None:
        """Get the clipboard data."""

    def _set_clipboard_data(self, data: ClipboardDataModel) -> None:
        """Set the clipboard data."""

    def _screenshot(self, target: str) -> NDArray[np.uint8]:
        """Take a screenshot of the target area."""

    def _process_parametric_widget(self, widget: _W) -> _W:
        """Process a parametric widget so that it can be added to the main window.

        The incoming widget must implements the `get_params` method, which gives the
        dictionary of parameters.
        """

    def _connect_parametric_widget_events(
        self,
        wrapper: ParametricWindow[_W],
        widget: _W,
    ) -> None:
        """Connect the events between the wrapper parametric window and the backend."""

    def _signature_to_widget(
        self,
        sig: inspect.Signature,
        show_parameter_labels: bool = True,
        preview: bool = False,
    ) -> _W:
        """Convert a function signature to a widget that can run it."""

    def _add_widget_to_parametric_window(
        self,
        wrapper: ParametricWindow[_W],
        widget: _W,
        result_as: Literal["below", "right"],
    ) -> None:
        """Add a widget to the parametric window."""

    def _remove_widget_from_parametric_window(
        self,
        wrapper: ParametricWindow[_W],
    ) -> None:
        """Remove a widget from the parametric window."""

    def _move_focus_to(self, widget: _W) -> None:
        """Move the focus to the widget."""

    def _set_status_tip(self, tip: str, duration: float) -> None:
        """Set the status tip of the main window for a duration (sec)."""

    def _show_notification(self, text: str, duration: float) -> None:
        """Show notification for a duration (sec)."""

    def _rebuild_for_runtime(self, new_menus: list[str]) -> None:
        """Register the actions at runtime."""

    def _process_future_done_callback(
        self,
        cb: Callable[[Future], None],
        cb_errored: Callable[[Exception], None],
        **kwargs,
    ) -> Callable[[Future], None]:
        """Wrap the callback of the future done event so that it can be run in the main
        thread."""

    def _set_parametric_widget_busy(self, wrapper: ParametricWindow[_W], busy: bool):
        """Set the parametric widget busy status (disable call button etc)."""

    def _add_job_progress(self, future: Future, desc: str, total: int = 0) -> None:
        """Add a job to the job stack."""

    def _add_whats_this(self, text: str, style: Literal["plain", "markdown", "html"]):
        """Add a what's this text to the main window."""

    def _append_result(self, item: dict[str, object]) -> None:
        """Append a new result to the result stack."""
add_dock_widget(widget, title, area=DockArea.RIGHT, allowed_areas=None)

Add a dock widget containing the widget to the main window.

Return the backend dock widget.

Source code in src\himena\widgets\_backend.py
222
223
224
225
226
227
228
229
230
231
232
def add_dock_widget(
    self,
    widget: _W,
    title: str | None,
    area: DockAreaString | DockArea | None = DockArea.RIGHT,
    allowed_areas: list[DockAreaString | DockArea] | None = None,
) -> _W:
    """Add a dock widget containing the widget to the main window.

    Return the backend dock widget.
    """
add_tab(title)

Add a empty tab with the title.

Source code in src\himena\widgets\_backend.py
219
220
def add_tab(self, title: str) -> None:
    """Add a empty tab with the title."""
add_widget(widget, i_tab, title)

Add a sub window containing the widget to the tab at the index.

Return the backend widget.

Source code in src\himena\widgets\_backend.py
210
211
212
213
214
def add_widget(self, widget: _W, i_tab: int, title: str) -> _W:
    """Add a sub window containing the widget to the tab at the index.

    Return the backend widget.
    """
set_widget_as_preview(widget)

Set the widget state as the preview mode.

Source code in src\himena\widgets\_backend.py
216
217
def set_widget_as_preview(self, widget: _W):
    """Set the widget state as the preview mode."""
show(run=False)

Show the main window and run the app immediately if run is True

Source code in src\himena\widgets\_backend.py
251
252
def show(self, run: bool = False) -> None:
    """Show the main window and run the app immediately if `run` is True"""

DockWidget

Dock widget wrapper.

Source code in src\himena\widgets\_wrapper.py
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
class DockWidget(WidgetWrapper[_W]):
    """Dock widget wrapper."""

    def __init__(
        self,
        widget: _W,
        main_window: BackendMainWindow[_W],
        identifier: uuid.UUID | None = None,
    ):
        super().__init__(widget, main_window, identifier)
        self._has_update_configs = hasattr(widget, "update_configs")
        self._parse_config_cache: Callable[[dict], Any] | None = None
        self._command_id: str | None = None

    def __repr__(self) -> str:
        return (
            f"{type(self).__name__}(title={self.title!r}, widget={_widget_repr(self)})"
        )

    @property
    def visible(self) -> bool:
        """Visibility of the dock widget."""
        return self._main_window()._dock_widget_visible(self._frontend_widget())

    @visible.setter
    def visible(self, visible: bool) -> bool:
        return self._main_window()._set_dock_widget_visible(
            self._frontend_widget(), visible
        )

    def show(self) -> None:
        """Show the dock widget."""
        self.visible = True

    def hide(self) -> None:
        """Hide the dock widget."""
        self.visible = False

    @property
    def title(self) -> str:
        """Title of the dock widget."""
        return self._main_window()._dock_widget_title(self._frontend_widget())

    @title.setter
    def title(self, title: str) -> None:
        return self._main_window()._set_dock_widget_title(
            self._frontend_widget(), str(title)
        )

    def _parse_config(self, cfg_dict: dict[str, Any]) -> Any:
        if self._parse_config_cache is not None:
            return self._parse_config_cache(**cfg_dict)
        cfgs = AppActionRegistry.instance()._plugin_default_configs
        cfg_type = cfgs[self._command_id].config_class
        self._parse_config_cache = cfg_type
        return cfg_type(**cfg_dict)

    def update_configs(self, cfg: Any):
        """Update the configuration of the dock widget."""
        if self._has_update_configs:
            if isinstance(cfg, dict):
                cfg = self._parse_config(cfg)
            self.widget.update_configs(cfg)
        return None
title property writable

Title of the dock widget.

visible property writable

Visibility of the dock widget.

hide()

Hide the dock widget.

Source code in src\himena\widgets\_wrapper.py
989
990
991
def hide(self) -> None:
    """Hide the dock widget."""
    self.visible = False
show()

Show the dock widget.

Source code in src\himena\widgets\_wrapper.py
985
986
987
def show(self) -> None:
    """Show the dock widget."""
    self.visible = True
update_configs(cfg)

Update the configuration of the dock widget.

Source code in src\himena\widgets\_wrapper.py
1012
1013
1014
1015
1016
1017
1018
def update_configs(self, cfg: Any):
    """Update the configuration of the dock widget."""
    if self._has_update_configs:
        if isinstance(cfg, dict):
            cfg = self._parse_config(cfg)
        self.widget.update_configs(cfg)
    return None

MainWindow

The main window handler object.

Source code in src\himena\widgets\_main_window.py
  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
 123
 124
 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
 210
 211
 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
 387
 388
 389
 390
 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
 443
 444
 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
 479
 480
 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
 518
 519
 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
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
class MainWindow(Generic[_W]):
    """The main window handler object."""

    def __init__(
        self,
        backend: BackendMainWindow[_W],
        app: HimenaApplication,  # must be initialized
        theme: Theme,
    ) -> None:
        from himena.widgets._initialize import set_current_instance

        self._events = MainWindowEvents()
        self._backend_main_window = backend
        self._internal_clipboard_data: Any | None = None
        self._tab_list = TabList(backend)
        self._new_widget_behavior = NewWidgetBehavior.WINDOW
        self._model_app = app
        self._instructions = BackendInstructions()
        self._history_tab = HistoryContainer[int](max_size=20)
        self._history_command = HistoryContainer[str](max_size=200)
        self._history_closed = HistoryContainer[tuple[Path, str | None]](max_size=10)
        self._file_dialog_hist = FileDialogHistoryDict()
        app.commands.executed.connect(self._on_command_execution)
        backend._connect_main_window_signals(self)
        self._ctx_keys = AppContext(create_context(self, max_depth=0))
        self._tab_list.changed.connect(backend._update_context)
        self._dock_widget_list = DockWidgetList(backend)
        self._recent_manager = RecentFileManager.default(app)
        self._recent_session_manager = RecentSessionManager.default(app)
        if "." not in app.name:
            # likely a mock instance
            set_current_instance(app.name, self)
            self._recent_manager.update_menu()
            self._recent_session_manager.update_menu()
        self._executor = ThreadPoolExecutor(max_workers=5)
        self._global_lock = threading.Lock()
        self._object_type_map = ObjectTypeMap()
        self.theme = theme
        register_defaults(self._object_type_map)

    @property
    def events(self) -> MainWindowEvents[_W]:
        """Main window events."""
        return self._events

    @property
    def object_type_map(self) -> ObjectTypeMap:
        """Mapping object to string that describes the type."""
        return self._object_type_map

    @property
    def socket_info(self) -> SocketInfo:
        """Socket information."""
        eh = self._backend_main_window._event_loop_handler
        return SocketInfo(host=eh._host, port=eh._port)

    @property
    def theme(self) -> Theme:
        """Get the current color theme of the main window."""
        return self._theme

    @theme.setter
    def theme(self, theme: str | Theme) -> None:
        """Set the style of the main window."""
        if isinstance(theme, str):
            theme = Theme.from_global(theme)
        self._theme = theme
        self._backend_main_window._update_widget_theme(theme)

        # if child implements "theme_changed_callback", call it
        for win in self.iter_windows():
            _checker.call_theme_changed_callback(win.widget, theme)
        for dock in self.dock_widgets:
            _checker.call_theme_changed_callback(dock.widget, theme)
        return None

    @property
    def app_profile(self) -> AppProfile:
        """Get the current application profile object."""
        return load_app_profile(self._model_app.name)

    def submit_async_task(
        self,
        func: Callable,
        *args,
        progress_description: str | None = None,
        **kwargs,
    ) -> Future:
        """Submit a task to the thread pool.

        Parameters
        ----------
        func : callable
            Function to run in the background.
        progress_description : str, optional
            Description of the task in the progress bar.
        """
        future = self._executor.submit(func, *args, **kwargs)
        if progress_description is None:
            progress_description = f"Running {func!r}"
        self._backend_main_window._add_job_progress(
            future, desc=progress_description, total=0
        )
        self.model_app.injection_store.process(future)
        return future

    @property
    def tabs(self) -> TabList[_W]:
        """Tab list object."""
        return self._tab_list

    def windows_for_type(self, types: str | list[str]) -> list[SubWindow[_W]]:
        """Get all sub-windows for the given types."""
        windows = []
        if isinstance(types, str):
            types = [types]
        if tab := self.tabs.current():
            for win in tab:
                mtype = win.model_type()
                if mtype and any(is_subtype(mtype, t) for t in types):
                    windows.append(win)
        return windows

    @property
    def dock_widgets(self) -> DockWidgetList[_W]:
        """Dock widget list object."""
        return self._dock_widget_list

    @property
    def model_app(self) -> HimenaApplication:
        """The app-model application instance."""
        return self._model_app

    @property
    def area_size(self) -> tuple[int, int]:
        """(width, height) of the main window tab area."""
        return self._backend_main_window._area_size()

    @property
    def rect(self) -> WindowRect:
        """Window rect (left, top, width, height) of the main window."""
        return self._backend_main_window._main_window_rect()

    @rect.setter
    def rect(self, value) -> None:
        rect = WindowRect.from_tuple(*value)
        return self._backend_main_window._set_main_window_rect(rect)

    @property
    def size(self) -> Size:
        """Size (width, height) of the main window."""
        return self.rect.size()

    @size.setter
    def size(self, size) -> None:
        r0 = self.rect
        s0 = Size(*size)
        self.rect = WindowRect(r0.left, r0.top, s0.width, s0.height)
        return None

    @property
    def clipboard(self) -> ClipboardDataModel | None:
        """Get the clipboard data as a ClipboardDataModel instance."""
        model = self._backend_main_window._clipboard_data()
        model.internal_data = self._internal_clipboard_data
        return model

    @clipboard.setter
    def clipboard(self, data: str | ClipboardDataModel) -> None:
        """Set the clipboard data."""
        if isinstance(data, str):
            data = ClipboardDataModel(text=data)
        elif not isinstance(data, ClipboardDataModel):
            raise ValueError("Clipboard data must be a ClipboardDataModel instance.")
        _LOGGER.info("Setting clipboard data: %r", data)
        self._backend_main_window._set_clipboard_data(data)
        self._internal_clipboard_data = data.internal_data
        return None

    def set_clipboard(
        self,
        *,
        text: str | None = None,
        html: str | None = None,
        image: Any | None = None,
        files: list[str | Path] | None = None,
        internal_data: Any | None = None,
    ) -> None:
        """Set clipboard data."""
        self.clipboard = ClipboardDataModel(
            text=text,
            html=html,
            image=image,
            files=files or [],
            internal_data=internal_data,
        )
        return None

    def add_tab(self, title: str | None = None) -> TabArea[_W]:
        """Add a new tab of given name."""
        return self.tabs.add(title)

    def window_for_id(self, identifier: uuid.UUID) -> SubWindow[_W] | None:
        """Retrieve a sub-window by its identifier."""
        if not isinstance(identifier, uuid.UUID):
            raise ValueError(f"Expected UUID, got {identifier!r}.")
        for win in self.iter_windows():
            if win._identifier == identifier:
                return win
        return None

    def _window_for_workflow_id(self, identifier: uuid.UUID) -> SubWindow[_W] | None:
        """Retrieve a sub-window by its workflow identifier."""
        for win in self.iter_windows():
            try:
                last_id = win._widget_workflow.last_id()
            except Exception:
                return None
            if last_id == identifier:
                return win
        return None

    def _current_or_new_tab(self) -> tuple[int, TabArea[_W]]:
        if self._new_widget_behavior is NewWidgetBehavior.WINDOW:
            if len(self.tabs) == 0:
                self.add_tab()
                idx = 0
            else:
                idx = self._backend_main_window._current_tab_index()
            tabarea = self.tabs[idx]
        else:
            tabarea = self.add_tab()
            idx = len(self.tabs) - 1
        return idx, tabarea

    def add_widget(
        self,
        widget: _W,
        *,
        title: str | None = None,
    ) -> SubWindow[_W]:
        """Add a widget to the sub window.

        Any widget that can be interpreted by the backend can be added. For example, for
        Qt application, you can add any QWidget instance:

        ```python
        ui.add_widget(QtW.QLabel("Hello world!"), title="my widget!")
        ```

        Parameters
        ----------
        widget : Any
            Widget to add.
        title : str, optional
            Title of the sub-window. If not given, its name will be automatically
            generated.

        Returns
        -------
        SubWindow
            The sub-window handler.
        """
        _, tabarea = self._current_or_new_tab()
        return tabarea.add_widget(widget, title=title)

    def add_dock_widget(
        self,
        widget: _W,
        *,
        title: str | None = None,
        area: DockAreaString | DockArea | None = DockArea.RIGHT,
        allowed_areas: list[DockAreaString | DockArea] | None = None,
        _identifier: uuid.UUID | None = None,
    ) -> DockWidget[_W]:
        """Add a custom widget as a dock widget of the main window.

        Parameters
        ----------
        widget : Widget type
            Widget instance that is allowed for the backend.
        title : str, optional
            Title of the dock widget.
        area : dock area, default DockArea.RIGHT
            String or DockArea enum that describes where the dock widget initially
            appears.
        allowed_areas : list of dock area, optional
            List of allowed dock areas for the widget.

        Returns
        -------
        DockWidget
            The dock widget handler.
        """
        dock = DockWidget(widget, self._backend_main_window, identifier=_identifier)
        dock_native = dock._split_interface_and_frontend()[1]
        self._backend_main_window.add_dock_widget(
            dock_native, title=title, area=area, allowed_areas=allowed_areas
        )
        self._dock_widget_list._add_dock_widget(dock)
        _checker.call_widget_added_callback(widget)
        _checker.call_theme_changed_callback(widget, self.theme)
        return dock

    def add_object(
        self,
        value: Any,
        *,
        type: str | None = None,
        title: str | None = None,
        force_open_with: str | None = None,
        metadata: Any | None = None,
    ) -> SubWindow[_W]:
        """Add any data as a widget data model.

        Parameters
        ----------
        value : Any
            Any object. Whether it can be represented as a widget is dependent on the
            plugins that are installed.
        type : str, optional
            Any str that describes the type of the object. This type must be registered
            with a proper widget class.
        title : str, optional
            Title of the sub-window.

        Returns
        -------
        SubWindow
            The sub-window handler.
        """
        if type is None:
            if isinstance(metadata, BaseMetadata):
                type = metadata.expected_type()
        if type is None:
            type = self._object_type_map.pick_type(value)
        wd = WidgetDataModel(
            value=value,
            type=type,
            title=title,
            force_open_with=force_open_with,
            metadata=metadata,
            workflow=ProgrammaticMethod(output_model_type=type).construct_workflow(),
        )
        return self.add_data_model(wd)

    def add_data_model(self, model_data: WidgetDataModel) -> SubWindow[_W]:
        """Add a widget data model as a widget."""
        _, tabarea = self._current_or_new_tab()
        return tabarea.add_data_model(model_data)

    def add_function(
        self,
        func: Callable[..., _T],
        *,
        preview: bool = False,
        title: str | None = None,
        show_parameter_labels: bool = True,
        auto_close: bool = True,
        run_async: bool = False,
        result_as: Literal["window", "below", "right"] = "window",
    ) -> ParametricWindow[_W]:
        """Add a function as a parametric sub-window.

        The input function must return a `WidgetDataModel` instance, which can be
        interpreted by the application.

        Parameters
        ----------
        func : function (...) -> WidgetDataModel
            Function that generates a model from the input parameters.
        title : str, optional
            Title of the sub-window.

        Returns
        -------
        SubWindow
            The sub-window instance that represents the output model.
        """
        _, tabarea = self._current_or_new_tab()
        return tabarea.add_function(
            func, title=title, preview=preview, result_as=result_as,
            show_parameter_labels=show_parameter_labels, auto_close=auto_close,
            run_async=run_async,
        )  # fmt: skip

    def add_parametric_widget(
        self,
        widget: _W,
        callback: Callable | None = None,
        *,
        title: str | None = None,
        preview: bool = False,
        auto_close: bool = True,
        auto_size: bool = True,
        run_async: bool = False,
        result_as: Literal["window", "below", "right"] = "window",
    ) -> ParametricWindow[_W]:
        _, area = self._current_or_new_tab()
        return area.add_parametric_widget(
            widget, callback, title=title, preview=preview, auto_close=auto_close,
            auto_size=auto_size, result_as=result_as, run_async=run_async,
        )  # fmt: skip

    def read_file(
        self,
        file_path: PathOrPaths,
        plugin: str | None = None,
    ) -> SubWindow[_W]:
        """Read local file(s) and open as a new sub-window."""
        _, tabarea = self._current_or_new_tab()
        return tabarea.read_file(file_path, plugin=plugin)

    def read_files(self, file_paths: PathOrPaths):
        """Read multiple files one by one and open as new sub-windows in a same tab."""
        _, tabarea = self._current_or_new_tab()
        return tabarea.read_files(file_paths)

    def read_files_async(self, file_paths: PathOrPaths, plugin: str | None = None):
        """Read multiple files asynchronously and open as new sub-windows."""
        _, tabarea = self._current_or_new_tab()
        return tabarea.read_files_async(file_paths, plugin=plugin)

    def load_session(self, path: str | Path) -> None:
        """Read a session file and update the main window based on the content."""
        from himena.session import update_from_zip, update_from_directory

        fp = Path(path)
        if fp.suffix == ".zip":
            update_from_zip(self, fp)
        elif fp.is_dir():
            update_from_directory(self, fp)
        else:
            raise ValueError(f"Session must be a zip file or a directory, got {fp}.")
        # always plugin=None for reading a session file as a session
        self._recent_session_manager.append_recent_files([(fp, None)])
        self.set_status_tip(f"Session loaded: {fp}", duration=5)
        return None

    def save_session(
        self,
        path: str | Path,
        *,
        save_copies: bool = False,
        allow_calculate: Sequence[str] = (),
    ) -> None:
        """Save the current session to a zip file as a stand-along file."""
        from himena.session import dump_zip, dump_directory

        path = Path(path)
        if path.suffix == ".zip":
            dump_zip(
                self, path, save_copies=save_copies, allow_calculate=allow_calculate
            )
        else:
            dump_directory(
                self, path, save_copies=save_copies, allow_calculate=allow_calculate
            )
        self.set_status_tip(f"Session saved to {path}")
        self._recent_session_manager.append_recent_files([(path, None)])
        return None

    def clear(self) -> None:
        """Clear all widgets in the main window."""
        self.tabs.clear()
        self.dock_widgets.clear()
        return None

    def set_status_tip(self, text: str, duration: float = 10.0) -> None:
        """Set the status tip of the main window.

        This method can be safely called from any thread.

        Parameters
        ----------
        text : str
            Text to show in the status bar.
        duration : float, default 10.0
            Duration (seconds) to show the status tip.
        """
        self._backend_main_window._set_status_tip(text, duration)
        return None

    def show_notification(self, text: str, duration: float = 5.0) -> None:
        self._backend_main_window._show_notification(text, duration)
        return None

    @overload
    def register_function(
        self,
        func: None = None,
        *,
        menus: str | Sequence[str] = "plugins",
        title: str | None = None,
        types: str | Sequence[str] | None = None,
        enablement: BoolOp | None = None,
        keybindings: Sequence[KeyBindingRule] | None = None,
        command_id: str | None = None,
    ) -> None: ...  # noqa: E501
    @overload
    def register_function(
        self,
        func: _F,
        *,
        menus: str | Sequence[str] = "plugins",
        title: str | None = None,
        types: str | Sequence[str] | None = None,
        enablement: BoolOp | None = None,
        keybindings: Sequence[KeyBindingRule] | None = None,
        command_id: str | None = None,
    ) -> _F: ...  # noqa: E501

    def register_function(
        self,
        func=None,
        *,
        menus="plugins",
        title=None,
        types=None,
        enablement=None,
        keybindings=None,
        command_id=None,
    ):
        """Register a function as a callback in runtime.

        Example
        -------
        ``` python
        @ui.register_function(menus="plugins", title="Test functions)
        def test_function():
            print("test")
        ```

        Parameters
        ----------
        func : callable, optional
            Function to register as an action.
        menus : str or sequence of str, default "plugins"
            Menu(s) to add the action. Submenus are separated by `/`.
        title : str, optional
            Title of the action. Name of the function will be used if not given.
        types: str or sequence of str, optional
            The `type` parameter(s) allowed as the WidgetDataModel. If this parameter
            is given, action will be grayed out if the active window does not satisfy
            the listed types.
        enablement: Expr, optional
            Expression that describes when the action will be enabled. As this argument
            is a generalized version of `types` argument, you cannot use both of them.
        command_id : str, optional
            Command ID. If not given, the function qualname will be used.
        """

        def _inner(f):
            action = _actions.make_action_for_function(
                f,
                menus=menus,
                title=title,
                types=types,
                enablement=enablement,
                keybindings=keybindings,
                command_id=command_id,
            )
            _actions.AppActionRegistry.instance().add_action(action)
            added_menus = _actions.AppActionRegistry.instance().install_to(
                self.model_app, [action]
            )
            self._backend_main_window._rebuild_for_runtime(added_menus)
            return f

        return _inner(func) if func else _inner

    def exec_action(
        self,
        id: str,
        *,
        model_context: WidgetDataModel | None = None,
        window_context: SubWindow | None = None,
        with_params: dict[str, Any] | None = None,
        process_model_output: bool = True,
    ) -> Any:
        """Execute an action by its ID.

        Parameters
        ----------
        id : str
            Action ID.
        model_context : WidgetDataModel, optional
            If given, this model will override the application context for the type
            `WidgetDataModel` before the execution.
        window_context : SubWindow, optional
            If given, this window will override the application context for the type
            `SubWindow` before the execution.
        with_params : dict, optional
            Parameters to pass to the parametric action. These parameters will directly
            be passed to the parametric window created after the action is executed.
        process_model_output : bool, default True
            If True, the output result will be processed by the application context. If
            the command return a `WidgetDataModel` instance, it will be converted to a
            sub-window.
        """
        providers: list[tuple[Any, type]] = []
        if model_context is not None:
            providers.append((model_context, WidgetDataModel, 1000))
        if window_context is not None:
            if isinstance(window_context, SubWindow):
                _window_context = window_context
            else:
                raise TypeError(
                    f"`window_context` must be SubWindow or UUID, got {window_context}"
                )
            providers.append((_window_context, SubWindow, 1000))
            if model_context is None:
                providers.append((_window_context.to_model(), WidgetDataModel, 100))
        # execute the command under the given context
        with (
            self.model_app.injection_store.register(providers=providers),
            self._execute_in_context(
                is_gui=with_params is None, process_model_output=process_model_output
            ),
        ):
            result = self.model_app.commands.execute_command(id).result()
            if with_params is not None:
                if (tab := self.tabs.current()) is not None and len(tab) > 0:
                    param_widget = tab[-1]
                else:  # pragma: no cover
                    raise RuntimeError("Unreachable code.")
                if not isinstance(param_widget, ParametricWindow):
                    if len(with_params) == 0:
                        if isinstance(result, Future):
                            return result.result()  # or appropriate handling
                        return result
                    raise ValueError(
                        f"Parametric widget expected but got {param_widget}."
                    )
                # run the callback with the given parameters synchronously
                result = param_widget._callback_with_params(
                    with_params, force_sync=True
                )
        return result

    @overload
    def exec_choose_one_dialog(
        self,
        title: str,
        message: str,
        choices: list[tuple[str, _T]],
        how: Literal["buttons", "radiobuttons"] = "buttons",
    ) -> _T | None: ...
    @overload
    def exec_choose_one_dialog(
        self,
        title: str,
        message: str,
        choices: list[str],
        how: Literal["buttons", "radiobuttons"] = "buttons",
    ) -> str | None: ...

    def exec_choose_one_dialog(self, title, message, choices, how="buttons"):
        """Execute a dialog to choose one from the given choices.

        Parameters
        ----------
        title : str
            Window title of the dialog.
        message : str
            HTML Message to show in the dialog.
        choices : list
            List of choices. Each choice can be a string or a tuple of (text, value).
            This method will return the selected value.
        how : str, default "buttons"
            How to show the choices. "buttons" for horizontal buttons, "radiobuttons"
            for vertically arranged radio buttons.
        """
        if res := self._instructions.choose_one_dialog_response:
            return res()
        _choices_normed = []
        for choice in choices:
            if isinstance(choice, str):
                _choices_normed.append((choice, choice))
            else:
                text, value = choice
                _choices_normed.append((text, value))
        return self._backend_main_window._request_choice_dialog(
            title, message, _choices_normed, how=how
        )

    @overload
    def exec_file_dialog(
        self,
        mode: Literal["r", "d", "w"] = "r",
        *,
        extension_default: str | None = None,
        allowed_extensions: list[str] | None = None,
        caption: str | None = None,
        start_path: str | Path | None = None,
        group: str | None = None,
    ) -> Path | None: ...
    @overload
    def exec_file_dialog(
        self,
        mode: Literal["rm"],
        *,
        extension_default: str | None = None,
        allowed_extensions: list[str] | None = None,
        caption: str | None = None,
        start_path: str | Path | None = None,
        group: str | None = None,
    ) -> list[Path] | None: ...

    def exec_file_dialog(
        self,
        mode: Literal["r", "d", "w", "rm"] = "r",
        *,
        extension_default=None,
        allowed_extensions=None,
        caption=None,
        start_path=None,
        group: str | None = None,
    ):
        """Execute a file dialog to get file path(s)."""
        if mode not in {"r", "d", "w", "rm"}:
            raise ValueError(f"`mode` must be 'r', 'd', 'w' or 'rm', got {mode!r}.")
        if res := self._instructions.file_dialog_response:
            return res()
        if group is None:
            group = mode

        if mode == "w":
            if start_path is None:
                _start_path = self._file_dialog_hist.get_path(group)
            elif Path(start_path).parent != Path("."):
                _start_path = Path(start_path)
            else:  # filename only is given
                _start_path = self._file_dialog_hist.get_path(group, str(start_path))
        else:
            _start_path = Path(start_path or self._file_dialog_hist.get_path(group))
        result = self._backend_main_window._open_file_dialog(
            mode,
            extension_default=extension_default,
            allowed_extensions=allowed_extensions,
            caption=caption,
            start_path=_start_path,
        )
        if result is None:
            return None
        if mode in ["r", "w", "d"]:
            self._file_dialog_hist.update(group, result.parent)
        elif result:
            self._file_dialog_hist.update(group, result[0].parent)
        return result

    def show(self, run: bool = False) -> None:
        """
        Show the main window.

        Parameters
        ----------
        run : bool, default False
            If True, run the application event loop.
        """
        self._backend_main_window.show(run)
        return None

    def close(self) -> None:
        """Close the main window."""
        self._backend_main_window._exit_main_window(confirm=False)
        remove_instance(self.model_app.name, self)
        return None

    @property
    def current_window(self) -> SubWindow[_W] | None:
        """Get the current sub-window."""
        idx_tab = self._backend_main_window._current_tab_index()
        if idx_tab is None:
            return None
        idx_win = self._backend_main_window._current_sub_window_index(idx_tab)
        if idx_win is None:
            return None
        return self.tabs[idx_tab][idx_win]

    @current_window.setter
    def current_window(self, win: SubWindow[_W] | None) -> None:
        """Set the current sub-window."""
        _main = self._backend_main_window
        if win is None:
            _main._set_current_tab_index(None)
            i_tab = _main._current_tab_index()
            if i_tab is not None:
                _main._set_current_sub_window_index(i_tab, None)
        else:
            for i_tab, tab in self.tabs.enumerate():
                for i_win, sub in tab.enumerate():
                    if sub is win:
                        _main._set_current_tab_index(i_tab)
                        _main._set_current_sub_window_index(i_tab, i_win)
                        return None
        return None

    @property
    def current_model(self) -> WidgetDataModel | None:
        """Get the current model of the active sub-window."""
        if sub := self.current_window:
            return sub.to_model()
        return None

    def iter_windows(self) -> Iterator[SubWindow[_W]]:
        """Iterate over all the sub-windows in this main window."""
        for tab in self.tabs:
            yield from tab

    def _provide_file_output(self) -> tuple[WidgetDataModel, SubWindow[_W]]:
        if sub := self.current_window:
            model = sub.to_model()
            return model, sub
        else:
            raise ValueError("No active window.")

    def _tab_activated(self, i: int):
        if i < 0:
            return None
        tab = self.tabs.get(i)
        if tab is not None:
            self.events.tab_activated.emit(tab)
            self._main_window_resized(self.area_size)  # update layout and anchor
        if self._history_tab.get_from_last(1) != i:
            self._history_tab.add(i)
        return None

    def move_window(self, sub: SubWindow[_W], target_index: int) -> None:
        """Move the sub-window to the target tab index."""
        i_tab = i_win = None
        for _i_tab, tab in self.tabs.enumerate():
            for _i_win, win in tab.enumerate():
                if win is sub:
                    i_tab = _i_tab
                    i_win = _i_win
                    break

        if i_tab is None or i_win is None or target_index == i_tab:
            return None
        title = self.tabs[i_tab][i_win].title
        old_rect = self.tabs[i_tab][i_win].rect
        win, widget = self.tabs[i_tab]._pop_no_emit(i_win)
        if target_index < 0:
            self.add_tab()
        self.tabs[target_index].append(win, title)
        win.rect = old_rect
        if layout := win._parent_layout_ref():
            layout.remove(win)
        self.tabs.current_index = i_tab
        return None

    def _window_activated(self):
        back = self._backend_main_window
        back._update_context()
        i_tab = back._current_tab_index()
        if i_tab is None:
            return back._update_control_widget(None)
        tab = self.tabs.get(i_tab)
        if tab is None or len(tab) == 0:
            return back._update_control_widget(None)
        i_win = back._current_sub_window_index(i_tab)
        if i_win is None or len(tab) <= i_win:
            return back._update_control_widget(None)
        win = tab[i_win]
        back._update_control_widget(win.widget)
        _checker.call_widget_activated_callback(win.widget)
        self.events.window_activated.emit(win)
        return None

    def _main_window_resized(self, size: Size):
        if tab := self.tabs.current():
            for layout in tab.layouts:
                layout.anchor = layout.anchor.update_for_window_rect(size, layout.rect)
                layout._reanchor(size)
            for win in tab:
                win.anchor = win.anchor.update_for_window_rect(size, win.rect)
                win._reanchor(size)

    @contextmanager
    def _execute_in_context(
        self,
        is_gui: bool = False,
        process_model_output: bool = True,
        unwrap_future: bool = True,
    ):
        with self._global_lock:
            old_inst = self._instructions.model_copy()
            self._instructions = self._instructions.updated(
                gui_execution=is_gui,
                process_model_output=process_model_output,
                unwrap_future=unwrap_future,
            )
            try:
                yield None
            finally:
                self._instructions = old_inst

    def _iter_widget_class(self, model: WidgetDataModel) -> Iterator[type[_W]]:
        """Pick the most suitable widget class for the given model."""
        if model.force_open_with:
            yield import_object(model.force_open_with)
            return
        widget_classes, fallback_class = self._backend_main_window._list_widget_class(
            model.type
        )
        if not widget_classes:
            warnings.warn(
                f"No widget class is registered for model type {model.type!r}.",
                RuntimeWarning,
                stacklevel=2,
            )
            yield fallback_class
            return
        complete_match = [
            (tup.priority, tup.widget_class)
            for tup in widget_classes
            if tup.type == model.type and tup.priority >= 0
        ]
        if complete_match:
            yield from _iter_sorted(complete_match)
        subtype_match = [
            ((tup.type.count("."), tup.priority), tup.widget_class)
            for tup in widget_classes
            if tup.priority >= 0
        ]
        yield from _iter_sorted(subtype_match)

    def _pick_widget(self, model: WidgetDataModel) -> _W:
        """Pick the most suitable widget for the given model."""
        exceptions: list[tuple[Any, Exception]] = []
        for factory in self._iter_widget_class(model):
            try:
                try:
                    widget = factory(self)
                except TypeError:
                    widget = factory()
                widget_id = get_widget_class_id(type(widget))
                reg = _actions.AppActionRegistry.instance()
                if self.model_app.name != "." and (
                    plugin_configs := self.app_profile.plugin_configs.get(widget_id)
                ):
                    params = {}
                    for k, v in plugin_configs.items():
                        params[k] = v["value"]
                    cfgs = reg._plugin_default_configs
                    cfg_type = cfgs[widget_id].config_class
                    # widget should always have `update_configs` in this case
                    widget.update_configs(cfg_type(**params))
                widget.update_model(model)
            except Exception as e:
                exceptions.append((factory, e))
            else:
                break
        else:
            raise ValueError(
                f"Failed to create a widget for {_short_repr(model)}. Errors:\n"
                f"{_format_exceptions(exceptions)}"
            ) from exceptions[-1][1]
        if exceptions:
            raise exceptions[-1][1]

        return widget

    def _on_command_execution(self, id: str, result: Future):
        if exc := result.exception():
            _LOGGER.exception("Command %r failed: %r", id, exc)
            return
        if action := self.model_app._registered_actions.get(id):
            if getattr(action.callback, NO_RECORDING_FIELD, False):
                return None
            self._history_command.add(id)
app_profile property

Get the current application profile object.

area_size property

(width, height) of the main window tab area.

clipboard property writable

Get the clipboard data as a ClipboardDataModel instance.

current_model property

Get the current model of the active sub-window.

current_window property writable

Get the current sub-window.

dock_widgets property

Dock widget list object.

events property

Main window events.

model_app property

The app-model application instance.

object_type_map property

Mapping object to string that describes the type.

rect property writable

Window rect (left, top, width, height) of the main window.

size property writable

Size (width, height) of the main window.

socket_info property

Socket information.

tabs property

Tab list object.

theme property writable

Get the current color theme of the main window.

add_data_model(model_data)

Add a widget data model as a widget.

Source code in src\himena\widgets\_main_window.py
416
417
418
419
def add_data_model(self, model_data: WidgetDataModel) -> SubWindow[_W]:
    """Add a widget data model as a widget."""
    _, tabarea = self._current_or_new_tab()
    return tabarea.add_data_model(model_data)
add_dock_widget(widget, *, title=None, area=DockArea.RIGHT, allowed_areas=None, _identifier=None)

Add a custom widget as a dock widget of the main window.

Parameters:

Name Type Description Default
widget Widget type

Widget instance that is allowed for the backend.

required
title str

Title of the dock widget.

None
area dock area

String or DockArea enum that describes where the dock widget initially appears.

DockArea.RIGHT
allowed_areas list of dock area

List of allowed dock areas for the widget.

None

Returns:

Type Description
DockWidget

The dock widget handler.

Source code in src\himena\widgets\_main_window.py
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
def add_dock_widget(
    self,
    widget: _W,
    *,
    title: str | None = None,
    area: DockAreaString | DockArea | None = DockArea.RIGHT,
    allowed_areas: list[DockAreaString | DockArea] | None = None,
    _identifier: uuid.UUID | None = None,
) -> DockWidget[_W]:
    """Add a custom widget as a dock widget of the main window.

    Parameters
    ----------
    widget : Widget type
        Widget instance that is allowed for the backend.
    title : str, optional
        Title of the dock widget.
    area : dock area, default DockArea.RIGHT
        String or DockArea enum that describes where the dock widget initially
        appears.
    allowed_areas : list of dock area, optional
        List of allowed dock areas for the widget.

    Returns
    -------
    DockWidget
        The dock widget handler.
    """
    dock = DockWidget(widget, self._backend_main_window, identifier=_identifier)
    dock_native = dock._split_interface_and_frontend()[1]
    self._backend_main_window.add_dock_widget(
        dock_native, title=title, area=area, allowed_areas=allowed_areas
    )
    self._dock_widget_list._add_dock_widget(dock)
    _checker.call_widget_added_callback(widget)
    _checker.call_theme_changed_callback(widget, self.theme)
    return dock
add_function(func, *, preview=False, title=None, show_parameter_labels=True, auto_close=True, run_async=False, result_as='window')

Add a function as a parametric sub-window.

The input function must return a WidgetDataModel instance, which can be interpreted by the application.

Parameters:

Name Type Description Default
func function (...) -> WidgetDataModel

Function that generates a model from the input parameters.

required
title str

Title of the sub-window.

None

Returns:

Type Description
SubWindow

The sub-window instance that represents the output model.

Source code in src\himena\widgets\_main_window.py
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def add_function(
    self,
    func: Callable[..., _T],
    *,
    preview: bool = False,
    title: str | None = None,
    show_parameter_labels: bool = True,
    auto_close: bool = True,
    run_async: bool = False,
    result_as: Literal["window", "below", "right"] = "window",
) -> ParametricWindow[_W]:
    """Add a function as a parametric sub-window.

    The input function must return a `WidgetDataModel` instance, which can be
    interpreted by the application.

    Parameters
    ----------
    func : function (...) -> WidgetDataModel
        Function that generates a model from the input parameters.
    title : str, optional
        Title of the sub-window.

    Returns
    -------
    SubWindow
        The sub-window instance that represents the output model.
    """
    _, tabarea = self._current_or_new_tab()
    return tabarea.add_function(
        func, title=title, preview=preview, result_as=result_as,
        show_parameter_labels=show_parameter_labels, auto_close=auto_close,
        run_async=run_async,
    )  # fmt: skip
add_object(value, *, type=None, title=None, force_open_with=None, metadata=None)

Add any data as a widget data model.

Parameters:

Name Type Description Default
value Any

Any object. Whether it can be represented as a widget is dependent on the plugins that are installed.

required
type str

Any str that describes the type of the object. This type must be registered with a proper widget class.

None
title str

Title of the sub-window.

None

Returns:

Type Description
SubWindow

The sub-window handler.

Source code in src\himena\widgets\_main_window.py
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def add_object(
    self,
    value: Any,
    *,
    type: str | None = None,
    title: str | None = None,
    force_open_with: str | None = None,
    metadata: Any | None = None,
) -> SubWindow[_W]:
    """Add any data as a widget data model.

    Parameters
    ----------
    value : Any
        Any object. Whether it can be represented as a widget is dependent on the
        plugins that are installed.
    type : str, optional
        Any str that describes the type of the object. This type must be registered
        with a proper widget class.
    title : str, optional
        Title of the sub-window.

    Returns
    -------
    SubWindow
        The sub-window handler.
    """
    if type is None:
        if isinstance(metadata, BaseMetadata):
            type = metadata.expected_type()
    if type is None:
        type = self._object_type_map.pick_type(value)
    wd = WidgetDataModel(
        value=value,
        type=type,
        title=title,
        force_open_with=force_open_with,
        metadata=metadata,
        workflow=ProgrammaticMethod(output_model_type=type).construct_workflow(),
    )
    return self.add_data_model(wd)
add_tab(title=None)

Add a new tab of given name.

Source code in src\himena\widgets\_main_window.py
268
269
270
def add_tab(self, title: str | None = None) -> TabArea[_W]:
    """Add a new tab of given name."""
    return self.tabs.add(title)
add_widget(widget, *, title=None)

Add a widget to the sub window.

Any widget that can be interpreted by the backend can be added. For example, for Qt application, you can add any QWidget instance:

ui.add_widget(QtW.QLabel("Hello world!"), title="my widget!")

Parameters:

Name Type Description Default
widget Any

Widget to add.

required
title str

Title of the sub-window. If not given, its name will be automatically generated.

None

Returns:

Type Description
SubWindow

The sub-window handler.

Source code in src\himena\widgets\_main_window.py
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 add_widget(
    self,
    widget: _W,
    *,
    title: str | None = None,
) -> SubWindow[_W]:
    """Add a widget to the sub window.

    Any widget that can be interpreted by the backend can be added. For example, for
    Qt application, you can add any QWidget instance:

    ```python
    ui.add_widget(QtW.QLabel("Hello world!"), title="my widget!")
    ```

    Parameters
    ----------
    widget : Any
        Widget to add.
    title : str, optional
        Title of the sub-window. If not given, its name will be automatically
        generated.

    Returns
    -------
    SubWindow
        The sub-window handler.
    """
    _, tabarea = self._current_or_new_tab()
    return tabarea.add_widget(widget, title=title)
clear()

Clear all widgets in the main window.

Source code in src\himena\widgets\_main_window.py
532
533
534
535
536
def clear(self) -> None:
    """Clear all widgets in the main window."""
    self.tabs.clear()
    self.dock_widgets.clear()
    return None
close()

Close the main window.

Source code in src\himena\widgets\_main_window.py
833
834
835
836
837
def close(self) -> None:
    """Close the main window."""
    self._backend_main_window._exit_main_window(confirm=False)
    remove_instance(self.model_app.name, self)
    return None
exec_action(id, *, model_context=None, window_context=None, with_params=None, process_model_output=True)

Execute an action by its ID.

Parameters:

Name Type Description Default
id str

Action ID.

required
model_context WidgetDataModel

If given, this model will override the application context for the type WidgetDataModel before the execution.

None
window_context SubWindow

If given, this window will override the application context for the type SubWindow before the execution.

None
with_params dict

Parameters to pass to the parametric action. These parameters will directly be passed to the parametric window created after the action is executed.

None
process_model_output bool

If True, the output result will be processed by the application context. If the command return a WidgetDataModel instance, it will be converted to a sub-window.

True
Source code in src\himena\widgets\_main_window.py
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
def exec_action(
    self,
    id: str,
    *,
    model_context: WidgetDataModel | None = None,
    window_context: SubWindow | None = None,
    with_params: dict[str, Any] | None = None,
    process_model_output: bool = True,
) -> Any:
    """Execute an action by its ID.

    Parameters
    ----------
    id : str
        Action ID.
    model_context : WidgetDataModel, optional
        If given, this model will override the application context for the type
        `WidgetDataModel` before the execution.
    window_context : SubWindow, optional
        If given, this window will override the application context for the type
        `SubWindow` before the execution.
    with_params : dict, optional
        Parameters to pass to the parametric action. These parameters will directly
        be passed to the parametric window created after the action is executed.
    process_model_output : bool, default True
        If True, the output result will be processed by the application context. If
        the command return a `WidgetDataModel` instance, it will be converted to a
        sub-window.
    """
    providers: list[tuple[Any, type]] = []
    if model_context is not None:
        providers.append((model_context, WidgetDataModel, 1000))
    if window_context is not None:
        if isinstance(window_context, SubWindow):
            _window_context = window_context
        else:
            raise TypeError(
                f"`window_context` must be SubWindow or UUID, got {window_context}"
            )
        providers.append((_window_context, SubWindow, 1000))
        if model_context is None:
            providers.append((_window_context.to_model(), WidgetDataModel, 100))
    # execute the command under the given context
    with (
        self.model_app.injection_store.register(providers=providers),
        self._execute_in_context(
            is_gui=with_params is None, process_model_output=process_model_output
        ),
    ):
        result = self.model_app.commands.execute_command(id).result()
        if with_params is not None:
            if (tab := self.tabs.current()) is not None and len(tab) > 0:
                param_widget = tab[-1]
            else:  # pragma: no cover
                raise RuntimeError("Unreachable code.")
            if not isinstance(param_widget, ParametricWindow):
                if len(with_params) == 0:
                    if isinstance(result, Future):
                        return result.result()  # or appropriate handling
                    return result
                raise ValueError(
                    f"Parametric widget expected but got {param_widget}."
                )
            # run the callback with the given parameters synchronously
            result = param_widget._callback_with_params(
                with_params, force_sync=True
            )
    return result
exec_choose_one_dialog(title, message, choices, how='buttons')
exec_choose_one_dialog(title: str, message: str, choices: list[tuple[str, _T]], how: Literal['buttons', 'radiobuttons'] = 'buttons') -> _T | None
exec_choose_one_dialog(title: str, message: str, choices: list[str], how: Literal['buttons', 'radiobuttons'] = 'buttons') -> str | None

Execute a dialog to choose one from the given choices.

Parameters:

Name Type Description Default
title str

Window title of the dialog.

required
message str

HTML Message to show in the dialog.

required
choices list

List of choices. Each choice can be a string or a tuple of (text, value). This method will return the selected value.

required
how str

How to show the choices. "buttons" for horizontal buttons, "radiobuttons" for vertically arranged radio buttons.

"buttons"
Source code in src\himena\widgets\_main_window.py
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
def exec_choose_one_dialog(self, title, message, choices, how="buttons"):
    """Execute a dialog to choose one from the given choices.

    Parameters
    ----------
    title : str
        Window title of the dialog.
    message : str
        HTML Message to show in the dialog.
    choices : list
        List of choices. Each choice can be a string or a tuple of (text, value).
        This method will return the selected value.
    how : str, default "buttons"
        How to show the choices. "buttons" for horizontal buttons, "radiobuttons"
        for vertically arranged radio buttons.
    """
    if res := self._instructions.choose_one_dialog_response:
        return res()
    _choices_normed = []
    for choice in choices:
        if isinstance(choice, str):
            _choices_normed.append((choice, choice))
        else:
            text, value = choice
            _choices_normed.append((text, value))
    return self._backend_main_window._request_choice_dialog(
        title, message, _choices_normed, how=how
    )
exec_file_dialog(mode='r', *, extension_default=None, allowed_extensions=None, caption=None, start_path=None, group=None)
exec_file_dialog(mode: Literal['r', 'd', 'w'] = 'r', *, extension_default: str | None = None, allowed_extensions: list[str] | None = None, caption: str | None = None, start_path: str | Path | None = None, group: str | None = None) -> Path | None
exec_file_dialog(mode: Literal['rm'], *, extension_default: str | None = None, allowed_extensions: list[str] | None = None, caption: str | None = None, start_path: str | Path | None = None, group: str | None = None) -> list[Path] | None

Execute a file dialog to get file path(s).

Source code in src\himena\widgets\_main_window.py
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
def exec_file_dialog(
    self,
    mode: Literal["r", "d", "w", "rm"] = "r",
    *,
    extension_default=None,
    allowed_extensions=None,
    caption=None,
    start_path=None,
    group: str | None = None,
):
    """Execute a file dialog to get file path(s)."""
    if mode not in {"r", "d", "w", "rm"}:
        raise ValueError(f"`mode` must be 'r', 'd', 'w' or 'rm', got {mode!r}.")
    if res := self._instructions.file_dialog_response:
        return res()
    if group is None:
        group = mode

    if mode == "w":
        if start_path is None:
            _start_path = self._file_dialog_hist.get_path(group)
        elif Path(start_path).parent != Path("."):
            _start_path = Path(start_path)
        else:  # filename only is given
            _start_path = self._file_dialog_hist.get_path(group, str(start_path))
    else:
        _start_path = Path(start_path or self._file_dialog_hist.get_path(group))
    result = self._backend_main_window._open_file_dialog(
        mode,
        extension_default=extension_default,
        allowed_extensions=allowed_extensions,
        caption=caption,
        start_path=_start_path,
    )
    if result is None:
        return None
    if mode in ["r", "w", "d"]:
        self._file_dialog_hist.update(group, result.parent)
    elif result:
        self._file_dialog_hist.update(group, result[0].parent)
    return result
iter_windows()

Iterate over all the sub-windows in this main window.

Source code in src\himena\widgets\_main_window.py
875
876
877
878
def iter_windows(self) -> Iterator[SubWindow[_W]]:
    """Iterate over all the sub-windows in this main window."""
    for tab in self.tabs:
        yield from tab
load_session(path)

Read a session file and update the main window based on the content.

Source code in src\himena\widgets\_main_window.py
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
def load_session(self, path: str | Path) -> None:
    """Read a session file and update the main window based on the content."""
    from himena.session import update_from_zip, update_from_directory

    fp = Path(path)
    if fp.suffix == ".zip":
        update_from_zip(self, fp)
    elif fp.is_dir():
        update_from_directory(self, fp)
    else:
        raise ValueError(f"Session must be a zip file or a directory, got {fp}.")
    # always plugin=None for reading a session file as a session
    self._recent_session_manager.append_recent_files([(fp, None)])
    self.set_status_tip(f"Session loaded: {fp}", duration=5)
    return None
move_window(sub, target_index)

Move the sub-window to the target tab index.

Source code in src\himena\widgets\_main_window.py
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
def move_window(self, sub: SubWindow[_W], target_index: int) -> None:
    """Move the sub-window to the target tab index."""
    i_tab = i_win = None
    for _i_tab, tab in self.tabs.enumerate():
        for _i_win, win in tab.enumerate():
            if win is sub:
                i_tab = _i_tab
                i_win = _i_win
                break

    if i_tab is None or i_win is None or target_index == i_tab:
        return None
    title = self.tabs[i_tab][i_win].title
    old_rect = self.tabs[i_tab][i_win].rect
    win, widget = self.tabs[i_tab]._pop_no_emit(i_win)
    if target_index < 0:
        self.add_tab()
    self.tabs[target_index].append(win, title)
    win.rect = old_rect
    if layout := win._parent_layout_ref():
        layout.remove(win)
    self.tabs.current_index = i_tab
    return None
read_file(file_path, plugin=None)

Read local file(s) and open as a new sub-window.

Source code in src\himena\widgets\_main_window.py
474
475
476
477
478
479
480
481
def read_file(
    self,
    file_path: PathOrPaths,
    plugin: str | None = None,
) -> SubWindow[_W]:
    """Read local file(s) and open as a new sub-window."""
    _, tabarea = self._current_or_new_tab()
    return tabarea.read_file(file_path, plugin=plugin)
read_files(file_paths)

Read multiple files one by one and open as new sub-windows in a same tab.

Source code in src\himena\widgets\_main_window.py
483
484
485
486
def read_files(self, file_paths: PathOrPaths):
    """Read multiple files one by one and open as new sub-windows in a same tab."""
    _, tabarea = self._current_or_new_tab()
    return tabarea.read_files(file_paths)
read_files_async(file_paths, plugin=None)

Read multiple files asynchronously and open as new sub-windows.

Source code in src\himena\widgets\_main_window.py
488
489
490
491
def read_files_async(self, file_paths: PathOrPaths, plugin: str | None = None):
    """Read multiple files asynchronously and open as new sub-windows."""
    _, tabarea = self._current_or_new_tab()
    return tabarea.read_files_async(file_paths, plugin=plugin)
register_function(func=None, *, menus='plugins', title=None, types=None, enablement=None, keybindings=None, command_id=None)
register_function(func: None = None, *, menus: str | Sequence[str] = 'plugins', title: str | None = None, types: str | Sequence[str] | None = None, enablement: BoolOp | None = None, keybindings: Sequence[KeyBindingRule] | None = None, command_id: str | None = None) -> None
register_function(func: _F, *, menus: str | Sequence[str] = 'plugins', title: str | None = None, types: str | Sequence[str] | None = None, enablement: BoolOp | None = None, keybindings: Sequence[KeyBindingRule] | None = None, command_id: str | None = None) -> _F

Register a function as a callback in runtime.

Example
@ui.register_function(menus="plugins", title="Test functions)
def test_function():
    print("test")

Parameters:

Name Type Description Default
func callable

Function to register as an action.

None
menus str or sequence of str

Menu(s) to add the action. Submenus are separated by /.

"plugins"
title str

Title of the action. Name of the function will be used if not given.

None
types

The type parameter(s) allowed as the WidgetDataModel. If this parameter is given, action will be grayed out if the active window does not satisfy the listed types.

None
enablement

Expression that describes when the action will be enabled. As this argument is a generalized version of types argument, you cannot use both of them.

None
command_id str

Command ID. If not given, the function qualname will be used.

None
Source code in src\himena\widgets\_main_window.py
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
def register_function(
    self,
    func=None,
    *,
    menus="plugins",
    title=None,
    types=None,
    enablement=None,
    keybindings=None,
    command_id=None,
):
    """Register a function as a callback in runtime.

    Example
    -------
    ``` python
    @ui.register_function(menus="plugins", title="Test functions)
    def test_function():
        print("test")
    ```

    Parameters
    ----------
    func : callable, optional
        Function to register as an action.
    menus : str or sequence of str, default "plugins"
        Menu(s) to add the action. Submenus are separated by `/`.
    title : str, optional
        Title of the action. Name of the function will be used if not given.
    types: str or sequence of str, optional
        The `type` parameter(s) allowed as the WidgetDataModel. If this parameter
        is given, action will be grayed out if the active window does not satisfy
        the listed types.
    enablement: Expr, optional
        Expression that describes when the action will be enabled. As this argument
        is a generalized version of `types` argument, you cannot use both of them.
    command_id : str, optional
        Command ID. If not given, the function qualname will be used.
    """

    def _inner(f):
        action = _actions.make_action_for_function(
            f,
            menus=menus,
            title=title,
            types=types,
            enablement=enablement,
            keybindings=keybindings,
            command_id=command_id,
        )
        _actions.AppActionRegistry.instance().add_action(action)
        added_menus = _actions.AppActionRegistry.instance().install_to(
            self.model_app, [action]
        )
        self._backend_main_window._rebuild_for_runtime(added_menus)
        return f

    return _inner(func) if func else _inner
save_session(path, *, save_copies=False, allow_calculate=())

Save the current session to a zip file as a stand-along file.

Source code in src\himena\widgets\_main_window.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
def save_session(
    self,
    path: str | Path,
    *,
    save_copies: bool = False,
    allow_calculate: Sequence[str] = (),
) -> None:
    """Save the current session to a zip file as a stand-along file."""
    from himena.session import dump_zip, dump_directory

    path = Path(path)
    if path.suffix == ".zip":
        dump_zip(
            self, path, save_copies=save_copies, allow_calculate=allow_calculate
        )
    else:
        dump_directory(
            self, path, save_copies=save_copies, allow_calculate=allow_calculate
        )
    self.set_status_tip(f"Session saved to {path}")
    self._recent_session_manager.append_recent_files([(path, None)])
    return None
set_clipboard(*, text=None, html=None, image=None, files=None, internal_data=None)

Set clipboard data.

Source code in src\himena\widgets\_main_window.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
def set_clipboard(
    self,
    *,
    text: str | None = None,
    html: str | None = None,
    image: Any | None = None,
    files: list[str | Path] | None = None,
    internal_data: Any | None = None,
) -> None:
    """Set clipboard data."""
    self.clipboard = ClipboardDataModel(
        text=text,
        html=html,
        image=image,
        files=files or [],
        internal_data=internal_data,
    )
    return None
set_status_tip(text, duration=10.0)

Set the status tip of the main window.

This method can be safely called from any thread.

Parameters:

Name Type Description Default
text str

Text to show in the status bar.

required
duration float

Duration (seconds) to show the status tip.

10.0
Source code in src\himena\widgets\_main_window.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
def set_status_tip(self, text: str, duration: float = 10.0) -> None:
    """Set the status tip of the main window.

    This method can be safely called from any thread.

    Parameters
    ----------
    text : str
        Text to show in the status bar.
    duration : float, default 10.0
        Duration (seconds) to show the status tip.
    """
    self._backend_main_window._set_status_tip(text, duration)
    return None
show(run=False)

Show the main window.

Parameters:

Name Type Description Default
run bool

If True, run the application event loop.

False
Source code in src\himena\widgets\_main_window.py
821
822
823
824
825
826
827
828
829
830
831
def show(self, run: bool = False) -> None:
    """
    Show the main window.

    Parameters
    ----------
    run : bool, default False
        If True, run the application event loop.
    """
    self._backend_main_window.show(run)
    return None
submit_async_task(func, *args, progress_description=None, **kwargs)

Submit a task to the thread pool.

Parameters:

Name Type Description Default
func callable

Function to run in the background.

required
progress_description str

Description of the task in the progress bar.

None
Source code in src\himena\widgets\_main_window.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def submit_async_task(
    self,
    func: Callable,
    *args,
    progress_description: str | None = None,
    **kwargs,
) -> Future:
    """Submit a task to the thread pool.

    Parameters
    ----------
    func : callable
        Function to run in the background.
    progress_description : str, optional
        Description of the task in the progress bar.
    """
    future = self._executor.submit(func, *args, **kwargs)
    if progress_description is None:
        progress_description = f"Running {func!r}"
    self._backend_main_window._add_job_progress(
        future, desc=progress_description, total=0
    )
    self.model_app.injection_store.process(future)
    return future
window_for_id(identifier)

Retrieve a sub-window by its identifier.

Source code in src\himena\widgets\_main_window.py
272
273
274
275
276
277
278
279
def window_for_id(self, identifier: uuid.UUID) -> SubWindow[_W] | None:
    """Retrieve a sub-window by its identifier."""
    if not isinstance(identifier, uuid.UUID):
        raise ValueError(f"Expected UUID, got {identifier!r}.")
    for win in self.iter_windows():
        if win._identifier == identifier:
            return win
    return None
windows_for_type(types)

Get all sub-windows for the given types.

Source code in src\himena\widgets\_main_window.py
181
182
183
184
185
186
187
188
189
190
191
def windows_for_type(self, types: str | list[str]) -> list[SubWindow[_W]]:
    """Get all sub-windows for the given types."""
    windows = []
    if isinstance(types, str):
        types = [types]
    if tab := self.tabs.current():
        for win in tab:
            mtype = win.model_type()
            if mtype and any(is_subtype(mtype, t) for t in types):
                windows.append(win)
    return windows

ParametricWindow

Subwindow with a parametric widget inside.

Source code in src\himena\widgets\_wrapper.py
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
class ParametricWindow(SubWindow[_W]):
    """Subwindow with a parametric widget inside."""

    _IS_PREVIEWING = "is_previewing"  # keyword argument used for preview flag
    btn_clicked = Signal(object)  # emit self
    params_changed = Signal(object)  # emit self

    def __init__(
        self,
        widget: _W,
        callback: Callable,
        main_window: BackendMainWindow[_W],
        identifier: uuid.UUID | None = None,
    ):
        super().__init__(widget, main_window, identifier)
        self._callback = callback
        self.btn_clicked.connect(self._widget_callback)
        self._preview_window_ref: Callable[[], WidgetWrapper[_W] | None] = _do_nothing
        self._auto_close = True
        self._run_asynchronously = False
        self._last_future: Future | None = None
        self._result_as: Literal["window", "below", "right"] = "window"

        # check if callback has "is_previewing" argument
        sig = inspect.signature(callback)
        self._has_is_previewing = self._IS_PREVIEWING in sig.parameters
        self._fn_signature = sig

    def get_params(self) -> dict[str, Any]:
        """Get the parameters of the widget."""
        if hasattr(self.widget, PWPN.GET_PARAMS):
            params = getattr(self.widget, PWPN.GET_PARAMS)()
            if not isinstance(params, dict):
                raise TypeError(
                    f"`{PWPN.GET_PARAMS}` of {self.widget!r} must return a dict, got "
                    f"{type(params)}."
                )
        else:
            params = {}
        return params

    def _get_preview_window(self) -> SubWindow[_W] | None:
        """Return the preview window if it is alive."""
        if (prev := self._preview_window_ref()) and prev.is_alive:
            return prev
        return None

    def _is_run_immediately(self) -> bool:
        for param in self._fn_signature.parameters.values():
            annot = param.annotation
            if _is_annotated(annot):
                _, op = _split_annotated_type(annot)
                if "bind" not in op:
                    return False
            else:
                return False
        return True

    def _widget_callback(self):
        """Callback when the call button is clicked."""
        main = self._main_window()
        main._set_parametric_widget_busy(self, True)
        try:
            self._callback_with_params(self.get_params())
        except Exception:
            main._set_parametric_widget_busy(self, False)
            raise

    def _call(self, **kwargs):
        """Call the callback (maybe) asynchronously."""
        ui = self._main_window()._himena_main_window
        if self._run_asynchronously:
            if self._last_future is not None:
                self._last_future.cancel()
                self._last_future = None
            self._last_future = future = ui._executor.submit(self._callback, **kwargs)
            return future
        else:
            return self._callback(**kwargs)

    def _widget_preview_callback(self):
        """Callback function of parameter change during preview"""
        main = self._main_window()
        if not self.is_preview_enabled():
            if prev := self._get_preview_window():
                self._preview_window_ref = _do_nothing
                self._child_windows.discard(prev)
                if self._result_as == "window":
                    prev._close_me(main._himena_main_window)
                else:
                    main._remove_widget_from_parametric_window(self)
                    if hint := self.size_hint():
                        self.rect = (self.rect.left, self.rect.top, hint[0], hint[1])
            return None
        kwargs = self.get_params()
        if self._has_is_previewing:
            kwargs[self._IS_PREVIEWING] = True
        return_value = self._call(**kwargs)
        if isinstance(return_value, Future):  # running asynchronously
            done = main._process_future_done_callback(
                self._widget_preview_callback_done,
                lambda e: main._set_parametric_widget_busy(self, False),
            )
            return_value.add_done_callback(done)
            main._set_parametric_widget_busy(self, True)
        else:
            temp_future = Future()
            temp_future.set_result(return_value)
            self._widget_preview_callback_done(temp_future)

    def _widget_preview_callback_done(self, future: Future):
        """Callback function when the job of preview is done."""
        main = self._main_window()
        main._set_parametric_widget_busy(self, False)
        return_value = future.result()
        if return_value is None:
            return None
        if not isinstance(return_value, WidgetDataModel):
            raise NotImplementedError(
                "Preview is only supported for WidgetDataModel but the return value "
                f"was {type(return_value)}"
            )
        if prev := self._get_preview_window():
            prev.update_model(return_value)
        else:
            # create a new preview window
            result_widget = self._model_to_new_window(return_value)
            if self._result_as == "window":
                # create a new preview window
                title = f"{return_value.title} (preview)"
                prev = self.add_child(result_widget, title=title)
                main.set_widget_as_preview(prev)
                prev.force_not_editable(True)  # disable editing if possible
                # move the window so that it does not overlap with the parametric window
                prev.rect = prevent_window_overlap(self, prev, main._area_size())
            else:
                main._add_widget_to_parametric_window(
                    self, result_widget, self._result_as
                )
                # update the size because new window is added
                if hint := self.size_hint():
                    self.rect = (self.rect.left, self.rect.top, hint[0], hint[1])
                prev = WidgetWrapper(result_widget, main)  # just for wrapping
            self._preview_window_ref = weakref.ref(prev)
            main._move_focus_to(self._frontend_widget())
        return None

    def _process_return_value(self, return_value: Any, kwargs: dict[str, Any]):
        main = self._main_window()
        ui = main._himena_main_window
        main._set_parametric_widget_busy(self, False)
        tracker = ModelTrack.get(self._callback)
        _return_annot = self._fn_signature.return_annotation
        _LOGGER.info("Got tracker: %r", tracker)
        if isinstance(return_value, WidgetDataModel):
            if prev := self._get_preview_window():
                # no need to create a new window, just use the preview window
                self._preview_window_ref = _do_nothing
                if self._result_as != "window":
                    widget = prev.widget  # avoid garbage collection
                    main._remove_widget_from_parametric_window(self)
                    result_widget = ui.add_widget(widget)
                    result_widget._update_from_returned_model(return_value)
                else:
                    self._child_windows.discard(prev)
                    result_widget = prev
                result_widget.title = return_value.title  # title needs update

                # if callback has "is_previewing" argument, the returned value may
                # differ, thus the widget needs update.
                if self._has_is_previewing:
                    result_widget.update_model(return_value)
                result_widget.force_not_editable(False)
                with suppress(AttributeError):
                    result_widget.is_editable = True
                if self._auto_close:
                    self._close_me(ui)
            else:
                result_widget = self._process_model_output(return_value, tracker)
                if result_widget is None:
                    if tracker is not None:
                        new_workflow = tracker.to_workflow(kwargs)
                        return_value.workflow = new_workflow  # needs inheritance
                    return None
                elif return_value.update_inplace:
                    result_widget._widget_workflow = tracker.to_workflow(kwargs)
            _LOGGER.info("Got subwindow: %r", result_widget)
            if tracker is not None:
                new_workflow = tracker.to_workflow(kwargs)
                _LOGGER.info(
                    "Inherited method %r, where the original method was %r",
                    new_workflow,
                    return_value.workflow,
                )
                # NOTE: overwrite=False is needed to avoid overwriting ReaderMethod
                result_widget._update_model_workflow(new_workflow, overwrite=False)
                if isinstance(new_workflow, CommandExecution):
                    if not isinstance(
                        return_value.save_behavior_override, NoNeedToSave
                    ):
                        result_widget._set_ask_save_before_close(True)
        elif _return_annot in (Parametric, ParametricWidgetProtocol):
            raise NotImplementedError
        else:
            annot = getattr(self._callback, "__annotations__", {})
            if isinstance(return_value, Future):
                injection_type_hint = Future
                # This is hacky. The injection store will process the result but the
                # return type cannot be inherited from the callback. Here, we just set
                # the type hint to Future and let it processed in the
                # "_future_done_callback" method of himena application.
                if prev := self._get_preview_window():
                    top_left = (prev.rect.left, prev.rect.top)
                    size = prev.rect.size()
                else:
                    top_left = (self.rect.left, self.rect.top)
                    size = None
                injection_ns = ui.model_app.injection_store.namespace
                FutureInfo(
                    type_hint=annot.get("return", None),
                    track=tracker,
                    kwargs=kwargs,
                    top_left=top_left,
                    size=size,
                ).resolve_type_hint(injection_ns).set(return_value)
            else:
                injection_type_hint = annot.get("return", None)
            self._process_other_output(return_value, injection_type_hint)
        return None

    def _callback_with_params(
        self,
        kwargs: dict[str, Any],
        force_sync: bool = False,
    ) -> Any:
        if self._has_is_previewing:
            kwargs = {**kwargs, self._IS_PREVIEWING: False}
        main = self._main_window()
        old_run_async = self._run_asynchronously
        try:
            if force_sync:
                self._run_asynchronously = False
            return_value = self._call(**kwargs)
        except Exception:
            main._set_parametric_widget_busy(self, False)
            raise
        finally:
            self._run_asynchronously = old_run_async
        if isinstance(return_value, Future):
            main._add_job_progress(return_value, desc=self.title, total=0)
            return_value.add_done_callback(
                main._process_future_done_callback(
                    self._process_return_value,
                    lambda e: main._set_parametric_widget_busy(self, False),
                    kwargs=kwargs,
                )
            )
            return return_value
        else:
            main._set_parametric_widget_busy(self, False)
            self._process_return_value(return_value, kwargs)
            return return_value

    def is_preview_enabled(self) -> bool:
        """Whether the widget supports preview."""
        isfunc = getattr(self.widget, PWPN.IS_PREVIEW_ENABLED, None)
        return callable(isfunc) and isfunc()

    def _emit_btn_clicked(self) -> None:
        return self.btn_clicked.emit(self)

    def _emit_param_changed(self) -> None:
        return self.params_changed.emit(self)

    def _process_model_output(
        self,
        model: WidgetDataModel,
        tracker: ModelTrack | None = None,
    ) -> SubWindow[_W] | None:
        """Process the returned WidgetDataModel."""
        ui = self._main_window()._himena_main_window
        i_tab, i_win = self._find_me(ui)
        rect = self.rect
        if self._auto_close:
            del ui.tabs[i_tab][i_win]
        if model.update_inplace and tracker and tracker.contexts:
            if win := ui._window_for_workflow_id(tracker.contexts[0].value):
                win.update_model(model)
                return win
        if ui._instructions.process_model_output:
            widget = self._model_to_new_window(model)
            result_widget = ui.tabs[i_tab].add_widget(
                widget, title=model.title, auto_size=False
            )
            # coerce rect
            if size_hint := result_widget.size_hint():
                new_rect = (rect.left, rect.top, size_hint[0], size_hint[1])
            else:
                new_rect = rect
            result_widget.rect = new_rect
            _checker.call_widget_added_callback(widget)
            return result_widget._update_from_returned_model(model)
        return None

    def _model_to_new_window(self, model: WidgetDataModel) -> _W:
        ui = self._main_window()._himena_main_window
        widget = ui._pick_widget(model)
        return widget

    def _process_other_output(self, return_value: Any, type_hint: Any | None = None):
        _LOGGER.info("Got output: %r with type hint %r", type(return_value), type_hint)
        ui = self._main_window()._himena_main_window
        ui.model_app.injection_store.process(return_value, type_hint=type_hint)
        if self._auto_close:
            with suppress(RuntimeError):
                # FIXME: if the async command does not require parameter input, this
                # window is already closed. We just ignore the error for now.
                self._close_me(ui)
get_params()

Get the parameters of the widget.

Source code in src\himena\widgets\_wrapper.py
663
664
665
666
667
668
669
670
671
672
673
674
def get_params(self) -> dict[str, Any]:
    """Get the parameters of the widget."""
    if hasattr(self.widget, PWPN.GET_PARAMS):
        params = getattr(self.widget, PWPN.GET_PARAMS)()
        if not isinstance(params, dict):
            raise TypeError(
                f"`{PWPN.GET_PARAMS}` of {self.widget!r} must return a dict, got "
                f"{type(params)}."
            )
    else:
        params = {}
    return params
is_preview_enabled()

Whether the widget supports preview.

Source code in src\himena\widgets\_wrapper.py
898
899
900
901
def is_preview_enabled(self) -> bool:
    """Whether the widget supports preview."""
    isfunc = getattr(self.widget, PWPN.IS_PREVIEW_ENABLED, None)
    return callable(isfunc) and isfunc()

SubWindow

Source code in src\himena\widgets\_wrapper.py
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
387
388
389
390
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
443
444
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
479
480
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
518
519
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
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
class SubWindow(WidgetWrapper[_W], Layout):
    state_changed = Signal(WindowState)
    renamed = Signal(str)
    closed = Signal()

    def __init__(
        self,
        widget: _W,
        main_window: BackendMainWindow[_W],
        identifier: uuid.UUID | None = None,
    ):
        super().__init__(widget, main_window=main_window, identifier=identifier)
        Layout.__init__(self, main_window)
        self._child_windows: weakref.WeakSet[SubWindow[_W]] = weakref.WeakSet()
        self._alive = False
        self.closed.connect(self._close_callback)

    def __repr__(self) -> str:
        return (
            f"{type(self).__name__}(title={self.title!r}, widget={_widget_repr(self)})"
        )

    def __class_getitem__(cls, widget_type: type[_W]):
        # this hack allows in_n_out to assign both SubWindow and SubWindow[T] to the
        # same provider/processor.
        return cls

    @classmethod
    def _deserialize_layout(cls, obj: dict, main: MainWindow) -> SubWindow[Any]:
        win = main.window_for_id(uuid.UUID(obj["id"]))
        if win is None:
            raise RuntimeError(f"SubWindow {obj['id']} not found in main window.")
        return win

    def _serialize_layout(self):
        return {"type": "subwindow", "id": self._identifier.hex}

    @property
    def title(self) -> str:
        """Title of the sub-window."""
        return self._main_window()._window_title(self._frontend_widget())

    @title.setter
    def title(self, value: str) -> None:
        self._main_window()._set_window_title(self._frontend_widget(), value)

    @property
    def state(self) -> WindowState:
        """State (e.g. maximized, minimized) of the sub-window."""
        return self._main_window()._window_state(self._frontend_widget())

    @state.setter
    def state(self, value: WindowState | str) -> None:
        main = self._main_window()._himena_main_window
        inst = main._instructions.updated(animate=False)
        self._set_state(value, inst)

    @property
    def rect(self) -> WindowRect:
        """Position and size of the sub-window."""
        return self._main_window()._window_rect(self._frontend_widget())

    @rect.setter
    def rect(self, value: tuple[int, int, int, int]) -> None:
        main = self._main_window()._himena_main_window
        inst = main._instructions.updated(animate=False)
        self._set_rect(value, inst)

    @property
    def is_alive(self) -> bool:
        """Whether the sub-window is present in a main window."""
        return self._alive

    def to_model(self) -> WidgetDataModel:
        """Export the widget data."""
        if not self.supports_to_model:
            raise ValueError("Widget does not have `to_model` method.")
        model = self.widget.to_model()  # type: ignore
        if not isinstance(model, WidgetDataModel):
            raise TypeError(
                "`to_model` method must return an instance of WidgetDataModel, got "
                f"{type(model)}"
            )

        if model.title is None:
            model.title = self.title
        if len(model.workflow) == 0:
            model.workflow = self._widget_workflow
        if self.is_modified:
            self._data_modifications.update_workflow(model)
        if model.extension_default is None:
            model.extension_default = self._extension_default_fallback
        return model

    def update_value(self, value: Any) -> None:
        """Update the value of the widget."""
        if hasattr(self.widget, "update_value"):
            self.widget.update_value(value)
        else:
            model = self.to_model()
            self.update_model(model.with_value(value))

    def update_metadata(self, metadata: Any) -> None:
        """Update the metadata of the widget data model."""
        return self.update_model(self.to_model().with_metadata(metadata))

    def write_model(self, path: str | Path, plugin: str | None = None) -> None:
        """Write the widget data to a file."""
        return self._write_model(path, plugin, self.to_model())

    @property
    def tab_area(self) -> TabArea[_W]:
        """Tab area of the sub-window."""
        _hash = self._main_window()._tab_hash_for_window(self._frontend_widget())
        return self._main_window()._himena_main_window.tabs._tab_areas[_hash]

    def _write_model(
        self, path: str | Path, plugin: str | None, model: WidgetDataModel
    ) -> None:
        io_utils.write(model, path, plugin=plugin)
        self.update_default_save_path(path)
        return None

    def _set_modification_tracking(self, enabled: bool) -> None:
        if self._data_modifications.track_enabled == enabled:
            return None  # already in the desired state
        if enabled and self.supports_to_model:
            self._data_modifications = Modifications(
                initial_value=self.to_model().value,
                track_enabled=True,
            )
        else:
            self._data_modifications = Modifications(
                initial_value=None, track_enabled=False
            )

    def _set_state(self, value: WindowState, inst: BackendInstructions | None = None):
        main = self._main_window()
        front = self._frontend_widget()
        if main._window_state(front) is value:  # if already in the state, do nothing
            return None
        if inst is None:
            inst = main._himena_main_window._instructions
        main._set_window_state(front, value, inst)
        stack = self.tab_area._minimized_window_stack_layout
        if value is WindowState.MIN:
            stack.add(self)
            self.anchor = None
        elif value in (WindowState.FULL, WindowState.MAX):
            self.anchor = _anchor.AllCornersAnchor()
        else:
            self.anchor = None
            if self._parent_layout_ref() is stack:
                stack.remove(self)
                stack._reanchor(Size(*main._area_size()))

    def _set_rect(
        self,
        value: tuple[int, int, int, int],
        inst: BackendInstructions | None = None,
    ):
        if inst is None:
            inst = self._main_window()._himena_main_window._instructions
        rect_old = self.rect
        main = self._main_window()
        front = self._frontend_widget()
        rect = WindowRect.from_tuple(*value)
        anc = self.anchor.update_for_window_rect(main._area_size(), rect)
        main._set_window_rect(front, rect, inst)
        self.anchor = anc
        if parent := self._parent_layout_ref():
            parent._adjust_child_resize(self, rect_old, rect)

    def _reanchor(self, size: Size):
        if self.state is WindowState.MIN:
            pass
        elif self.state in (WindowState.MAX, WindowState.FULL):
            self.rect = WindowRect(0, 0, *size)
        else:
            super()._reanchor(size)

    def _process_drop_event(
        self,
        incoming: DragDataModel,
        source: SubWindow[_W] | None = None,
    ) -> bool:
        if hasattr(self.widget, "dropped_callback"):
            # to remember how the model was mapped to a widget class
            model = incoming.data_model()
            if source is not None:
                model.force_open_with = get_widget_class_id(source.widget)
            drop_result = self.widget.dropped_callback(model)
            if drop_result is None:
                drop_result = DropResult()
            ui = self._main_window()._himena_main_window
            if drop_result.command_id:
                # record the command
                out = ui.exec_action(
                    drop_result.command_id,
                    window_context=self,
                    model_context=self.to_model(),
                    with_params=drop_result.with_params,
                    process_model_output=False,
                )
                if isinstance(out, WidgetDataModel):
                    self.update_model(out)
                    self._update_model_workflow(out.workflow)
            if source is not None:
                if drop_result.delete_input:
                    source._close_me(ui)
                ui._backend_main_window._move_focus_to(source._frontend_widget())
            return True
        return False

    def update(
        self,
        *,
        rect: tuple[int, int, int, int] | WindowRect | None = None,
        state: WindowState | None = None,
        title: str | None = None,
        anchor: _anchor.WindowAnchor | str | None = None,
    ) -> SubWindow[_W]:
        """A helper method to update window properties."""
        if rect is not None:
            self.rect = rect
        if state is not None:
            self.state = state
        if title is not None:
            self.title = title
        if anchor is not None:
            self.anchor = anchor
        return self

    def add_child(
        self,
        widget: _W,
        *,
        title: str | None = None,
    ) -> SubWindow[_W]:
        """Add a child sub-window, which is automatically closed when the parent is closed."""  # noqa: E501
        main = self._main_window()._himena_main_window
        i_tab, _ = self._find_me(main)
        child = main.tabs[i_tab].add_widget(widget, title=title)
        self._child_windows.add(child)
        return child

    def _find_me(self, main: MainWindow) -> tuple[int, int]:
        for i_tab, tab in main.tabs.enumerate():
            for i_win, win in tab.enumerate():
                # NOTE: should not be `win is self`, because the wrapper may be
                # recreated
                if win.widget is self.widget:
                    return i_tab, i_win
        raise RuntimeError(f"SubWindow {self.title} not found in main window.")

    def _close_me(self, main: MainWindow, confirm: bool = False) -> None:
        if self._need_ask_save_before_close() and confirm:
            title_short = repr(self.title)
            if len(title_short) > 60:
                title_short = title_short[:60] + "..."
            if isinstance(self.save_behavior, SaveToNewPath):
                message = f"{title_short} is not saved yet. Save before closing?"
            else:
                message = f"Save changes to {title_short}?"
            resp = main.exec_choose_one_dialog(
                title="Closing window",
                message=message,
                choices=["Save", "Don't save", "Cancel"],
            )
            if resp is None or resp == "Cancel":
                return None
            elif resp == "Save":
                if cb := self._save_from_dialog(main):
                    cb()
                else:
                    return None

        i_tab, i_win = self._find_me(main)
        del main.tabs[i_tab][i_win]

    def _save_from_dialog(
        self,
        main: MainWindow,
        behavior: SaveBehavior | None = None,
        plugin: str | None = None,
    ) -> Callable[[], None] | None:
        """Save this window to a new path, return if saved."""
        if behavior is None:
            behavior = self.save_behavior
        model = self.to_model()
        if save_path := behavior.get_save_path(main, model):

            def _save():
                main.set_status_tip(f"Saving {self.title!r} to {save_path}", duration=2)
                self._write_model(save_path, plugin=plugin, model=model)
                main.set_status_tip(f"Saved {self.title!r} to {save_path}", duration=2)
                return None

            return _save
        return None

    def _close_all_children(self, main: MainWindow) -> None:
        """Close all the sub-windows that are children of this window."""
        for child in self._child_windows:
            child._close_all_children(main)
            if child.is_alive:
                child._close_me(main, confirm=False)

    def _close_callback(self):
        main = self._main_window()._himena_main_window
        self._close_all_children(main)
        if layout := self._parent_layout_ref():
            layout.remove(self)
        self._alive = False

    def _determine_read_from(self) -> tuple[Path | list[Path], str | None] | None:
        """Determine how can the data be efficiently read."""
        workflow = self._widget_workflow.last()
        if isinstance(workflow, LocalReaderMethod):
            return workflow.path, workflow.plugin
        elif isinstance(save_bh := self.save_behavior, SaveToPath):
            return save_bh.path, None
        else:
            return None

    def _update_from_returned_model(self, model: WidgetDataModel) -> SubWindow[_W]:
        """Update the sub-window based on the returned model."""
        if (wf := model.workflow.last()) is not None:
            if isinstance(wf, LocalReaderMethod):
                # file is directly read from the local path
                if isinstance(save_path := wf.path, Path):
                    self.update_default_save_path(save_path, plugin=wf.plugin)
            elif isinstance(wf, CommandExecution):
                # model is created by some command
                if not isinstance(model.save_behavior_override, NoNeedToSave):
                    self._set_ask_save_before_close(True)
            self._identifier = wf.id
        if len(wlist := model.workflow) > 0:
            self._update_model_workflow(wlist)
        if save_behavior_override := model.save_behavior_override:
            self._save_behavior = save_behavior_override
        if not model.editable:
            with suppress(AttributeError):
                self.is_editable = False

        if model.is_subtype_of("text"):
            self._set_modification_tracking(True)
        return self

    def _switch_to_file_watch_mode(self):
        # TODO: don't use Qt in the future
        from himena.qt._qtwatchfiles import QWatchFileObject

        self.title = f"[Preview] {self.title}"
        QWatchFileObject(self)
        return None
is_alive property

Whether the sub-window is present in a main window.

rect property writable

Position and size of the sub-window.

state property writable

State (e.g. maximized, minimized) of the sub-window.

tab_area property

Tab area of the sub-window.

title property writable

Title of the sub-window.

add_child(widget, *, title=None)

Add a child sub-window, which is automatically closed when the parent is closed.

Source code in src\himena\widgets\_wrapper.py
510
511
512
513
514
515
516
517
518
519
520
521
def add_child(
    self,
    widget: _W,
    *,
    title: str | None = None,
) -> SubWindow[_W]:
    """Add a child sub-window, which is automatically closed when the parent is closed."""  # noqa: E501
    main = self._main_window()._himena_main_window
    i_tab, _ = self._find_me(main)
    child = main.tabs[i_tab].add_widget(widget, title=title)
    self._child_windows.add(child)
    return child
to_model()

Export the widget data.

Source code in src\himena\widgets\_wrapper.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
def to_model(self) -> WidgetDataModel:
    """Export the widget data."""
    if not self.supports_to_model:
        raise ValueError("Widget does not have `to_model` method.")
    model = self.widget.to_model()  # type: ignore
    if not isinstance(model, WidgetDataModel):
        raise TypeError(
            "`to_model` method must return an instance of WidgetDataModel, got "
            f"{type(model)}"
        )

    if model.title is None:
        model.title = self.title
    if len(model.workflow) == 0:
        model.workflow = self._widget_workflow
    if self.is_modified:
        self._data_modifications.update_workflow(model)
    if model.extension_default is None:
        model.extension_default = self._extension_default_fallback
    return model
update(*, rect=None, state=None, title=None, anchor=None)

A helper method to update window properties.

Source code in src\himena\widgets\_wrapper.py
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
def update(
    self,
    *,
    rect: tuple[int, int, int, int] | WindowRect | None = None,
    state: WindowState | None = None,
    title: str | None = None,
    anchor: _anchor.WindowAnchor | str | None = None,
) -> SubWindow[_W]:
    """A helper method to update window properties."""
    if rect is not None:
        self.rect = rect
    if state is not None:
        self.state = state
    if title is not None:
        self.title = title
    if anchor is not None:
        self.anchor = anchor
    return self
update_metadata(metadata)

Update the metadata of the widget data model.

Source code in src\himena\widgets\_wrapper.py
379
380
381
def update_metadata(self, metadata: Any) -> None:
    """Update the metadata of the widget data model."""
    return self.update_model(self.to_model().with_metadata(metadata))
update_value(value)

Update the value of the widget.

Source code in src\himena\widgets\_wrapper.py
371
372
373
374
375
376
377
def update_value(self, value: Any) -> None:
    """Update the value of the widget."""
    if hasattr(self.widget, "update_value"):
        self.widget.update_value(value)
    else:
        model = self.to_model()
        self.update_model(model.with_value(value))
write_model(path, plugin=None)

Write the widget data to a file.

Source code in src\himena\widgets\_wrapper.py
383
384
385
def write_model(self, path: str | Path, plugin: str | None = None) -> None:
    """Write the widget data to a file."""
    return self._write_model(path, plugin, self.to_model())

TabArea

An area containing multiple sub-windows.

Source code in src\himena\widgets\_widget_list.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
210
211
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
387
388
389
390
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
443
444
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
479
480
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
518
519
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
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
class TabArea(SemiMutableSequence[SubWindow[_W]], _HasMainWindowRef[_W]):
    """An area containing multiple sub-windows."""

    def __init__(self, main_window: BackendMainWindow[_W], hash_value: Hashable):
        super().__init__(main_window)
        self._hash_value = hash_value
        # A tab area always has a layout for stacking minimized windows
        self._minimized_window_stack_layout = VStackLayout(main_window, inverted=True)
        self._layouts = [self._minimized_window_stack_layout]
        self._minimized_window_stack_layout._reanchor(Size(*main_window._area_size()))
        # the tab-specific result stack
        self._result_stack_ref: Callable[[], _W | None] = lambda: None

    @property
    def layouts(self) -> FrozenList[Layout]:
        """List of layouts in the tab area."""
        return FrozenList(self._layouts)

    def _tab_index(self) -> int:
        main = self._main_window()
        for i in range(main._num_tabs()):
            if main._tab_hash(i) == self._hash_value:
                return i
        raise ValueError("Tab is already removed from the main window.")

    def __getitem__(self, index_or_name: int | str) -> SubWindow[_W]:
        index = self._norm_index_or_name(index_or_name)
        widgets = self._main_window()._get_widget_list(self._tab_index())
        front = widgets[index][1]
        return front._himena_widget

    def __delitem__(self, index_or_name: int | str) -> None:
        index = self._norm_index_or_name(index_or_name)
        win, widget = self._pop_no_emit(index)
        _checker.call_widget_closed_callback(widget)
        win.closed.emit()

    def _pop_no_emit(self, index: int) -> tuple[SubWindow[_W], _W]:
        main = self._main_window()
        win = self[index]
        widget = win.widget  # get widget here to avoid garbage collection
        main._del_widget_at(self._tab_index(), index)
        main._remove_control_widget(widget)
        if isinstance(sb := win.save_behavior, SaveToPath):
            main._himena_main_window._history_closed.add((sb.path, sb.plugin))
        return win, widget

    def __len__(self) -> int:
        return len(self._main_window()._get_widget_list(self._tab_index()))

    def __iter__(self) -> Iterator[SubWindow[_W]]:
        return iter(
            w[1]._himena_widget
            for w in self._main_window()._get_widget_list(self._tab_index())
        )

    def append(self, sub_window: SubWindow[_W], title: str) -> None:
        """Append a sub-window to the tab area."""
        main = self._main_window()
        interf, front = sub_window._split_interface_and_frontend()
        front._himena_widget = sub_window
        out = main.add_widget(front, self._tab_index(), title)
        if hasattr(interf, "control_widget"):
            main._set_control_widget(front, interf.control_widget())

        main._connect_window_events(sub_window, out)
        sub_window.title = title
        sub_window.state_changed.connect(main._update_context)

        main._set_current_tab_index(self._tab_index())
        if main._himena_main_window._new_widget_behavior is NewWidgetBehavior.TAB:
            main._set_window_state(
                front,
                WindowState.FULL,
                main._himena_main_window._instructions.updated(animate=False),
            )

        main._move_focus_to(front)
        sub_window._alive = True
        return None

    def current(self, default: _T = None) -> SubWindow[_W] | _T:
        """Get the current sub-window or a default value."""
        idx = self.current_index
        if idx is None:
            return default
        try:
            return self[idx]
        except IndexError:
            return default

    @property
    def name(self) -> str:
        """Name of the tab area."""
        return self._main_window()._get_tab_name_list()[self._tab_index()]

    @property
    def current_index(self) -> int | None:
        """Get the index of the current sub-window."""
        return self._main_window()._current_sub_window_index(self._tab_index())

    @current_index.setter
    def current_index(self, index: int) -> None:
        self._main_window()._set_current_sub_window_index(self._tab_index(), index)

    @property
    def title(self) -> str:
        """Title of the tab area."""
        return self._main_window()._tab_title(self._tab_index())

    def add_widget(
        self,
        widget: _W,
        *,
        title: str | None = None,
        auto_size: bool = True,
    ) -> SubWindow[_W]:
        """Add a widget to the sub window.

        Parameters
        ----------
        widget : QtW.QWidget
            Widget to add.
        title : str, optional
            Title of the sub-window. If not given, its name will be automatically
            generated.

        Returns
        -------
        SubWindow
            A sub-window widget. The added widget is available by calling
            `widget` property.
        """
        main = self._main_window()
        sub_window = SubWindow(widget=widget, main_window=main)
        self._process_new_widget(sub_window, title, auto_size)
        main._move_focus_to(sub_window._split_interface_and_frontend()[1])
        _checker.call_theme_changed_callback(widget, main._himena_main_window.theme)
        return sub_window

    def add_function(
        self,
        func: Callable[..., _T],
        *,
        preview: bool = False,
        title: str | None = None,
        show_parameter_labels: bool = True,
        auto_close: bool = True,
        run_async: bool = False,
        result_as: Literal["window", "below", "right"] = "window",
    ) -> ParametricWindow[_W]:
        """Add a function as a parametric sub-window.

        The input function must return a `WidgetDataModel` instance, which can be
        interpreted by the application.

        Parameters
        ----------
        func : function (...) -> WidgetDataModel
            Function that generates a model from the input parameters.
        preview : bool, default False
            If true, the parametric widget will be implemented with a preview toggle
            button, and the preview window will be created when it is enabled.
        title : str, optional
            Title of the parametric window.
        show_parameter_labels : bool, default True
            If true, the parameter labels will be shown in the parametric window.
        auto_close : bool, default True
            If true, close the parametric window after the function call.

        Returns
        -------
        SubWindow
            The sub-window instance that represents the output model.
        """
        sig = inspect.signature(func)
        back_main = self._main_window()
        _is_prev_arg = ParametricWindow._IS_PREVIEWING
        if preview and _is_prev_arg in sig.parameters:
            parameters = [p for p in sig.parameters.values() if p.name != _is_prev_arg]
            sig = sig.replace(parameters=parameters)
        fn_widget = back_main._signature_to_widget(
            sig,
            show_parameter_labels=show_parameter_labels,
            preview=preview,
        )
        param_widget = self.add_parametric_widget(
            fn_widget, func, title=title, preview=preview, run_async=run_async,
            auto_close=auto_close, result_as=result_as,
        )  # fmt: skip
        return param_widget

    def add_parametric_widget(
        self,
        widget: _W,
        callback: Callable | None = None,
        *,
        title: str | None = None,
        preview: bool = False,
        auto_close: bool = True,
        auto_size: bool = True,
        run_async: bool = False,
        result_as: Literal["window", "below", "right"] = "window",
    ) -> ParametricWindow[_W]:
        """Add a custom parametric widget and its callback as a subwindow.

        This method creates a parametric window inside the workspace, so that the
        calculation can be done with the user-defined parameters.

        Parameters
        ----------
        widget : _W
            The parametric widget implemented with `get_params` and/or `get_output`.
        callback : callable, optional
            The callback function that will be called with the parameters set by the
            widget.
        title : str, optional
            Title of the window to manage parameters.
        preview : bool, default False
            If true, the parametric widget will be check for whether preview is enabled
            everytime the parameter changed, and if preview is enabled, a preview window
            is created to show the preview result.
        auto_close : bool, default True
            If true, close the parametric window after the function call.
        auto_size : bool, default True
            If true, the output window will be auto-sized to the size of the parametric
            window.

        Returns
        -------
        ParametricWindow[_W]
            A wrapper containing the backend widget.
        """
        if callback is None:
            if not hasattr(widget, PWPN.GET_OUTPUT):
                raise TypeError(
                    f"Parametric widget must have `{PWPN.GET_OUTPUT}` method if "
                    "callback is not given."
                )
            callback = getattr(widget, PWPN.GET_OUTPUT)
        main = self._main_window()
        widget0 = main._process_parametric_widget(widget)
        param_widget = ParametricWindow(widget0, callback, main_window=main)
        param_widget._auto_close = auto_close
        param_widget._result_as = result_as
        param_widget._run_asynchronously = run_async
        main._connect_parametric_widget_events(param_widget, widget0)
        self._process_new_widget(param_widget, title, auto_size)
        if preview:
            if not (
                hasattr(widget, PWPN.CONNECT_CHANGED_SIGNAL)
                and hasattr(widget, PWPN.IS_PREVIEW_ENABLED)
            ):
                raise TypeError(
                    f"If preview=True, the backend widget {widget!r} must implements "
                    f"methods {PWPN.CONNECT_CHANGED_SIGNAL!r} and "
                    f"{PWPN.IS_PREVIEW_ENABLED!r}"
                )
            param_widget.params_changed.connect(param_widget._widget_preview_callback)
        main._move_focus_to(widget0)
        return param_widget

    def add_layout(self, layout: Layout) -> Layout:
        layout._main_window_ref = weakref.ref(self._main_window())
        return self._add_layout_impl(layout)

    def add_vbox_layout(
        self,
        *,
        margins: Margins[int] | tuple[int, int, int, int] = (0, 0, 0, 0),
        spacing: int = 0,
    ) -> VBoxLayout:
        """Add a vertical box layout to the tab area.

        Parameters
        ----------
        margins : (int, int, int, int) or Margins, optional
            Left, top, right and bottom margins of the layout.
        spacing : int, optional
            Spacing between the widgets.
        """
        main = self._main_window()
        layout = VBoxLayout(main, margins=margins, spacing=spacing)
        return self._add_layout_impl(layout)

    def add_hbox_layout(
        self,
        *,
        margins: Margins[int] | tuple[int, int, int, int] = (0, 0, 0, 0),
        spacing: int = 0,
    ) -> HBoxLayout:
        """Add a horizontal box layout to the tab area.

        Parameters
        ----------
        margins : (int, int, int, int) or Margins, optional
            Left, top, right and bottom margins of the layout.
        spacing : int, optional
            Spacing between the widgets.
        """
        main = self._main_window()
        layout = HBoxLayout(main, margins=margins, spacing=spacing)
        return self._add_layout_impl(layout)

    def _add_layout_impl(self, layout: Layout) -> Layout:
        self._layouts.append(layout)
        layout._reanchor(self._main_window()._area_size())
        return layout

    def _process_new_widget(
        self,
        sub_window: SubWindow[_W],
        title: str | None = None,
        auto_size: bool = True,
    ) -> None:
        """Add, resize, and set the focus to the new widget."""
        main = self._main_window()
        interf, front = sub_window._split_interface_and_frontend()
        if title is None:
            title = getattr(interf, "default_title", _make_title)(len(self))
        out = main.add_widget(front, self._tab_index(), title)
        if hasattr(interf, "control_widget"):
            main._set_control_widget(front, interf.control_widget())

        main._connect_window_events(sub_window, out)
        sub_window.title = title
        sub_window.state_changed.connect(main._update_context)

        main._set_current_tab_index(self._tab_index())
        nwindows = len(self)
        if main._himena_main_window._new_widget_behavior is NewWidgetBehavior.TAB:
            main._set_window_state(
                front,
                WindowState.FULL,
                main._himena_main_window._instructions.updated(animate=False),
            )
        else:
            i_tab = main._current_tab_index()
            main._set_current_sub_window_index(i_tab, len(self) - 1)
            if auto_size:
                left = 4 + 24 * (nwindows % 5)
                top = 4 + 24 * (nwindows % 5)
                if size_hint := sub_window.size_hint():
                    width, height = size_hint
                else:
                    _, _, width, height = sub_window.rect
                sub_window.rect = WindowRect(left, top, width, height)
        _checker.call_widget_added_callback(sub_window.widget)
        sub_window._alive = True
        return None

    def add_data_model(self, model: WidgetDataModel) -> SubWindow[_W]:
        """Add a widget data model as a widget."""
        if not isinstance(model, WidgetDataModel):
            raise TypeError(
                f"input model must be an instance of WidgetDataModel, got {model!r}"
            )
        if len(model.workflow) == 0:
            # this case may happen if this method was programatically called
            wf = ProgrammaticMethod(output_model_type=model.type).construct_workflow()
            model = model.model_copy(update={"workflow": wf})
        ui = self._main_window()._himena_main_window
        widget = ui._pick_widget(model)
        ui.set_status_tip(f"Data model {model.title!r} added.", duration=1)
        sub_win = self.add_widget(widget, title=model.title)
        sub_win._update_from_returned_model(model)
        if rect_factory := model.window_rect_override:
            rect = WindowRect.from_tuple(*rect_factory(sub_win.size))
            sub_win.rect = rect
        if model.extension_default is not None:
            sub_win._extension_default_fallback = model.extension_default
        return sub_win

    def read_file(
        self,
        file_path: PathOrPaths,
        plugin: str | None = None,
    ) -> SubWindow[_W]:
        """Read local file(s) and open as a new sub-window in this tab.

        Parameters
        ----------
        file_path : str or Path or list of them
            Path(s) to the file to read. If a list is given, they will be read as a
            group, not as separate windows.
        plugin : str, optional
            If given, reader provider will be searched with the plugin name. This value
            is usually the full import path to the reader provider function, such as
            `"himena_builtins.io.default_reader_provider"`.

        Returns
        -------
        SubWindow
            The sub-window instance that is constructed based on the return value of
            the reader.
        """
        return self.read_files([file_path], plugin=plugin)[0]

    def read_files(
        self,
        file_paths: PathOrPaths,
        plugin: str | None = None,
    ) -> list[SubWindow[_W]]:
        """Read multiple files and open as new sub-windows in this tab."""
        models = self._paths_to_models(file_paths, plugin=plugin)
        out = [self.add_data_model(model) for model in models]
        ui = self._main_window()._himena_main_window
        if len(out) == 1:
            ui.set_status_tip(f"File opened: {out[0].title}", duration=5)
        elif len(out) > 1:
            _titles = ", ".join(w.title for w in out)
            ui.set_status_tip(f"File opened: {_titles}", duration=5)
        return out

    def _paths_to_models(self, file_paths: PathOrPaths, plugin: str | None = None):
        ins = _providers.ReaderStore.instance()
        file_paths = _norm_paths(file_paths)
        reader_path_sets = [
            (ins.pick(file_path, plugin=plugin), file_path) for file_path in file_paths
        ]
        models = [
            reader.read_and_update_source(file_path)
            for reader, file_path in reader_path_sets
        ]
        ui = self._main_window()._himena_main_window
        ui._recent_manager.append_recent_files(
            [(fp, reader.plugin_str) for reader, fp in reader_path_sets]
        )
        return models

    def read_files_async(
        self,
        file_paths: PathOrPaths,
        plugin: str | None = None,
    ) -> Future:
        """Read multiple files asynchronously and return a future."""
        ui = self._main_window()._himena_main_window
        file_paths = _norm_paths(file_paths)
        future = ui._executor.submit(self._paths_to_models, file_paths, plugin=plugin)
        if len(file_paths) == 1:
            ui.set_status_tip(f"Opening: {file_paths[0].as_posix()}", duration=5)
        else:
            ui.set_status_tip(f"Opening {len(file_paths)} files", duration=5)
        FutureInfo(list[WidgetDataModel]).set(future)  # set info for injection store
        return future

    def save_session(
        self,
        file_path: str | Path,
        save_copies: bool = False,
        allow_calculate: Sequence[str] = (),
    ) -> None:
        """Save the current session to a file."""
        from himena.session import dump_tab_to_zip

        dump_tab_to_zip(
            self, file_path, save_copies=save_copies, allow_calculate=allow_calculate
        )
        return None

    def tile_windows(
        self,
        nrows: int | None = None,
        ncols: int | None = None,
    ) -> None:
        main = self._main_window()
        inst = main._himena_main_window._instructions
        width, height = main._area_size()
        nrows, ncols = _norm_nrows_ncols(nrows, ncols, len(self))

        w = width / ncols
        h = height / nrows
        for i in range(nrows):
            for j in range(ncols):
                idx = i * ncols + j
                if idx >= len(self):
                    break
                x = j * width / ncols
                y = i * height / nrows
                sub = self[idx]
                rect = WindowRect.from_tuple(x, y, w, h)
                main._set_window_rect(sub.widget, rect, inst)
        return None

    def _norm_index_or_name(self, index_or_name: int | str) -> int:
        if isinstance(index_or_name, str):
            for i, w in enumerate(
                self._main_window()._get_widget_list(self._tab_index())
            ):
                if w[0] == index_or_name:
                    index = i
                    break
            else:
                raise ValueError(f"Name {index_or_name!r} not found.")
        else:
            if index_or_name < 0:
                index = len(self) + index_or_name
            else:
                index = index_or_name
        return index

    def _discard_result_stack_ref(self):
        """Discard the result stack reference."""
        self._result_stack_ref = lambda: None
current_index property writable

Get the index of the current sub-window.

layouts property

List of layouts in the tab area.

name property

Name of the tab area.

title property

Title of the tab area.

add_data_model(model)

Add a widget data model as a widget.

Source code in src\himena\widgets\_widget_list.py
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
def add_data_model(self, model: WidgetDataModel) -> SubWindow[_W]:
    """Add a widget data model as a widget."""
    if not isinstance(model, WidgetDataModel):
        raise TypeError(
            f"input model must be an instance of WidgetDataModel, got {model!r}"
        )
    if len(model.workflow) == 0:
        # this case may happen if this method was programatically called
        wf = ProgrammaticMethod(output_model_type=model.type).construct_workflow()
        model = model.model_copy(update={"workflow": wf})
    ui = self._main_window()._himena_main_window
    widget = ui._pick_widget(model)
    ui.set_status_tip(f"Data model {model.title!r} added.", duration=1)
    sub_win = self.add_widget(widget, title=model.title)
    sub_win._update_from_returned_model(model)
    if rect_factory := model.window_rect_override:
        rect = WindowRect.from_tuple(*rect_factory(sub_win.size))
        sub_win.rect = rect
    if model.extension_default is not None:
        sub_win._extension_default_fallback = model.extension_default
    return sub_win
add_function(func, *, preview=False, title=None, show_parameter_labels=True, auto_close=True, run_async=False, result_as='window')

Add a function as a parametric sub-window.

The input function must return a WidgetDataModel instance, which can be interpreted by the application.

Parameters:

Name Type Description Default
func function (...) -> WidgetDataModel

Function that generates a model from the input parameters.

required
preview bool

If true, the parametric widget will be implemented with a preview toggle button, and the preview window will be created when it is enabled.

False
title str

Title of the parametric window.

None
show_parameter_labels bool

If true, the parameter labels will be shown in the parametric window.

True
auto_close bool

If true, close the parametric window after the function call.

True

Returns:

Type Description
SubWindow

The sub-window instance that represents the output model.

Source code in src\himena\widgets\_widget_list.py
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
def add_function(
    self,
    func: Callable[..., _T],
    *,
    preview: bool = False,
    title: str | None = None,
    show_parameter_labels: bool = True,
    auto_close: bool = True,
    run_async: bool = False,
    result_as: Literal["window", "below", "right"] = "window",
) -> ParametricWindow[_W]:
    """Add a function as a parametric sub-window.

    The input function must return a `WidgetDataModel` instance, which can be
    interpreted by the application.

    Parameters
    ----------
    func : function (...) -> WidgetDataModel
        Function that generates a model from the input parameters.
    preview : bool, default False
        If true, the parametric widget will be implemented with a preview toggle
        button, and the preview window will be created when it is enabled.
    title : str, optional
        Title of the parametric window.
    show_parameter_labels : bool, default True
        If true, the parameter labels will be shown in the parametric window.
    auto_close : bool, default True
        If true, close the parametric window after the function call.

    Returns
    -------
    SubWindow
        The sub-window instance that represents the output model.
    """
    sig = inspect.signature(func)
    back_main = self._main_window()
    _is_prev_arg = ParametricWindow._IS_PREVIEWING
    if preview and _is_prev_arg in sig.parameters:
        parameters = [p for p in sig.parameters.values() if p.name != _is_prev_arg]
        sig = sig.replace(parameters=parameters)
    fn_widget = back_main._signature_to_widget(
        sig,
        show_parameter_labels=show_parameter_labels,
        preview=preview,
    )
    param_widget = self.add_parametric_widget(
        fn_widget, func, title=title, preview=preview, run_async=run_async,
        auto_close=auto_close, result_as=result_as,
    )  # fmt: skip
    return param_widget
add_hbox_layout(*, margins=(0, 0, 0, 0), spacing=0)

Add a horizontal box layout to the tab area.

Parameters:

Name Type Description Default
margins (int, int, int, int) or Margins

Left, top, right and bottom margins of the layout.

(0, 0, 0, 0)
spacing int

Spacing between the widgets.

0
Source code in src\himena\widgets\_widget_list.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
def add_hbox_layout(
    self,
    *,
    margins: Margins[int] | tuple[int, int, int, int] = (0, 0, 0, 0),
    spacing: int = 0,
) -> HBoxLayout:
    """Add a horizontal box layout to the tab area.

    Parameters
    ----------
    margins : (int, int, int, int) or Margins, optional
        Left, top, right and bottom margins of the layout.
    spacing : int, optional
        Spacing between the widgets.
    """
    main = self._main_window()
    layout = HBoxLayout(main, margins=margins, spacing=spacing)
    return self._add_layout_impl(layout)
add_parametric_widget(widget, callback=None, *, title=None, preview=False, auto_close=True, auto_size=True, run_async=False, result_as='window')

Add a custom parametric widget and its callback as a subwindow.

This method creates a parametric window inside the workspace, so that the calculation can be done with the user-defined parameters.

Parameters:

Name Type Description Default
widget _W

The parametric widget implemented with get_params and/or get_output.

required
callback callable

The callback function that will be called with the parameters set by the widget.

None
title str

Title of the window to manage parameters.

None
preview bool

If true, the parametric widget will be check for whether preview is enabled everytime the parameter changed, and if preview is enabled, a preview window is created to show the preview result.

False
auto_close bool

If true, close the parametric window after the function call.

True
auto_size bool

If true, the output window will be auto-sized to the size of the parametric window.

True

Returns:

Type Description
ParametricWindow[_W]

A wrapper containing the backend widget.

Source code in src\himena\widgets\_widget_list.py
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
def add_parametric_widget(
    self,
    widget: _W,
    callback: Callable | None = None,
    *,
    title: str | None = None,
    preview: bool = False,
    auto_close: bool = True,
    auto_size: bool = True,
    run_async: bool = False,
    result_as: Literal["window", "below", "right"] = "window",
) -> ParametricWindow[_W]:
    """Add a custom parametric widget and its callback as a subwindow.

    This method creates a parametric window inside the workspace, so that the
    calculation can be done with the user-defined parameters.

    Parameters
    ----------
    widget : _W
        The parametric widget implemented with `get_params` and/or `get_output`.
    callback : callable, optional
        The callback function that will be called with the parameters set by the
        widget.
    title : str, optional
        Title of the window to manage parameters.
    preview : bool, default False
        If true, the parametric widget will be check for whether preview is enabled
        everytime the parameter changed, and if preview is enabled, a preview window
        is created to show the preview result.
    auto_close : bool, default True
        If true, close the parametric window after the function call.
    auto_size : bool, default True
        If true, the output window will be auto-sized to the size of the parametric
        window.

    Returns
    -------
    ParametricWindow[_W]
        A wrapper containing the backend widget.
    """
    if callback is None:
        if not hasattr(widget, PWPN.GET_OUTPUT):
            raise TypeError(
                f"Parametric widget must have `{PWPN.GET_OUTPUT}` method if "
                "callback is not given."
            )
        callback = getattr(widget, PWPN.GET_OUTPUT)
    main = self._main_window()
    widget0 = main._process_parametric_widget(widget)
    param_widget = ParametricWindow(widget0, callback, main_window=main)
    param_widget._auto_close = auto_close
    param_widget._result_as = result_as
    param_widget._run_asynchronously = run_async
    main._connect_parametric_widget_events(param_widget, widget0)
    self._process_new_widget(param_widget, title, auto_size)
    if preview:
        if not (
            hasattr(widget, PWPN.CONNECT_CHANGED_SIGNAL)
            and hasattr(widget, PWPN.IS_PREVIEW_ENABLED)
        ):
            raise TypeError(
                f"If preview=True, the backend widget {widget!r} must implements "
                f"methods {PWPN.CONNECT_CHANGED_SIGNAL!r} and "
                f"{PWPN.IS_PREVIEW_ENABLED!r}"
            )
        param_widget.params_changed.connect(param_widget._widget_preview_callback)
    main._move_focus_to(widget0)
    return param_widget
add_vbox_layout(*, margins=(0, 0, 0, 0), spacing=0)

Add a vertical box layout to the tab area.

Parameters:

Name Type Description Default
margins (int, int, int, int) or Margins

Left, top, right and bottom margins of the layout.

(0, 0, 0, 0)
spacing int

Spacing between the widgets.

0
Source code in src\himena\widgets\_widget_list.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
def add_vbox_layout(
    self,
    *,
    margins: Margins[int] | tuple[int, int, int, int] = (0, 0, 0, 0),
    spacing: int = 0,
) -> VBoxLayout:
    """Add a vertical box layout to the tab area.

    Parameters
    ----------
    margins : (int, int, int, int) or Margins, optional
        Left, top, right and bottom margins of the layout.
    spacing : int, optional
        Spacing between the widgets.
    """
    main = self._main_window()
    layout = VBoxLayout(main, margins=margins, spacing=spacing)
    return self._add_layout_impl(layout)
add_widget(widget, *, title=None, auto_size=True)

Add a widget to the sub window.

Parameters:

Name Type Description Default
widget QWidget

Widget to add.

required
title str

Title of the sub-window. If not given, its name will be automatically generated.

None

Returns:

Type Description
SubWindow

A sub-window widget. The added widget is available by calling widget property.

Source code in src\himena\widgets\_widget_list.py
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
def add_widget(
    self,
    widget: _W,
    *,
    title: str | None = None,
    auto_size: bool = True,
) -> SubWindow[_W]:
    """Add a widget to the sub window.

    Parameters
    ----------
    widget : QtW.QWidget
        Widget to add.
    title : str, optional
        Title of the sub-window. If not given, its name will be automatically
        generated.

    Returns
    -------
    SubWindow
        A sub-window widget. The added widget is available by calling
        `widget` property.
    """
    main = self._main_window()
    sub_window = SubWindow(widget=widget, main_window=main)
    self._process_new_widget(sub_window, title, auto_size)
    main._move_focus_to(sub_window._split_interface_and_frontend()[1])
    _checker.call_theme_changed_callback(widget, main._himena_main_window.theme)
    return sub_window
append(sub_window, title)

Append a sub-window to the tab area.

Source code in src\himena\widgets\_widget_list.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def append(self, sub_window: SubWindow[_W], title: str) -> None:
    """Append a sub-window to the tab area."""
    main = self._main_window()
    interf, front = sub_window._split_interface_and_frontend()
    front._himena_widget = sub_window
    out = main.add_widget(front, self._tab_index(), title)
    if hasattr(interf, "control_widget"):
        main._set_control_widget(front, interf.control_widget())

    main._connect_window_events(sub_window, out)
    sub_window.title = title
    sub_window.state_changed.connect(main._update_context)

    main._set_current_tab_index(self._tab_index())
    if main._himena_main_window._new_widget_behavior is NewWidgetBehavior.TAB:
        main._set_window_state(
            front,
            WindowState.FULL,
            main._himena_main_window._instructions.updated(animate=False),
        )

    main._move_focus_to(front)
    sub_window._alive = True
    return None
current(default=None)

Get the current sub-window or a default value.

Source code in src\himena\widgets\_widget_list.py
185
186
187
188
189
190
191
192
193
def current(self, default: _T = None) -> SubWindow[_W] | _T:
    """Get the current sub-window or a default value."""
    idx = self.current_index
    if idx is None:
        return default
    try:
        return self[idx]
    except IndexError:
        return default
read_file(file_path, plugin=None)

Read local file(s) and open as a new sub-window in this tab.

Parameters:

Name Type Description Default
file_path str or Path or list of them

Path(s) to the file to read. If a list is given, they will be read as a group, not as separate windows.

required
plugin str

If given, reader provider will be searched with the plugin name. This value is usually the full import path to the reader provider function, such as "himena_builtins.io.default_reader_provider".

None

Returns:

Type Description
SubWindow

The sub-window instance that is constructed based on the return value of the reader.

Source code in src\himena\widgets\_widget_list.py
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
def read_file(
    self,
    file_path: PathOrPaths,
    plugin: str | None = None,
) -> SubWindow[_W]:
    """Read local file(s) and open as a new sub-window in this tab.

    Parameters
    ----------
    file_path : str or Path or list of them
        Path(s) to the file to read. If a list is given, they will be read as a
        group, not as separate windows.
    plugin : str, optional
        If given, reader provider will be searched with the plugin name. This value
        is usually the full import path to the reader provider function, such as
        `"himena_builtins.io.default_reader_provider"`.

    Returns
    -------
    SubWindow
        The sub-window instance that is constructed based on the return value of
        the reader.
    """
    return self.read_files([file_path], plugin=plugin)[0]
read_files(file_paths, plugin=None)

Read multiple files and open as new sub-windows in this tab.

Source code in src\himena\widgets\_widget_list.py
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
def read_files(
    self,
    file_paths: PathOrPaths,
    plugin: str | None = None,
) -> list[SubWindow[_W]]:
    """Read multiple files and open as new sub-windows in this tab."""
    models = self._paths_to_models(file_paths, plugin=plugin)
    out = [self.add_data_model(model) for model in models]
    ui = self._main_window()._himena_main_window
    if len(out) == 1:
        ui.set_status_tip(f"File opened: {out[0].title}", duration=5)
    elif len(out) > 1:
        _titles = ", ".join(w.title for w in out)
        ui.set_status_tip(f"File opened: {_titles}", duration=5)
    return out
read_files_async(file_paths, plugin=None)

Read multiple files asynchronously and return a future.

Source code in src\himena\widgets\_widget_list.py
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
def read_files_async(
    self,
    file_paths: PathOrPaths,
    plugin: str | None = None,
) -> Future:
    """Read multiple files asynchronously and return a future."""
    ui = self._main_window()._himena_main_window
    file_paths = _norm_paths(file_paths)
    future = ui._executor.submit(self._paths_to_models, file_paths, plugin=plugin)
    if len(file_paths) == 1:
        ui.set_status_tip(f"Opening: {file_paths[0].as_posix()}", duration=5)
    else:
        ui.set_status_tip(f"Opening {len(file_paths)} files", duration=5)
    FutureInfo(list[WidgetDataModel]).set(future)  # set info for injection store
    return future
save_session(file_path, save_copies=False, allow_calculate=())

Save the current session to a file.

Source code in src\himena\widgets\_widget_list.py
550
551
552
553
554
555
556
557
558
559
560
561
562
def save_session(
    self,
    file_path: str | Path,
    save_copies: bool = False,
    allow_calculate: Sequence[str] = (),
) -> None:
    """Save the current session to a file."""
    from himena.session import dump_tab_to_zip

    dump_tab_to_zip(
        self, file_path, save_copies=save_copies, allow_calculate=allow_calculate
    )
    return None

append_result(item)

Append a new result to the result stack.

Source code in src\himena\widgets\_functions.py
58
59
60
61
62
def append_result(item: dict[str, Any], /) -> None:
    """Append a new result to the result stack."""
    ins = current_instance()
    ins._backend_main_window._append_result(item)
    return None

current_instance(name=None)

Get current instance of the main window (raise if not exists).

Source code in src\himena\widgets\_initialize.py
32
33
34
35
36
def current_instance(name: str | None = None) -> MainWindow[_W]:
    """Get current instance of the main window (raise if not exists)."""
    if name is None:
        name = next(iter(_APP_INSTANCES))
    return _APP_INSTANCES[name][-1]

get_clipboard()

Get the current clipboard data.

Source code in src\himena\widgets\_functions.py
19
20
21
def get_clipboard() -> ClipboardDataModel:
    """Get the current clipboard data."""
    return current_instance().clipboard

notify(text, duration=5.0)

Show a notification popup in the bottom right corner.

Source code in src\himena\widgets\_functions.py
51
52
53
54
55
def notify(text: str, duration: float = 5.0) -> None:
    """Show a notification popup in the bottom right corner."""
    ins = current_instance()
    ins._backend_main_window._show_notification(text, duration)
    return None

remove_instance(name, instance)

Remove the instance from the list.

Source code in src\himena\widgets\_initialize.py
57
58
59
60
61
62
63
64
65
66
def remove_instance(name: str, instance: MainWindow[_W]) -> None:
    """Remove the instance from the list."""
    if name in _APP_INSTANCES:
        instances = _APP_INSTANCES[name]
        if instance in instances:
            instances.remove(instance)
            instance.model_app.destroy(instance.model_app.name)
        if not instances:
            _APP_INSTANCES.pop(name, None)
    return None

set_clipboard(model=None, **kwargs)

set_clipboard(*, text: str | None = None, html: str | None = None, image: Any | None = None, files: list[str | Path] | None = None, interanal_data: Any | None = None) -> None
set_clipboard(model: ClipboardDataModel) -> None

Set data to clipboard.

Source code in src\himena\widgets\_functions.py
39
40
41
42
43
44
45
46
47
48
def set_clipboard(model=None, **kwargs) -> None:
    """Set data to clipboard."""
    ins = current_instance()
    if model is not None:
        if kwargs:
            raise TypeError("Cannot specify both model and keyword arguments")
        ins.clipboard = model
    else:
        ins.set_clipboard(**kwargs)
    return None

set_current_instance(name, instance)

Set the instance as the current one.

Source code in src\himena\widgets\_initialize.py
39
40
41
42
43
44
45
46
def set_current_instance(name: str, instance: MainWindow[_W]) -> None:
    """Set the instance as the current one."""
    if name not in _APP_INSTANCES:
        _APP_INSTANCES[name] = []
    elif instance in _APP_INSTANCES[name]:
        _APP_INSTANCES[name].remove(instance)
    _APP_INSTANCES[name].append(instance)
    return None

set_status_tip(text, duration=10.0)

Set a status tip to the current main window for duration (second).

Source code in src\himena\widgets\_functions.py
10
11
12
13
14
15
16
def set_status_tip(text: str, duration: float = 10.0) -> None:
    """Set a status tip to the current main window for duration (second)."""

    with suppress(Exception):
        ins = current_instance()
        ins.set_status_tip(text, duration=duration)
    return None