Exemplo n.º 1
0
class MainWindow(QDialog):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.layout_ = QVBoxLayout()
        self.setLayout(self.layout_)
        # override Escape key behavior
        self.reject = self.reset
        self.entry = None

    def show_(self):
        self.update_title()
        self.adjustSize()
        self.show()

    def reset(self):
        self.clear()
        self.hide()

    def clear(self):
        for i in reversed(range(self.layout_.count())):
            self.layout_.takeAt(i).widget().deleteLater()

    def move_(self, x, y):
        try:
            self.move(x, y)
        except OverflowError:
            pass

    def show_entry(self, callback):
        def on_return():
            entry = self.entry.text()
            self.reset()
            callback(entry)

        self.entry = QLineEdit()
        self.entry.returnPressed.connect(on_return)
        self.layout_.addWidget(self.entry)
        self.entry.setFocus()

    def add_label(self, text, sunken=False, raised=False):
        label = QLabel(text, self)
        if sunken:
            label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        elif raised:
            label.setFrameStyle(QFrame.Panel | QFrame.Raised)
        label.setLineWidth(2)
        self.layout_.addWidget(label)

    def update_title(self):
        time_ = time.asctime(time.localtime())
        self.setWindowTitle(time_)
Exemplo n.º 2
0
class PageContent(QWidget):
    def __init__(self):
        super(PageContent, self).__init__()
        PageSignal.changed.connect(self.on_change_page)
        self.settings = get_settings()

        self.layout = QVBoxLayout()
        self.layout.setMargin(0)
        self.layout.addWidget(HomeFeed())
        self.setLayout(self.layout)

    @Slot(QWidget)
    def on_change_page(self, widget):
        current = self.layout.takeAt(0)
        w = current.widget()
        w.setParent(None)
        self.layout.addWidget(widget)
Exemplo n.º 3
0
class _ExecuteTab(QTabWidget):
    """Tab used to execute modules or shell commands on the selected bot."""

    def __init__(self, responses_tab, model):
        """
        :type responses_tab: _ResponsesTab
        """
        super(_ExecuteTab, self).__init__()

        self._model = model
        self._current_layout = None
        self._current_bot = None

        self._layout = QGridLayout()
        self._sub_layout = QVBoxLayout()
        self._module_view = ModuleView(responses_tab)

        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)
        self.set_empty_layout()

    def set_current_bot(self, bot):
        """Sets the connected bot this tab will interact with.

        :type bot: Bot
        """
        self._current_bot = bot

    def _clear_layout(self):
        while self._layout.count():
            child = self._layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

    def set_empty_layout(self):
        """Default layout shown when the user has not yet selected a row."""
        self._current_layout = "Empty"
        self._clear_layout()

        self._layout.addWidget(QLabel("Please select a bot in the table above."), 0, 0)

    def set_module_layout(self, module_name="screenshot"):
        """Sets the layout which can execute modules.

        :type module_name: str
        """
        self._current_layout = "Module"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Module")
        command_type_combobox.addItem("Shell")

        module_label = QLabel("Module name: ")
        module_combobox = QComboBox()

        for module_name in modules.get_names():
            module_combobox.addItem(module_name)

        module_combobox.currentTextChanged.connect(self._on_module_change)
        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)

        self._layout.setColumnStretch(1, 1)
        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(module_label, 1, 0)
        self._layout.addWidget(module_combobox, 1, 1)

        # Module layout
        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)
            input_fields.append(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_combobox.currentText(), input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

        self._on_module_change(module_combobox.currentText())

    def set_shell_layout(self):
        """Sets the layout which can execute shell commands."""
        self._current_layout = "Shell"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Shell")
        command_type_combobox.addItem("Module")

        command_label = QLabel("Command:")
        command_input = QLineEdit()

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)
        run_button.pressed.connect(lambda: self._on_command_run(command_input))

        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(command_label, 1, 0)
        self._layout.addWidget(command_input, 1, 1)

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

    def _on_command_type_change(self, text):
        """Handles the command type combobox change event.

        :type text: str
        """
        if text == "Module":
            self.set_module_layout()
        else:
            self.set_shell_layout()

    def _on_module_change(self, module_name):
        """Handles module combobox changes.

        :type module_name: str
        """
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()
            input_fields.append(input_field)

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_name, input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)

    def display_info(self, text):
        """
        :type text: str
        """
        message_box = QMessageBox()

        message_box.setIcon(QMessageBox.Information)
        message_box.setWindowTitle("Information")
        message_box.setText(text)
        message_box.setStandardButtons(QMessageBox.Ok)
        message_box.exec_()

    def _on_module_run(self, module_name, input_fields):
        """Handles running modules.

        :type module_name: str
        :type input_fields: list
        """
        set_options = []

        for input_field in input_fields:
            set_options.append(input_field.text())

        module = modules.get_module(module_name)

        if not module:
            module = modules.load_module(module_name, self._module_view, self._model)

        successful, options = module.setup(set_options)

        if successful:
            if module_name == "remove_bot":
                code = loaders.get_remove_code(self._current_bot.loader_name)
            elif module_name == "update_bot":
                code = loaders.get_update_code(self._current_bot.loader_name)
            else:
                code = modules.get_code(module_name)

            if not options:
                options = {}

            options["module_name"] = module_name

            self._model.add_command(self._current_bot.uid, Command(
                CommandType.MODULE, code, options
            ))

            self.display_info("Module added to the queue of:\n {}@{}".format(
                self._current_bot.username, self._current_bot.hostname)
            )

    def _on_command_run(self, command_input):
        """Handles running commands.

        :type command_input: QLineEdit
        """
        if command_input.text().strip() == "":
            return

        self._model.add_command(self._current_bot.uid, Command(CommandType.SHELL, command_input.text().encode()))

        command_input.clear()
        self.display_info("Command added to the queue of:\n {}@{}".format(
            self._current_bot.username, self._current_bot.hostname
        ))
Exemplo n.º 4
0
class _BuilderTab(QWidget):
    """Handles the creation of launchers."""

    def __init__(self):
        super(_BuilderTab, self).__init__()

        self._layout = QVBoxLayout()

        host_label = QLabel("Server host (where EvilOSX will connect to):")
        self._host_field = QLineEdit()

        self._layout.addWidget(host_label)
        self._layout.addWidget(self._host_field)

        port_label = QLabel("Server port:")
        self._port_field = QLineEdit()

        self._layout.addWidget(port_label)
        self._layout.addWidget(self._port_field)

        live_label = QLabel("Where should EvilOSX live? (Leave empty for ~/Library/Containers/.<RANDOM>): ")
        self._live_field = QLineEdit()

        self._layout.addWidget(live_label)
        self._layout.addWidget(self._live_field)

        launcher_label = QLabel("Launcher name:")
        self._launcher_combobox = QComboBox()

        for launcher_name in launchers.get_names():
            self._launcher_combobox.addItem(launcher_name)

        self._layout.addWidget(launcher_label)
        self._layout.addWidget(self._launcher_combobox)

        loader_label = QLabel("Loader name:")
        loader_combobox = QComboBox()
        self._loader_layout = QVBoxLayout()

        for loader_name in loaders.get_names():
            loader_combobox.addItem(loader_name)

        self._layout.addWidget(loader_label)
        self._layout.addWidget(loader_combobox)
        loader_combobox.currentTextChanged.connect(self._set_on_loader_change)

        # Dynamically loaded loader layout
        self._layout.addLayout(self._loader_layout)
        self._set_on_loader_change(loader_combobox.currentText())

        self._layout.setContentsMargins(10, 10, 10, 0)
        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)

    def _set_on_loader_change(self, new_text):
        """Handles the loader combobox change event.

        :type new_text: str
        """
        while self._loader_layout.count():
            child = self._loader_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        input_fields = []

        for message in loaders.get_option_messages(new_text):
            input_field = QLineEdit()

            self._loader_layout.addWidget(QLabel(message))
            self._loader_layout.addWidget(input_field)
            input_fields.append(input_field)

        create_button = QPushButton("Create launcher")
        create_button.setMaximumWidth(250)
        create_button.setMinimumHeight(30)
        create_button.pressed.connect(lambda: self._on_create_launcher(
            self._host_field.text(), self._port_field.text(), self._live_field.text(),
            new_text, self._launcher_combobox.currentText(), input_fields
        ))

        self._loader_layout.addWidget(QLabel(""))
        self._loader_layout.addWidget(create_button)

    @staticmethod
    def display_error(text):
        """Displays an error message to the user.

        :type text: str
        """
        message = QMessageBox()

        message.setIcon(QMessageBox.Critical)
        message.setWindowTitle("Error")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    @staticmethod
    def display_info(text):
        """
        :type text: str
        """
        message = QMessageBox()

        message.setIcon(QMessageBox.Information)
        message.setWindowTitle("Information")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    def _on_create_launcher(self, server_host, server_port, program_directory,
                            loader_name, launcher_name, input_fields):
        """Creates the launcher and outputs it to the builds directory.

        :type server_host: str
        :type server_port: int
        :type program_directory: str
        :type loader_name: str
        :type launcher_name: str
        :type input_fields: list
        """
        if not self._host_field.text():
            self.display_error("Invalid host specified.")
        elif not str(self._port_field.text()).isdigit():
            self.display_error("Invalid port specified.")
        else:
            set_options = []

            for field in input_fields:
                set_options.append(field.text())

            loader_options = loaders.get_options(loader_name, set_options)
            loader_options["program_directory"] = program_directory

            stager = launchers.create_stager(server_host, server_port, loader_options)

            launcher_extension, launcher = launchers.generate(launcher_name, stager)
            launcher_path = path.realpath(path.join(
                path.dirname(__file__), path.pardir, path.pardir, "data", "builds", "Launcher-{}.{}".format(
                    str(uuid4())[:6], launcher_extension
                )))

            with open(launcher_path, "w") as output_file:
                output_file.write(launcher)

            self.display_info("Launcher written to: \n{}".format(launcher_path))
Exemplo n.º 5
0
class ViewWidget(QWidget):
    exercise_name_label = None
    exercise_name_line = None

    scroll_area = None
    base_widget = None
    exercises_widget = None

    return_button = None

    add_button = None

    def __init__(self):
        QWidget.__init__(self)

        self.file = ""
        self.setup_widget()

    def setup_widget(self):
        self.exercise_name_label = QLabel("Exercise name:", self)
        self.exercise_name_label.move(5, 5)
        self.exercise_name_label.resize(125, 25)

        self.add_button = QPushButton("Add", self)
        self.add_button.resize(75, 25)
        self.add_button.clicked.connect(self.add_line)

        self.exercise_name_line = QLineEdit(self)
        self.exercise_name_line.move(135, 5)
        self.exercise_name_line.resize(125, 25)

        self.scroll_area = QScrollArea(self)
        self.base_widget = QWidget(self)
        self.scroll_area.setWidget(self.base_widget)

        self.exercises_widget = QVBoxLayout()
        self.exercises_widget.setAlignment(Qt.AlignTop)

        self.base_widget.setLayout(self.exercises_widget)

        self.return_button = QPushButton("Return wo save", self)

    def resizeEvent(self, event):
        self.scroll_area.move(5, 35)
        self.scroll_area.resize(self.width() - 165, self.height() - 40)
        self.add_button.move(self.width() - 160 - 75, 5)
        self.return_button.move(self.width() - 155, 5)
        self.return_button.resize(150, 40)

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    def clear_widget(self):
        while self.exercises_widget.count() > 0:
            self.exercises_widget.takeAt(0)

    def open_exercise_file(self, file: str):
        self.file = file

        with open(self.file, "r") as json_file:
            json_data = json.load(json_file)

            name = json_data['name']

            for data in json_data['exercise']:
                movement = data['name']
                description = data['description']
                time = data['time']

                widget = PanelWidget()
                widget.set_data(movement, description, time)
                widget.remove_signal.connect(self.remove_panel_item)
                widget.move_down_signal.connect(self.move_widget_down)
                widget.move_up_signal.connect(self.move_widget_up)

                self.exercises_widget.addWidget(widget)

            json_file.close()

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

        self.exercise_name_line.setText(name)

    @Slot()
    def add_line(self):
        widget = PanelWidget()
        self.exercises_widget.addWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    @Slot(QWidget)
    def move_widget_down(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind + 1), widget)

    @Slot(QWidget)
    def move_widget_up(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind - 1), widget)

    @Slot(QWidget)
    def remove_panel_item(self, widget: QWidget):
        self.exercises_widget.removeWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)
Exemplo n.º 6
0
class ManagerWindow(MayaQWidgetDockableMixin, QWidget):
    add_spore_clicked = Signal(str)
    remove_spore_clicked = Signal()
    refresh_spore_clicked = Signal()
    close_event = Signal()

    def __init__(self, parent=None):
        super(ManagerWindow, self).__init__(parent=parent)

        self.setWindowTitle('Spore Manager')
        self.setGeometry(50, 50, 400, 550)
        #  self.setMinimumSize(350, 400)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        self.items = []  # list of all item widgets

        self.build_ui()
        self.connect_signals()

    def build_ui(self):
        layout = QGridLayout(self)
        layout.setContentsMargins(5, 5, 5, 5)
        self.setLayout(layout)

        self.name_edt = QLineEdit()
        self.name_edt.setPlaceholderText('Create New')
        self.name_edt.setSizePolicy(QSizePolicy.Preferred,
                                    QSizePolicy.Preferred)
        layout.addWidget(self.name_edt, 0, 0, 1, 1)

        self.add_btn = QPushButton()
        self.add_btn.setIcon(QIcon(QPixmap(':/teAdditive.png')))
        self.add_btn.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
        layout.addWidget(self.add_btn, 0, 1, 1, 1)

        self.refresh_btn = QPushButton()
        self.refresh_btn.setIcon(QIcon(QPixmap(':/teKeyRefresh.png')))
        self.refresh_btn.setSizePolicy(QSizePolicy.Maximum,
                                       QSizePolicy.Preferred)
        layout.addWidget(self.refresh_btn, 0, 2, 1, 1)

        scroll_wdg = QWidget(self)
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setStyleSheet(
            "QScrollArea { background-color: rgb(57,57,57);}")
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setWidget(scroll_wdg)

        self.spore_layout = QVBoxLayout()
        self.spore_layout.setContentsMargins(1, 1, 3, 1)
        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()
        scroll_wdg.setLayout(self.spore_layout)
        layout.addWidget(scroll_area, 1, 0, 1, 3)

        #  self.frame_lay.addWidget(ItemWidget())
        #  layout.addWidget(btn, 0, 0, 1, 1)

    def connect_signals(self):
        self.name_edt.returnPressed.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        self.add_btn.clicked.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        #  self.remove_btn.clicked.connect(self.remove_spore_clicked.emit)
        self.refresh_btn.clicked.connect(self.refresh_spore_clicked.emit)

    def append_item(self, item):
        self.items.append(item)
        self.spore_layout.insertWidget(0, item)

    def remove_item(self, item):
        pass

    #  def clear_items(self):
    #      for item in self.items:
    #          self.spore_layout.removeWidget(item)
    #          self.items.remove(item)
    #          item.delateLater()
    #          del item
    #
    #      self.spore_layout.update()

    def clear_layout(self):
        """ remove all child widgets and layout """

        self.name_edt.setText('')

        del self.items[:]
        while self.spore_layout.count():
            child = self.spore_layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            #  elif child.layout() is not None:
            #      self.clear_layout(child.layout())

        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()

    def closeEvent(self, event):
        self.close_event.emit()

    def hideEvent(self, event):
        self.close_event.emit()
Exemplo n.º 7
0
class PropertyEditor(MayaQWidgetDockableMixin, QWidget):
    """
    This class represents the property editor which displays the selected render setup item's property information.


    Note: The Qt should never called any 'deferred' actions because all the design is based on synchronous notifications
          and any asynchronous events will change the order of execution of these events.
    
          For example when the selection in the Render Setup Window is changed (so the Property Editor must be updated). 
          The delete must be synchronous on the 'unselected' layouts otherwise they will be updated along with selected ones. 
          The two main side effects are that lot of unnecessary processings are triggered (those one the deleted layouts) 
          and the infamous 'C++ already deleted' issue appears because the Data Model & Qt Model objects were deleted 
          but not their corresponding Layout (instance used by the Property Editor to display a render setup object).    
    """

    width = cmds.optionVar(query='workspacesWidePanelInitialWidth')
    PREFERRED_SIZE = QSize(width, 600)
    MINIMUM_SIZE = QSize((width * 0.75), 0)

    def __init__(self, treeView, parent):
        super(PropertyEditor, self).__init__(parent=parent)
        self.needsRebuild = None
        self.itemsToRepopulate = None
        self.rebuildInProgress = None
        self.preferredSize = self.PREFERRED_SIZE
                
        self.treeView = weakref.ref(treeView)
        self.model = weakref.ref(treeView.model())

        self.setWindowTitle(maya.stringTable['y_main.kPropertyEditor' ])
        
        self.scrollArea = PropertyEditorScrollArea(self)
        self.scrollAreaLayout = QVBoxLayout(self)
        self.scrollArea.setLayout(self.scrollAreaLayout)
        self.scrollWidget = QWidget(self)
        self.scrollArea.setWidget(self.scrollWidget)         
        self.scrollArea.setWidgetResizable(True)
        self.scrollWidgetLayout = QVBoxLayout(self)
        self.scrollWidget.setLayout(self.scrollWidgetLayout)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0,0,0,0)
        layout.addWidget(self.scrollArea, 0)
        self.setLayout(layout)

        self.frameLayouts = []
        
        self.setAcceptDrops(True)
        
        self._registered = False
        self._register()

        renderSetupModel.addObserver(self)

    def __del__(self):
        self._unregister()
        
    def _register(self):
        if not self._registered:
            self.model().itemChanged.connect(self.itemChanged)
            self.rebuildInProgress = False
            self.itemsToRepopulate = [] # List of items waiting to be repopulated
            self.needsRebuild = False

            selectionModel = self.treeView().selectionModel()
            selectionModel.selectionChanged.connect(self.selectionChanged)
            
            self._registered = True
        
    def _unregister(self):
        if self._registered:
            self.model().itemChanged.disconnect()
            self.rebuildInProgress = False
            self.itemsToRepopulate = [] # List of items waiting to be repopulated
            self.needsRebuild = False

            selectionModel = self.treeView().selectionModel()

            # The following more obvious implementation:
            #
            # selectionModel.selectionChanged.disconnect(self.selectionChanged)
            #
            # raises
            #
            # // Error: line 0: RuntimeError: file renderSetup\views\propertyEditor\main.py line 103: Failed to disconnect signal selectionChanged(QItemSelection,QItemSelection). //
            #
            # which comes from PySide2's CPython implementation, in file
            # pysidesignal.cpp, function signalInstanceDisconnect().  The
            # argument slot is not recognized, and the function fails.
            # Use old-style disconnection as a workaround.

            selectionModel.disconnect(
                QtCore.SIGNAL(
                    'selectionChanged(QItemSelection,QItemSelection)'),
                self, QtCore.SLOT(
                    'selectionChanged(QItemSelection,QItemSelection)'))
            
            self._registered = False

    def setSizeHint(self, size):
        self.preferredSize = size

    def sizeHint(self):
        return self.preferredSize

    def minimumSizeHint(self):
        return self.MINIMUM_SIZE

    def aboutToDelete(self):
        """Cleanup method to be called immediately before the object is deleted."""
        self._unregister()
        self._clearWidgets()
        # Qt object can take a long time before actually being destroyed
        # => observation of renderSetupModel may remain active (since self is not dead)
        # => explicitly remove observer to avoid receiving unwanted calls
        renderSetupModel.removeObserver(self)

    # Obsolete interface.
    dispose = aboutToDelete

    def renderSetupAdded(self):
        """ RenderSetup node was created """
        self._register()

    def renderSetupPreDelete(self):
        """ RenderSetup node is about to be deleted """
        # Flush the current content to avoid intermediate refreshes
        self._unregister()
        self._clearWidgets()

    def _clearWidgets(self):
        """ Clears the property editor widgets """
        while self.scrollWidgetLayout.count() > 0:
            layoutItem = self.scrollWidgetLayout.takeAt(0)
            # Note: Not obvious but to enforce the layoutItem delete, the parent should be None. 
            #  I would have expected that the takeAt() call did it by default 
            #  as the layoutItem is not anymore in the layout.
            if isinstance(layoutItem, QWidgetItem):
                layoutItem.widget().setParent(None)
            del layoutItem

        self.frameLayouts = []

    def _addItemEditor(self, propertyEditorItem):
        """
        Adds a property editor item type to the end of the layout, also
        keeps track of the control and frameLayout in case there is a data
        change.
        """
        frameLayout = FrameLayout(propertyEditorItem.item(), self)
        self.scrollWidgetLayout.addWidget(frameLayout)
        frameLayout.addWidget(propertyEditorItem)
        self.frameLayouts.append(frameLayout)
        return propertyEditorItem
        
    def _addLayer(self, currentItem):
        """ Adds a property editor layer to the end of the layout. """
        self._addItemEditor(RenderLayer(currentItem, self))

    def _addCollection(self, currentItem):
        """ Adds a property editor collection to the end of the layout. """
        collection = self._addItemEditor(
            collectionFactory.create(currentItem, self))

    def _addOverride(self, currentItem):
        """ Adds a property editor override to the end of the layout. """
        override = self._addItemEditor(Override(currentItem, self))

    @Slot(QStandardItem)
    def itemChanged(self, item):
        """
        When an item in the model changes, update the control and 
        frameLayout that make use of that item (if one exists).
        """
        if not item.isModelDirty():
            # itemChanged was not triggered due to a change to the model.
            # Nothing to do.
            # This is a workaround (see views/proxy/renderSetup.py (modelChanged() callback))
            return
        if item.data(renderSetupRoles.NODE_REBUILD_UI_WHEN_CHANGED):
            self.triggerRebuild()
        else:
            self.triggerRepopulate(item)

    def _getSortedSelectedIndexes(self):
        """ Unfortunately the selected items that are given to us from Qt are not sorted, we need to do this ourselves. """
        selectionModel = self.treeView().selectionModel()
        selectedIndexes = selectionModel.selectedIndexes()
        rootIndex = self.treeView().rootIndex()
        indexStack = []
        indexStack.append(rootIndex)

        # Pre-order traversal of our tree in order to get the preOrderIndex for each item in the tree.
        # Then a sort is applied by preOrderIndex on the selected items to get the sorted selected indexes.
        count = 0
        while(len(indexStack) > 0):
            index = indexStack.pop()
            if index != self.treeView().rootIndex():
                item = self.model().itemFromIndex(index)
                item.preOrderIndex = count
                count = count + 1
            if index is not None and (index.isValid() or index == self.treeView().rootIndex()):
                numRows = self.model().rowCount(index)
                for i in range(numRows):
                    indexStack.append(self.model().index(numRows - i - 1, 0, index))

        sortedSelectedIndices = []
        for i in range(len(selectedIndexes)):
            item = self.model().itemFromIndex(selectedIndexes[i])
            sortedSelectedIndices.append((selectedIndexes[i], item.preOrderIndex))
        sortedSelectedIndices = sorted(sortedSelectedIndices, key=lambda element: element[1]) # Sort by preOrderIndex
        return sortedSelectedIndices

    @Slot(QItemSelection, QItemSelection)
    def selectionChanged(self, selected, deselected):
        """
        On selection changed we lazily regenerate our collection/override/layer 
        controls.
        """
        self.triggerRebuild()

    def triggerRebuild(self):
        self.needsRebuild = True
        if len(self.itemsToRepopulate) == 0 and not self.rebuildInProgress:
            self.rebuildInProgress = True
            QTimer.singleShot(0, lambda: self.rebuild())

    def rebuild(self):
        """ regenerate our collection/override/layer controls. """
        if not self.needsRebuild:
            # early out if we no longer need to rebuild
            # this can happen because rebuild is asynchronous
            return
        self.scrollArea.setVisible(False)
        self._clearWidgets()
        indexes = self._getSortedSelectedIndexes()
        
        creators = {    renderSetup.RENDER_LAYER_TYPE            : self._addLayer,
                        renderSetup.COLLECTION_TYPE              : self._addCollection,
                        renderSetup.RENDER_SETTINGS_TYPE         : self._addCollection,
                        renderSetup.LIGHTS_TYPE                  : self._addCollection,
                        renderSetup.AOVS_TYPE                    : self._addCollection,
                        renderSetup.AOVS_CHILD_COLLECTION_TYPE   : self._addCollection,
                        renderSetup.LIGHTS_CHILD_COLLECTION_TYPE : self._addCollection,
                        renderSetup.RENDER_OVERRIDE_TYPE         : self._addOverride }
        
        for i in range(0, len(indexes)):
            currentIndex = QPersistentModelIndex(indexes[i][0])
            currentItem = self.model().itemFromIndex(currentIndex)
            creators[currentItem.type()](currentItem)

        self.scrollWidgetLayout.addStretch(1)
        self.rebuildInProgress = False
        self.needsRebuild = False
        self.itemsToRepopulate = []
        self.scrollArea.setVisible(True)

    def triggerRepopulate(self, item):
        if not self.rebuildInProgress and not item in self.itemsToRepopulate:
            self.itemsToRepopulate.append(item)
            QTimer.singleShot(0, partial(self.populateFields, item=item))

    def populateFields(self, item):
        # If we need a rebuild while a populateFields request is made, the rebuild is the priority, so rebuild and return.
        if self.needsRebuild:
            return self.rebuild()
        # If another populateFields caused a rebuild then the item will no longer be in the list so return there is no work to do.
        elif not item in self.itemsToRepopulate:
            return

        PropertyEditor.updaters = \
            { renderSetup.RENDER_LAYER_TYPE            : self._updateItem,
              renderSetup.COLLECTION_TYPE              : self._updateCollection,
              renderSetup.RENDER_SETTINGS_TYPE         : self._updateItem,
              renderSetup.LIGHTS_TYPE                  : self._updateItem,
              renderSetup.AOVS_TYPE                    : self._updateItem,
              renderSetup.AOVS_CHILD_COLLECTION_TYPE   : self._updateItem,
              renderSetup.LIGHTS_CHILD_COLLECTION_TYPE : self._updateItem,
              renderSetup.RENDER_OVERRIDE_TYPE         : self._updateItem }
        
        PropertyEditor.updaters[item.type()](item)
        self.itemsToRepopulate.remove(item)

    def _updateItem(self, item):
        for frameLayout in self.frameLayouts:
            if frameLayout.item() is item:
                frameLayout.update()

    def _updateCollection(self, item):
        for frameLayout in self.frameLayouts:
            if frameLayout.item() is item:
                frameLayout.getWidget(0).populateFields()
        self._updateItem(item)
        
    def highlight(self, names):
        if not isinstance(names, set):
            names = set(names)
        def doHighlight():
            collections = (frameLayout.getWidget(0) for frameLayout in self.frameLayouts \
                if frameLayout.item().type() == renderSetup.COLLECTION_TYPE)
            for col in collections:
                col.highlight(names)
        # triggerRepopulate is delayed => highlight must also be delayed to apply only
        # when repopulate is complete
        QTimer.singleShot(0, doHighlight)