Beispiel #1
0
 def test_add_item_to_string_list_widget_causes_container_to_relayout(self):
     from nion.ui import Widgets
     ui = TestUI.UserInterface()
     widget = Widgets.StringListWidget(ui)
     with contextlib.closing(widget):
         canvas_item = widget.content_widget.children[0].canvas_item
         canvas_item.update_layout(Geometry.IntPoint(x=0, y=0),
                                   Geometry.IntSize(width=300, height=200),
                                   immediate=True)
         scroll_area_canvas_item = canvas_item.canvas_items[0].canvas_items[
             0]
         canvas_item.layout_immediate(
             Geometry.IntSize(width=300, height=200))
         # check assumptions
         self.assertEqual(scroll_area_canvas_item.canvas_rect.height, 200)
         self.assertEqual(
             scroll_area_canvas_item.content.canvas_rect.height, 0)
         # add item
         self.assertFalse(canvas_item._needs_layout_for_testing)
         widget.items = ["abc"]
         # self.assertTrue(canvas_item._needs_layout_for_testing)
         # check that column was laid out again
         canvas_item.layout_immediate(Geometry.IntSize(width=300,
                                                       height=200),
                                      force=False)
         self.assertEqual(scroll_area_canvas_item.canvas_rect.height, 200)
         self.assertEqual(
             scroll_area_canvas_item.content.canvas_rect.height, 20)
Beispiel #2
0
    def __init__(self, window: Window.Window, profile: Profile.Profile):
        content_widget = window.ui.create_column_widget()
        super().__init__(content_widget)

        ui = window.ui

        self._tree_model = TreeModel(profile)

        def closed_items_changed(closed_items: typing.Set[str]) -> None:
            profile.closed_items = list(closed_items)

        self._tree_model.on_closed_items_changed = closed_items_changed

        self._tree_selection = Selection.IndexedSelection(
            Selection.Style.multiple)

        projects_list_widget = Widgets.ListWidget(
            ui,
            ProjectTreeCanvasItemDelegate(window, self._tree_model),
            selection=self._tree_selection,
            v_scroll_enabled=False,
            v_auto_resize=True)
        projects_list_widget.wants_drag_events = True
        projects_list_widget.bind_items(
            Binding.PropertyBinding(self._tree_model, "value"))

        projects_section = Widgets.SectionWidget(ui, _("Projects"),
                                                 projects_list_widget)
        projects_section.expanded = True

        content_widget.add(projects_section)

        # configure an observer for watching for project references changes.
        # this serves as the master updater for changes. move to document controller?

        def project_references_changed(item: Observer.ItemValue) -> None:
            # update the tree model.
            project_references = typing.cast(
                typing.Sequence[Profile.ProjectReference], item)
            self._tree_model.update_project_references(project_references)

        oo = Observer.ObserverBuilder()
        oo.source(profile).ordered_sequence_from_array(
            "project_references").collect_list().action_fn(
                project_references_changed)
        self.__projects_model_observer = typing.cast(
            Observer.AbstractItemSource, oo.make_observable())
Beispiel #3
0
 def test_add_item_to_string_list_widget_causes_container_to_relayout(
         self) -> None:
     # ugly type casting
     from nion.ui import Widgets
     ui = TestUI.UserInterface()
     widget = Widgets.StringListWidget(ui)
     with contextlib.closing(widget):
         canvas_item = typing.cast(
             CanvasItem.CanvasItemComposition,
             typing.cast(
                 UserInterface.CanvasWidget,
                 typing.cast(
                     UserInterface.BoxWidget,
                     widget.content_widget).children[0]).canvas_item)
         canvas_item.update_layout(Geometry.IntPoint(x=0, y=0),
                                   Geometry.IntSize(width=300, height=200),
                                   immediate=True)
         scroll_area_canvas_item = typing.cast(
             CanvasItem.ScrollAreaCanvasItem,
             typing.cast(CanvasItem.CanvasItemComposition,
                         canvas_item.canvas_items[0]).canvas_items[0])
         canvas_item.layout_immediate(
             Geometry.IntSize(width=300, height=200))
         # check assumptions
         scroll_canvas_rect = scroll_area_canvas_item.canvas_rect or Geometry.IntRect.empty_rect(
         )
         scroll_content = scroll_area_canvas_item.content
         assert scroll_content
         self.assertEqual(scroll_canvas_rect.height, 200)
         scroll_content_rect = scroll_content.canvas_rect or Geometry.IntRect.empty_rect(
         )
         self.assertEqual(scroll_content_rect.height, 0)
         # add item
         self.assertFalse(canvas_item._needs_layout_for_testing)
         widget.items = ["abc"]
         # self.assertTrue(canvas_item._needs_layout_for_testing)
         # check that column was laid out again
         canvas_item.layout_immediate(Geometry.IntSize(width=300,
                                                       height=200),
                                      force=False)
         scroll_canvas_rect = scroll_area_canvas_item.canvas_rect or Geometry.IntRect.empty_rect(
         )
         scroll_content = scroll_area_canvas_item.content
         assert scroll_content
         scroll_content_rect = scroll_content.canvas_rect or Geometry.IntRect.empty_rect(
         )
         self.assertEqual(scroll_canvas_rect.height, 200)
         self.assertEqual(scroll_content_rect.height, 20)
Beispiel #4
0
    def test_table_widget_handles_pending_updates_in_close(self):
        from nion.ui import Widgets
        ui = TestUI.UserInterface()

        def create_item(item):
            return ui.create_label_widget(item)

        widget = Widgets.TableWidget(ui, create_item)
        list_model = ListModel.ListModel()
        widget.bind_items(Binding.ListBinding(list_model, "items"))
        with contextlib.closing(widget):
            list_model.insert_item(0, "abc")
            list_model.insert_item(1, "abc")
            list_model.remove_item(0)
            self.assertEqual(3, len(widget.pending_queued_tasks))
        self.assertEqual(0, len(widget.pending_queued_tasks))
        widget.run_pending_keyed_tasks()
Beispiel #5
0
    def __init__(self, ui, app):
        super().__init__(ui, _("Preferences"), app=app)

        self.ui = ui
        self.document_controller = self

        self._create_menus()

        properties = dict()
        properties["min-height"] = 400
        properties["min-width"] = 800

        preference_pane_delegates = list()
        preference_pane_delegate_id_ref = [None]

        content_stack = ui.create_stack_widget()

        def change_selection(indexes):
            index = list(indexes)[0]
            assert 0 <= index < len(preference_pane_delegates)
            content_stack.current_index = index
            preference_pane_delegate_id_ref[0] = preference_pane_delegates[
                index].identifier

        selector_list_widget = Widgets.StringListWidget(
            ui, selection_style=Selection.Style.single_or_none)
        selector_list_widget.on_selection_changed = change_selection

        row = self.ui.create_row_widget(properties={
            "min-width": 640,
            "min-height": 320
        })
        selector_column = self.ui.create_column_widget(
            properties={"width": 200})
        selector_row = ui.create_row_widget()
        selector_row.add_spacing(8)
        selector_row.add(selector_list_widget)
        selector_row.add_spacing(8)
        selector_column.add_spacing(8)
        selector_column.add(selector_row)
        selector_column.add_spacing(8)
        content_column = self.ui.create_column_widget()
        content_column.add(content_stack)
        row.add(selector_column)
        row.add(content_column)
        self.content.add(row)

        self.add_button(_("Done"), lambda: True)

        def rebuild():
            content_stack.remove_all()
            preference_pane_delegates.clear()
            preference_pane_delegate_id = preference_pane_delegate_id_ref[0]
            items = list()
            selected_index = 0
            for index, preference_pane_delegate in enumerate(
                    PreferencesManager().preference_pane_delegates):
                preference_pane_delegates.append(preference_pane_delegate)
                content_column_widget = ui.create_column_widget()
                content_column_widget.add_spacing(12)
                content_column_widget.add(
                    preference_pane_delegate.build(ui,
                                                   event_loop=self.event_loop))
                content_column_widget.add_spacing(12)
                content_row_widget = ui.create_row_widget()
                content_row_widget.add_spacing(12)
                content_row_widget.add(content_column_widget)
                content_row_widget.add_spacing(12)
                content_stack.add(content_row_widget)
                items.append(preference_pane_delegate.label)
                if preference_pane_delegate.identifier == preference_pane_delegate_id:
                    selected_index = index
            change_selection({selected_index})
            selector_list_widget.items = items

        self.__preference_pane_delegates_changed_listener = PreferencesManager(
        ).preference_pane_delegates_changed_event.listen(rebuild)

        rebuild()
Beispiel #6
0
    def __init__(self,
                 document_controller: "DocumentController.DocumentController"):
        ui = document_controller.ui
        super().__init__(ui,
                         _("Scripts"),
                         parent_window=document_controller,
                         persistent_id="ScriptsDialog")

        self.ui = ui
        self.document_controller = document_controller

        app = typing.cast(typing.Any,
                          self.document_controller.app)  # trick typing
        self.__profile: typing.Optional[
            Profile.Profile] = app.profile if app else None

        self.script_filter_pattern = "\\.py$"

        self.__cancelled = False

        self.__thread = None

        properties = dict()
        properties["min-height"] = 180
        properties["min-width"] = 540

        self.__output_widget = self.ui.create_text_edit_widget(properties)
        self.__output_widget.set_text_font(
            Panel.Panel.get_monospace_text_font())
        self.__output_widget.set_line_height_proportional(
            Panel.Panel.get_monospace_proportional_line_height())

        self.__message_column = ui.create_column_widget()

        self.__message_column.add(self.__make_cancel_row())

        # load the list of script items
        items = []
        if self.__profile:
            for script_item in self.__profile.script_items:
                if isinstance(script_item, Profile.FileScriptItem):
                    script_list_item = ScriptListItem(str(script_item.path))
                    script_list_item.script_item = script_item
                    items.append(script_list_item)
                elif isinstance(script_item, Profile.FolderScriptItem):
                    folder_list_item = FolderListItem(
                        str(script_item.folder_path))
                    folder_list_item.script_item = script_item
                    folder_list_item.folder_closed = script_item.is_closed
                    items.append(folder_list_item)

        self.__new_path_entries = []

        for item in items:
            if isinstance(item, FolderListItem):
                full_path = item.full_path
                self.__new_path_entries.append(full_path)
                if full_path not in sys.path:
                    sys.path.append(full_path)

        def selected_changed(indexes: typing.AbstractSet[int]) -> None:
            run_button_widget.enabled = len(indexes) == 1

        def add_clicked() -> None:
            assert self.__profile
            add_dir = self.ui.get_persistent_string("import_directory", "")
            file_paths, filter_str, directory = self.get_file_paths_dialog(
                _("Add Scripts"), add_dir, "Python Files (*.py)",
                "Python Files (*.py)")
            self.ui.set_persistent_string("import_directory", directory)
            items = self.scripts_list_widget.items
            for file_path_str in file_paths:
                script_item = Profile.FileScriptItem(
                    pathlib.Path(file_path_str))
                self.__profile.append_script_item(script_item)
                script_list_item = ScriptListItem(file_path_str)
                script_list_item.script_item = script_item
                items.append(script_list_item)
            self.update_scripts_list(items)

        def add_folder_clicked() -> None:
            assert self.__profile
            add_dir = self.ui.get_persistent_string("import_directory", "")
            existing_directory, directory = self.ui.get_existing_directory_dialog(
                _("Add Scripts Folder"), add_dir)
            if existing_directory:
                folder_list_item = FolderListItem(existing_directory)
                folder_list_item.update_content_from_file_system(
                    filter_pattern=self.script_filter_pattern)
                full_path = folder_list_item.full_path
                if full_path not in sys.path:
                    sys.path.append(full_path)
                    self.__new_path_entries.append(full_path)
                items = self.scripts_list_widget.items
                script_item = Profile.FolderScriptItem(
                    pathlib.Path(existing_directory))
                self.__profile.append_script_item(script_item)
                folder_list_item.script_item = script_item
                items.append(folder_list_item)
                self.update_scripts_list(items)
            else:
                self.rebuild_scripts_list()

        def remove_clicked() -> None:
            assert self.__profile
            indexes = list(self.scripts_list_widget.selected_items)
            new_items = []
            for i, item in enumerate(self.scripts_list_widget.items):
                if i not in indexes:
                    new_items.append(item)
                elif item.script_item:
                    self.__profile.remove_script_item(item.script_item)
            self.update_scripts_list(new_items)

        def run_clicked() -> None:
            indexes = self.scripts_list_widget.selected_items
            if len(indexes) == 1:
                script_item = self.scripts_list_widget.items[list(indexes)[0]]
                script_item.check_existence()
                # Use "type" instead of "isinstance" to exclude subclasses from matching
                if type(script_item) is ScriptListItem and script_item.exists:
                    script_path = script_item.full_path
                    self.run_script(script_path)

        def item_selected(index: int) -> bool:
            run_clicked()
            return True

        self.scripts_list_widget = Widgets.ListWidget(
            ui,
            ScriptListCanvasItemDelegate(ui, document_controller,
                                         self.rebuild_scripts_list),
            items=items,
            selection_style=Selection.Style.single_or_none,
            border_color="#888",
            properties={
                "min-height": 200,
                "min-width": 560,
                "size-policy-vertical": "expanding"
            })
        self.scripts_list_widget.on_selection_changed = selected_changed
        self.scripts_list_widget.on_item_selected = item_selected
        self.rebuild_scripts_list()

        add_button_widget = ui.create_push_button_widget(_("Add..."))
        add_button_widget.on_clicked = add_clicked

        add_folder_button_widget = ui.create_push_button_widget(
            _("Add Folder..."))
        add_folder_button_widget.on_clicked = add_folder_clicked

        remove_button_widget = ui.create_push_button_widget(_("Remove"))
        remove_button_widget.on_clicked = remove_clicked

        run_button_widget = ui.create_push_button_widget(_("Run"))
        run_button_widget.on_clicked = run_clicked
        run_button_widget.enabled = False

        list_widget_row = ui.create_row_widget()
        list_widget_row.add_spacing(8)
        list_widget_row.add(self.scripts_list_widget)
        list_widget_row.add_spacing(8)

        close_button_widget = ui.create_push_button_widget(_("Close"))
        close_button_widget.on_clicked = self.request_close

        button_row = ui.create_row_widget()
        button_row.add_spacing(12)
        button_row.add(add_button_widget)
        button_row.add_spacing(4)
        button_row.add(add_folder_button_widget)
        button_row.add_spacing(4)
        button_row.add(remove_button_widget)
        button_row.add_stretch()
        button_row.add(run_button_widget)
        button_row.add_spacing(12)

        select_column = ui.create_column_widget()
        select_column.add_spacing(8)
        select_column.add(list_widget_row)
        select_column.add_spacing(8)
        select_column.add(button_row)
        select_column.add_spacing(8)

        run_column = ui.create_column_widget()
        run_column.add(self.__output_widget)
        run_column.add_spacing(6)
        run_column.add(self.__message_column)

        self.__stack = ui.create_stack_widget()

        self.__stack.add(select_column)
        self.__stack.add(run_column)

        self.content.add(self.__stack)

        self.__sync_events: typing.Set[threading.Event] = set()

        self.__lock = threading.RLock()

        self.__q: typing.Deque[typing.Callable[[], None]] = collections.deque()
        self.__output_queue: typing.Deque[str] = collections.deque()

        self.__is_closed = False
Beispiel #7
0
            def __init__(self, ui, app):
                super().__init__(ui, _("Choose Library"))

                current_item_ref = [None]

                def handle_choose():
                    current_item = current_item_ref[0]
                    if current_item:
                        app.switch_library(current_item)
                        return True
                    return False

                def handle_new():
                    workspace_dir = pose_open_library_dialog_fn()
                    if workspace_dir:
                        items.insert(0, (workspace_dir, datetime.datetime.now()))
                        list_widget.items = items
                        list_widget.set_selected_index(0)
                        app.switch_library(workspace_dir)
                        return True
                    return False

                self.add_button(_("New..."), handle_new)
                self.add_button(_("Other..."), handle_new)
                self.add_button(_("Cancel"), lambda: True)
                self.add_button(_("Choose"), handle_choose)

                path_label = ui.create_label_widget()

                prompt_row = ui.create_row_widget()
                prompt_row.add_spacing(13)
                prompt_row.add(ui.create_label_widget(_("Which library do you want Nion Swift to use?"), properties={"stylesheet": "font-weight: bold"}))
                prompt_row.add_spacing(13)
                prompt_row.add_stretch()

                explanation1_row = ui.create_row_widget()
                explanation1_row.add_spacing(13)
                explanation1_row.add(ui.create_label_widget(_("You can select a library from the list, find another library, or create a new library.")))
                explanation1_row.add_spacing(13)
                explanation1_row.add_stretch()

                explanation2_row = ui.create_row_widget()
                explanation2_row.add_spacing(13)
                explanation2_row.add(ui.create_label_widget(_("The same library will be used the next time you open Nion Swift.")))
                explanation2_row.add_spacing(13)
                explanation2_row.add_stretch()

                def selection_changed(indexes):
                    if len(indexes) == 1:
                        item = items[list(indexes)[0]]
                        current_item_ref[0] = os.path.dirname(item[0])
                        path_label.text = os.path.dirname(item[0])
                    else:
                        current_item_ref[0] = None
                        path_label.text = None

                def stringify_item(item):
                    date_utc = item[1]
                    tz_minutes = Utility.local_utcoffset_minutes(date_utc)
                    date_local = date_utc + datetime.timedelta(minutes=tz_minutes)
                    return str(os.path.basename(os.path.dirname(item[0]))) + " (" + date_local.strftime("%c") + ")"

                def item_selected(index):
                    item = items[index]
                    current_item_ref[0] = os.path.dirname(item[0])
                    path_label.text = os.path.dirname(item[0])
                    handle_choose()
                    self.request_close()

                list_widget = Widgets.StringListWidget(ui, items=items, selection_style=Selection.Style.single_or_none, item_getter=stringify_item, border_color="#888", properties={"min-height": 200, "min-width": 560})
                list_widget.on_selection_changed = selection_changed
                list_widget.on_item_selected = item_selected
                list_widget.on_cancel = self.request_close
                if len(items) > 0:
                    list_widget.set_selected_index(0)

                items_row = ui.create_row_widget()
                items_row.add_spacing(13)
                items_row.add(list_widget)
                items_row.add_spacing(13)
                items_row.add_stretch()

                path_row = ui.create_row_widget()
                path_row.add_spacing(13)
                path_row.add(path_label)
                path_row.add_spacing(13)
                path_row.add_stretch()

                column = ui.create_column_widget()
                column.add_spacing(18)
                column.add(prompt_row)
                column.add_spacing(6)
                column.add(explanation1_row)
                column.add(explanation2_row)
                column.add_spacing(12)
                column.add(items_row)
                column.add_spacing(6)
                column.add(path_row)
                column.add_spacing(6)
                column.add_stretch()
                self.content.add(column)

                self.__list_widget = list_widget
Beispiel #8
0
    def __init__(self, document_controller):
        ui = document_controller.ui
        super().__init__(ui,
                         _("Interactive Dialog"),
                         document_controller.app,
                         persistent_id="ScriptsDialog")

        self.ui = ui
        self.document_controller = document_controller

        self._create_menus()

        self.__cancelled = False

        self.__thread = None

        properties = dict()
        properties["min-height"] = 180
        properties["min-width"] = 540
        properties[
            "stylesheet"] = "background: white; font-family: Monaco, Courier, monospace"

        self.__output_widget = self.ui.create_text_edit_widget(properties)

        self.__message_column = ui.create_column_widget()

        self.__message_column.add(self.__make_cancel_row())

        items = self.ui.get_persistent_object("interactive_scripts_0", list())

        def selected_changed(indexes: AbstractSet[int]) -> None:
            run_button_widget.enabled = len(indexes) == 1

        def add_clicked() -> None:
            add_dir = self.ui.get_persistent_string("import_directory", "")
            file_paths, filter_str, directory = self.get_file_paths_dialog(
                _("Add Scripts"), add_dir, "Python Files (*.py)",
                "Python Files (*.py)")
            self.ui.set_persistent_string("import_directory", directory)
            items.extend(file_paths)
            items.sort()
            list_widget.items = items
            self.ui.set_persistent_object("interactive_scripts_0", items)

        def remove_clicked() -> None:
            indexes = list(list_widget.selected_items)
            for index in sorted(indexes, reverse=True):
                del items[index]
            list_widget.items = items
            self.ui.set_persistent_object("interactive_scripts_0", items)

        def run_clicked() -> None:
            indexes = list_widget.selected_items
            if len(indexes) == 1:
                script_path = items[list(indexes)[0]]
                self.run_script(script_path)

        def item_selected(index: int) -> bool:
            run_clicked()
            return True

        list_widget = Widgets.StringListWidget(
            ui,
            items=items,
            selection_style=Selection.Style.single_or_none,
            border_color="#888",
            properties={
                "min-height": 200,
                "min-width": 560
            })
        list_widget.on_selection_changed = selected_changed
        list_widget.on_item_selected = item_selected

        add_button_widget = ui.create_push_button_widget(_("Add..."))
        add_button_widget.on_clicked = add_clicked

        remove_button_widget = ui.create_push_button_widget(_("Remove"))
        remove_button_widget.on_clicked = remove_clicked

        run_button_widget = ui.create_push_button_widget(_("Run"))
        run_button_widget.on_clicked = run_clicked
        run_button_widget.enabled = False

        list_widget_row = ui.create_row_widget()
        list_widget_row.add_spacing(8)
        list_widget_row.add(list_widget)
        list_widget_row.add_spacing(8)

        close_button_widget = ui.create_push_button_widget(_("Close"))
        close_button_widget.on_clicked = self.request_close

        button_row = ui.create_row_widget()
        button_row.add_spacing(12)
        button_row.add(add_button_widget)
        button_row.add(remove_button_widget)
        button_row.add_stretch()
        button_row.add(run_button_widget)
        button_row.add_spacing(12)

        select_column = ui.create_column_widget()
        select_column.add_spacing(8)
        select_column.add(list_widget_row)
        select_column.add_spacing(8)
        select_column.add(button_row)
        select_column.add_spacing(8)

        run_column = ui.create_column_widget()
        run_column.add(self.__output_widget)
        run_column.add_spacing(6)
        run_column.add(self.__message_column)

        self.__stack = ui.create_stack_widget()

        self.__stack.add(select_column)
        self.__stack.add(run_column)

        self.content.add(self.__stack)

        self.__lock = threading.RLock()

        self.__q = collections.deque()
        self.__output_queue = collections.deque()
Beispiel #9
0
    def __init__(
            self, ui: UserInterface.UserInterface,
            document_controller: DocumentController.DocumentController
    ) -> None:
        content_widget = ui.create_column_widget()
        super().__init__(content_widget)

        document_model = document_controller.document_model

        all_items_controller = CollectionDisplayItemCounter(
            _("All"), None, "all", document_controller)
        persistent_items_controller = CollectionDisplayItemCounter(
            _("Persistent"), None, "persistent", document_controller)
        live_items_controller = CollectionDisplayItemCounter(
            _("Live"), None, "temporary", document_controller)
        latest_items_controller = CollectionDisplayItemCounter(
            _("Latest Session"), None, "latest-session", document_controller)

        self.__item_controllers = [
            all_items_controller, persistent_items_controller,
            live_items_controller, latest_items_controller
        ]

        self.__data_group_controllers: typing.List[
            CollectionDisplayItemCounter] = list()

        collection_selection = Selection.IndexedSelection(
            Selection.Style.single_or_none)

        collections_list_widget = Widgets.ListWidget(
            ui,
            CollectionListCanvasItemDelegate(collection_selection),
            selection=collection_selection,
            v_scroll_enabled=False,
            v_auto_resize=True)
        collections_list_widget.wants_drag_events = True

        def filter_changed(data_group: typing.Optional[DataGroup.DataGroup],
                           filter_id: typing.Optional[str]) -> None:
            if data_group:
                for index, controller in enumerate(
                        collections_list_widget.items):
                    if data_group == controller.data_group:
                        collection_selection.set(index)
                        break
            else:
                if filter_id == "latest-session":
                    collection_selection.set(3)
                elif filter_id == "temporary":
                    collection_selection.set(2)
                elif filter_id == "persistent":
                    collection_selection.set(1)
                else:
                    collection_selection.set(0)

        self.__filter_changed_event_listener = document_controller.filter_changed_event.listen(
            filter_changed)

        def collections_changed(t: str) -> None:
            collections_list_widget.items = [
                all_items_controller,
                persistent_items_controller,
                live_items_controller,
                latest_items_controller,
            ] + self.__data_group_controllers

        all_items_controller.on_title_changed = collections_changed
        persistent_items_controller.on_title_changed = collections_changed
        live_items_controller.on_title_changed = collections_changed
        latest_items_controller.on_title_changed = collections_changed

        def document_model_item_inserted(key: str, value: typing.Any,
                                         before_index: int) -> None:
            if key == "data_groups":
                data_group = value
                controller = CollectionDisplayItemCounter(
                    data_group.title, data_group, None, document_controller)
                self.__data_group_controllers.insert(before_index, controller)
                controller.on_title_changed = collections_changed
                collections_changed(str())

        def document_model_item_removed(key: str, value: typing.Any,
                                        index: int) -> None:
            if key == "data_groups":
                controller = self.__data_group_controllers.pop(index)
                controller.close()
                collections_changed(str())

        self.__document_model_item_inserted_listener = document_model.item_inserted_event.listen(
            document_model_item_inserted)
        self.__document_model_item_removed_listener = document_model.item_removed_event.listen(
            document_model_item_removed)

        data_group, filter_id = document_controller.get_data_group_and_filter_id(
        )
        filter_changed(data_group, filter_id)

        for index, data_group in enumerate(document_model.data_groups):
            document_model_item_inserted("data_groups", data_group, index)

        collections_changed(str())

        def collections_selection_changed(
                indexes: typing.AbstractSet[int]) -> None:
            if len(indexes) == 0:
                controller = collections_list_widget.items[0]
                document_controller.set_filter(controller.filter_id)
            elif len(indexes) == 1:
                controller = collections_list_widget.items[list(indexes)[0]]
                if controller.is_smart_collection:
                    document_controller.set_filter(controller.filter_id)
                    document_controller.set_data_group(None)
                else:
                    document_controller.set_filter(None)
                    document_controller.set_data_group(controller.data_group)

        collections_list_widget.on_selection_changed = collections_selection_changed

        collections_column = ui.create_column_widget()
        collections_column.add(collections_list_widget)

        collections_section = Widgets.SectionWidget(ui, _("Collections"),
                                                    collections_column)
        collections_section.expanded = True

        content_widget.add(collections_section)
        content_widget.add_stretch()

        # for testing
        self._collection_selection = collection_selection
    def __init__(self,
                 document_controller: "DocumentController.DocumentController"):
        ui = document_controller.ui
        super().__init__(ui,
                         _("Interactive Dialog"),
                         parent_window=document_controller,
                         persistent_id="ScriptsDialog")

        self.ui = ui
        self.document_controller = document_controller

        self.script_filter_pattern = "\.py$"

        self._create_menus()

        self.__cancelled = False

        self.__thread = None

        properties = dict()
        properties["min-height"] = 180
        properties["min-width"] = 540

        self.__output_widget = self.ui.create_text_edit_widget(properties)
        self.__output_widget.set_text_font(
            Panel.Panel.get_monospace_text_font())
        self.__output_widget.set_line_height_proportional(
            Panel.Panel.get_monospace_proportional_line_height())

        self.__message_column = ui.create_column_widget()

        self.__message_column.add(self.__make_cancel_row())

        # Load the list of known scripts. Upgrade from old storage system ("interactive_scripts_0")
        items = []
        items_str = self.ui.get_persistent_string("interactive_scripts_1", "")
        if items_str:
            for item_dict in json.loads(items_str):
                items.append(_create_list_item_from_dict(item_dict))
        else:
            items_old = self.ui.get_persistent_object("interactive_scripts_0",
                                                      list())
            for item in items_old:
                items.append(ScriptListItem(item))
        if items:
            items = _build_sorted_scripts_list(items)
            self.ui.set_persistent_string(
                "interactive_scripts_1",
                json.dumps([item.to_dict() for item in items]))

        self.__new_path_entries = []

        for item in items:
            if isinstance(item, FolderListItem):
                full_path = item.full_path
                self.__new_path_entries.append(full_path)
                if not full_path in sys.path:
                    sys.path.append(full_path)

        def selected_changed(indexes: AbstractSet[int]) -> None:
            run_button_widget.enabled = len(indexes) == 1

        def add_clicked() -> None:
            add_dir = self.ui.get_persistent_string("import_directory", "")
            file_paths, filter_str, directory = self.get_file_paths_dialog(
                _("Add Scripts"), add_dir, "Python Files (*.py)",
                "Python Files (*.py)")
            self.ui.set_persistent_string("import_directory", directory)
            items = self.scripts_list_widget.items
            items.extend(
                [ScriptListItem(file_path) for file_path in file_paths])
            self.update_scripts_list(items)

        def add_folder_clicked() -> None:
            add_dir = self.ui.get_persistent_string("import_directory", "")
            existing_directory, directory = self.ui.get_existing_directory_dialog(
                _("Add Scripts Folder"), add_dir)
            if existing_directory:
                new_folder = FolderListItem(existing_directory)
                new_folder.update_content_from_file_system(
                    filter_pattern=self.script_filter_pattern)
                full_path = new_folder.full_path
                if not full_path in sys.path:
                    sys.path.append(full_path)
                    self.__new_path_entries.append(full_path)
                items = self.scripts_list_widget.items
                items.append(new_folder)
                self.update_scripts_list(items)
            else:
                self.rebuild_scripts_list()

        def remove_clicked() -> None:
            indexes = list(self.scripts_list_widget.selected_items)
            new_items = []
            for i, item in enumerate(self.scripts_list_widget.items):
                if not i in indexes:
                    new_items.append(item)
            self.update_scripts_list(new_items)

        def run_clicked() -> None:
            indexes = self.scripts_list_widget.selected_items
            if len(indexes) == 1:
                script_item = self.scripts_list_widget.items[list(indexes)[0]]
                script_item.check_existance()
                # Use "type" instead of "isinstance" to exclude subclasses from matching
                if type(script_item) is ScriptListItem and script_item.exists:
                    script_path = script_item.full_path
                    self.run_script(script_path)

        def item_selected(index: int) -> bool:
            run_clicked()
            return True

        self.scripts_list_widget = Widgets.ListWidget(
            ui,
            ScriptListCanvasItemDelegate(ui, document_controller,
                                         self.rebuild_scripts_list),
            items=items,
            selection_style=Selection.Style.single_or_none,
            border_color="#888",
            properties={
                "min-height": 200,
                "min-width": 560,
                "size-policy-vertical": "expanding"
            })
        self.scripts_list_widget.on_selection_changed = selected_changed
        self.scripts_list_widget.on_item_selected = item_selected
        self.rebuild_scripts_list()

        add_button_widget = ui.create_push_button_widget(_("Add..."))
        add_button_widget.on_clicked = add_clicked

        add_folder_button_widget = ui.create_push_button_widget(
            _("Add Folder..."))
        add_folder_button_widget.on_clicked = add_folder_clicked

        remove_button_widget = ui.create_push_button_widget(_("Remove"))
        remove_button_widget.on_clicked = remove_clicked

        run_button_widget = ui.create_push_button_widget(_("Run"))
        run_button_widget.on_clicked = run_clicked
        run_button_widget.enabled = False

        list_widget_row = ui.create_row_widget()
        list_widget_row.add_spacing(8)
        list_widget_row.add(self.scripts_list_widget)
        list_widget_row.add_spacing(8)

        close_button_widget = ui.create_push_button_widget(_("Close"))
        close_button_widget.on_clicked = self.request_close

        button_row = ui.create_row_widget()
        button_row.add_spacing(12)
        button_row.add(add_button_widget)
        button_row.add_spacing(4)
        button_row.add(add_folder_button_widget)
        button_row.add_spacing(4)
        button_row.add(remove_button_widget)
        button_row.add_stretch()
        button_row.add(run_button_widget)
        button_row.add_spacing(12)

        select_column = ui.create_column_widget()
        select_column.add_spacing(8)
        select_column.add(list_widget_row)
        select_column.add_spacing(8)
        select_column.add(button_row)
        select_column.add_spacing(8)

        run_column = ui.create_column_widget()
        run_column.add(self.__output_widget)
        run_column.add_spacing(6)
        run_column.add(self.__message_column)

        self.__stack = ui.create_stack_widget()

        self.__stack.add(select_column)
        self.__stack.add(run_column)

        self.content.add(self.__stack)

        self.__sync_events = set()

        self.__lock = threading.RLock()

        self.__q = collections.deque()
        self.__output_queue = collections.deque()

        self.__is_closed = False