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_)
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)
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 ))
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))
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)
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()
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)