class FrameLayout(QFrame): """ This class defines the FrameLayout which emulates the behaviour of the Maya Python API's maya.cmds.frameLayout function. The only difference is that the render setup version also provides a color bar and background color to differentiate layers, collections, and overrides. """ def __init__(self, item, parent): super(FrameLayout, self).__init__(parent) self.item = weakref.ref(item) self.isCollapsed = False self.mainLayout = None self.titleFrame = None self.contentFrame = None self.contentLayout = None self.initFrameLayout() def addWidget(self, widget): self.contentLayout.addWidget(widget) def getWidget(self, index): return self.contentLayout.itemAt( index).widget() if self.contentLayout.count() > index else None def initMainLayout(self): self.mainLayout = QVBoxLayout() self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self.setLayout(self.mainLayout) def initTitleFrame(self): self.titleFrame = TitleFrame(self.item(), self) self.mainLayout.addWidget(self.titleFrame) def initContentFrame(self): self.contentFrame = QFrame() self.contentFrame.setContentsMargins(0, 0, 0, 0) self.contentLayout = QVBoxLayout() self.contentLayout.setContentsMargins(0, 0, 0, 0) self.contentLayout.setSpacing(0) self.contentFrame.setLayout(self.contentLayout) self.mainLayout.addWidget(self.contentFrame) def toggleCollapsed(self): """ When the title frame is clicked, this function is called to toggle the collapsed state """ self.isCollapsed = not self.isCollapsed self.contentFrame.setVisible(self.isCollapsed) self.titleFrame.arrowAndTitle.setArrow(self.isCollapsed) self.update() def initFrameLayout(self): self.setContentsMargins(0, 0, 0, 0) self.initMainLayout() self.initTitleFrame() self.initContentFrame() self.titleFrame.clicked.connect(self.toggleCollapsed)
class GridTransformerDialog(QDialog): def __init__(self, parent=None): super(GridTransformerDialog, self).__init__( parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint) self.setWindowTitle("Grid Transformer") self.setWindowIcon(qta.icon('fa.th')) # dict to store inputs that will get passed to the grid transformer # this gets validated to ensure all the bits of info are in it before # the run button gets enabled self.grid_transformer_inputs = {} self.layout = QVBoxLayout() self.setLayout(self.layout) self._add_inputs() self._add_output() self._add_process() close_layout = QHBoxLayout() close_layout.addStretch() button_close = QPushButton("Close") button_close.clicked.connect(self.close_dialog) close_layout.addWidget(button_close) self.layout.addLayout(close_layout) def _add_inputs(self): inputs_groupbox = QGroupBox("Inputs") inputs_groupbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) inputs_layout = QVBoxLayout() inputs_layout.setSpacing(0) inputs_groupbox.setLayout(inputs_layout) for input_band_name in input_band_names: inputband = GridTransformerInputBand(input_band_name) inputs_layout.addWidget(inputband) inputband.band_selected.connect(self._band_selected) inputband.log_message.connect(self._log_message) self.layout.addWidget(inputs_groupbox) def _band_selected(self, band_details): band_name, filename, band_index = band_details self.grid_transformer_inputs[band_name] = (filename, band_index) self.validate() def validate(self): self.run_button.setEnabled(self._is_valid()) def _is_valid(self) -> bool: '''Is this ready to run, as in has the user specified all inputs needed for the grid transformer''' for band_name in input_band_names: if band_name not in self.grid_transformer_inputs: return False if self.grid_transformer_inputs[band_name] is None: return False if 'output' not in self.grid_transformer_inputs: return False return True def _log_message(self, message): self.log_messages.appendPlainText(message) def _add_output(self): output_groupbox = QGroupBox("Output") output_groupbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) output_layout = QVBoxLayout() output_layout.setSpacing(0) output_groupbox.setLayout(output_layout) output_file_layout = QHBoxLayout() output_file_layout.setSpacing(4) self.output_file_input = QLineEdit() self.output_file_input.textChanged.connect( self._on_output_filename_changed) self.output_file_input.setMinimumWidth(400) self.output_file_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) output_file_layout.addWidget(self.output_file_input) output_layout.addLayout(output_file_layout) self.open_output_file_button = QPushButton() output_file_layout.addWidget(self.open_output_file_button) self.open_output_file_button.setIcon(qta.icon('fa.folder-open')) self.open_output_file_button.setToolTip("Select output file location") self.open_output_file_button.clicked.connect(self._click_open_output) self.layout.addWidget(output_groupbox) def _on_output_filename_changed(self, filename): self.grid_transformer_inputs['output'] = filename self.validate() def _set_output_filename(self, filename): self.output_file_input.setText(filename) self.grid_transformer_inputs['output'] = filename self.validate() def _click_open_output(self): filters = ("GeoTIFF (*.tif *.tiff)") filename, _ = QFileDialog.getSaveFileName( self, f"Select output file", GuiSettings.settings().value(output_folder_settings), filters) if filename is None: return last_open_folder = os.path.dirname(filename) if os.path.exists(last_open_folder): GuiSettings.settings().setValue(output_folder_settings, last_open_folder) self._set_output_filename(filename) def _add_process(self): process_groupbox = QGroupBox("Process") process_groupbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) process_layout = QVBoxLayout() process_layout.setSpacing(0) process_groupbox.setLayout(process_layout) pbar_frame = QFrame() pbar_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) pbar_hbox = QHBoxLayout() pbar_hbox.setContentsMargins(QtCore.QMargins(0, 0, 0, 16)) pbar_hbox.setSpacing(16) # Run and stop buttons hbox = QHBoxLayout() hbox.setSpacing(8) self.run_button = QPushButton() # is only enabled when validation passes self.run_button.setEnabled(False) self.run_button.setText("Run") self.run_button.setFixedWidth(100) run_icon = qta.icon('fa.play', color='green') self.run_button.setIcon(run_icon) self.run_button.clicked.connect(self._click_run) hbox.addWidget(self.run_button) self.stop_button = QPushButton() self.stop_button.setEnabled(False) self.stop_button.setText("Stop") self.stop_button.setFixedWidth(100) stop_icon = qta.icon('fa.stop', color='red') self.stop_button.setIcon(stop_icon) self.stop_button.clicked.connect(self._click_stop) hbox.addWidget(self.stop_button) self.progress_bar = QProgressBar() self.progress_bar.setTextVisible(True) self.progress_bar.setAlignment(QtCore.Qt.AlignCenter) self.progress_bar.setValue(0) self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) pbar_hbox.addLayout(hbox) pbar_hbox.addWidget(self.progress_bar) pbar_frame.setLayout(pbar_hbox) process_layout.addWidget(pbar_frame) self.warning_frame = QFrame() self.warning_frame.setVisible(False) self.warning_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) hbox = QHBoxLayout() warning_icon_widget = qta.IconWidget('fa.warning', color='red') warning_icon_widget.setIconSize(QtCore.QSize(48, 48)) warning_icon_widget.update() hbox.addWidget(warning_icon_widget) warning_label = QLabel( "Grid Transformer did not complete successfully. Please refer to " "log output.") warning_label.setStyleSheet("QLabel { color: red; }") warning_label.setWordWrap(True) warning_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) hbox.addWidget(warning_label) self.warning_frame.setLayout(hbox) process_layout.addWidget(self.warning_frame) self.success_frame = QFrame() self.success_frame.setVisible(False) self.success_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) hbox = QHBoxLayout() success_icon_widget = qta.IconWidget('fa.check', color='green') success_icon_widget.setIconSize(QtCore.QSize(48, 48)) success_icon_widget.update() hbox.addWidget(success_icon_widget) success_label = QLabel("Grid Transformer completed successfully.") success_label.setStyleSheet("QLabel { color: green; }") success_label.setWordWrap(True) success_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) hbox.addWidget(success_label) self.success_frame.setLayout(hbox) process_layout.addWidget(self.success_frame) log_layout = QVBoxLayout() log_layout.setSpacing(4) log_label = QLabel("Log messages") log_label.setStyleSheet("QLabel { color: grey; }") log_layout.addWidget(log_label) self.log_messages = QPlainTextEdit() log_font = QFont("monospace") log_font.setStyleHint(QFont.TypeWriter) self.log_messages.setFont(log_font) self.log_messages.setReadOnly(True) self.log_messages.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # self.log_messages.sizePolicy.setVerticalStretch(1) log_layout.addWidget(self.log_messages) process_layout.addLayout(log_layout) self.layout.addWidget(process_groupbox) def _on_progress(self, progress): self.progress_bar.setValue(int(progress * 100)) def _on_complete(self, successful: bool): self.warning_frame.setVisible(not successful) self.success_frame.setVisible(successful) self.stop_button.setEnabled(False) self.run_button.setEnabled(True) run_time = time.perf_counter() - self.start_time self._log_message( f"Total grid transformation time = {run_time:.2f} sec") self._log_message("\n\n") def _click_run(self): self.warning_frame.setVisible(False) self.success_frame.setVisible(False) self.stop_button.setEnabled(True) self.run_button.setEnabled(False) self.gt_executor = QtGridTransformerThread( self.grid_transformer_inputs) self.gt_executor.progress.connect(self._on_progress) self.gt_executor.message.connect(self._log_message) self.gt_executor.complete.connect(self._on_complete) self.start_time = time.perf_counter() self._log_message("\n\nStarting Grid Transformer process") self.gt_executor.start() def _click_stop(self): if self.gt_executor is not None: self.gt_executor.stop() def close_dialog(self): self.close()
class RunTab(QtWidgets.QWidget): run_checks = QtCore.Signal() def __init__(self, prj: QAXProject): super(RunTab, self).__init__() self.prj = prj self.check_executor = None self.vbox = QtWidgets.QVBoxLayout() self.setLayout(self.vbox) self._add_check_outputs() self._add_process() # final setup self.set_run_stop_buttons_enabled(False) def _add_check_outputs(self): co_groupbox = QGroupBox("Check outputs") co_groupbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) co_layout = QVBoxLayout() co_layout.setSpacing(16) co_groupbox.setLayout(co_layout) self.qajson_spatial_checkbox = QCheckBox( "Include summary spatial output in QAJSON. " "Supports QAX visualisation.") self.qajson_spatial_checkbox.setCheckState( QtCore.Qt.CheckState.Checked) co_layout.addWidget(self.qajson_spatial_checkbox) export_layout = QVBoxLayout() export_layout.setSpacing(4) self.export_spatial_checkbox = QCheckBox( "Export detailed spatial outputs to file. " "Supports visualisation in other geospatial applications.") self.export_spatial_checkbox.stateChanged.connect( self._on_export_spatial_changed) export_layout.addWidget(self.export_spatial_checkbox) output_folder_layout = QHBoxLayout() output_folder_layout.setSpacing(4) output_folder_layout.addSpacerItem(QtWidgets.QSpacerItem(37, 20)) self.output_folder_label = QLabel( "Detailed spatial output folder location:") output_folder_layout.addWidget(self.output_folder_label) self.output_folder_input = QLineEdit() self.output_folder_input.setText( GuiSettings.settings().value("spatial_outputs")) self.output_folder_input.setMinimumWidth(300) self.output_folder_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) output_folder_layout.addWidget(self.output_folder_input) self.open_output_folder_button = QPushButton() output_folder_layout.addWidget(self.open_output_folder_button) self.open_output_folder_button.setIcon(qta.icon('fa.folder-open')) self.open_output_folder_button.setToolTip( f"Select file containing data") self.open_output_folder_button.clicked.connect( self._click_open_spatial_export_folder) export_layout.addLayout(output_folder_layout) co_layout.addLayout(export_layout) self._on_export_spatial_changed() self.vbox.addWidget(co_groupbox) def _click_open_spatial_export_folder(self): output_folder = QFileDialog.getExistingDirectory( self, f"Select folder for spatial outputs", GuiSettings.settings().value("spatial_outputs"), QFileDialog.ShowDirsOnly) if os.path.exists(output_folder): GuiSettings.settings().setValue("spatial_outputs", output_folder) self.output_folder_input.setText(output_folder) def _on_export_spatial_changed(self): is_export = self.export_spatial_checkbox.isChecked() self.output_folder_label.setEnabled(is_export) self.output_folder_input.setEnabled(is_export) self.open_output_folder_button.setEnabled(is_export) def _add_process(self): process_groupbox = QGroupBox("Process") process_groupbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) process_layout = QVBoxLayout() process_layout.setSpacing(0) process_groupbox.setLayout(process_layout) pbar_frame = QFrame() pbar_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) pbar_hbox = QHBoxLayout() pbar_hbox.setContentsMargins(QtCore.QMargins(0, 0, 0, 16)) pbar_hbox.setSpacing(16) # Run and stop buttons hbox = QHBoxLayout() hbox.setSpacing(8) self.run_button = QPushButton() # is only enabled when validation passes self.run_button.setEnabled(False) self.run_button.setText("Run") self.run_button.setToolTip("Start check execution") self.run_button.setFixedWidth(100) run_icon = qta.icon('fa.play', color='green') self.run_button.setIcon(run_icon) self.run_button.clicked.connect(self._click_run) hbox.addWidget(self.run_button) self.stop_button = QPushButton() self.stop_button.setEnabled(False) self.stop_button.setText("Stop") self.stop_button.setToolTip("Stop check execution") self.stop_button.setFixedWidth(100) stop_icon = qta.icon('fa.stop', color='red') self.stop_button.setIcon(stop_icon) self.stop_button.clicked.connect(self._click_stop) hbox.addWidget(self.stop_button) self.progress_bar = QProgressBar() self.progress_bar.setTextVisible(True) self.progress_bar.setAlignment(QtCore.Qt.AlignCenter) self.progress_bar.setValue(0) self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) pbar_hbox.addLayout(hbox) pbar_hbox.addWidget(self.progress_bar) pbar_frame.setLayout(pbar_hbox) process_layout.addWidget(pbar_frame) vbox = QVBoxLayout() vbox.setSpacing(8) vbox.setContentsMargins(QtCore.QMargins(0, 0, 0, 16)) process_layout.addLayout(vbox) hbox = QHBoxLayout() vbox.addLayout(hbox) check_name_label = QLabel("Check:") check_name_label.setFixedWidth(80) hbox.addWidget(check_name_label) self.check_name_text_label = QLabel("n/a") hbox.addWidget(self.check_name_text_label) hbox = QHBoxLayout() vbox.addLayout(hbox) status_name_label = QLabel("Status:") status_name_label.setFixedWidth(80) hbox.addWidget(status_name_label) self.status_name_text_label = QLabel("Not started") hbox.addWidget(self.status_name_text_label) self.warning_frame = QFrame() self.warning_frame.setVisible(False) self.warning_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) hbox = QHBoxLayout() warning_icon_widget = qta.IconWidget('fa.warning', color='red') warning_icon_widget.setIconSize(QtCore.QSize(48, 48)) warning_icon_widget.update() hbox.addWidget(warning_icon_widget) warning_label = QLabel( "Grid Transformer did not complete successfully. Please refer to " "log output.") warning_label.setStyleSheet("QLabel { color: red; }") warning_label.setWordWrap(True) warning_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) hbox.addWidget(warning_label) self.warning_frame.setLayout(hbox) process_layout.addWidget(self.warning_frame) self.success_frame = QFrame() self.success_frame.setVisible(False) self.success_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) hbox = QHBoxLayout() success_icon_widget = qta.IconWidget('fa.check', color='green') success_icon_widget.setIconSize(QtCore.QSize(48, 48)) success_icon_widget.update() hbox.addWidget(success_icon_widget) success_label = QLabel("All checks completed successfully.") success_label.setStyleSheet("QLabel { color: green; }") success_label.setWordWrap(True) success_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) hbox.addWidget(success_label) self.success_frame.setLayout(hbox) process_layout.addWidget(self.success_frame) log_layout = QVBoxLayout() log_layout.setSpacing(4) log_label = QLabel("Log messages") log_label.setStyleSheet("QLabel { color: grey; }") log_layout.addWidget(log_label) self.log_messages = QPlainTextEdit() log_font = QFont("monospace") log_font.setStyleHint(QFont.TypeWriter) self.log_messages.setFont(log_font) self.log_messages.setReadOnly(True) self.log_messages.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) log_layout.addWidget(self.log_messages) process_layout.addLayout(log_layout) self.vbox.addWidget(process_groupbox) def set_run_stop_buttons_enabled(self, is_running: bool) -> NoReturn: if is_running: self.run_button.setEnabled(False) self.stop_button.setEnabled(True) else: self.run_button.setEnabled(True) self.stop_button.setEnabled(False) def _log_message(self, message): self.log_messages.appendPlainText(message) def run_executor(self, check_executor: QtCheckExecutorThread): # we pass the check_executor into the run tab as this is where the UI # components are that will display the execution status. self.set_run_stop_buttons_enabled(True) self._log_message("Check execution started") self.start_time = time.perf_counter() self.check_executor = check_executor self.check_executor.options = self.get_options() self.check_executor.check_tool_started.connect( self._on_check_tool_started) self.check_executor.progress.connect(self._on_progress) self.check_executor.qajson_updated.connect(self._on_qajson_update) self.check_executor.checks_complete.connect(self._on_checks_complete) self.check_executor.status_changed.connect(self._on_status_change) self.check_executor.start() def get_options(self) -> Dict: ''' Gets a list of options based on user entered data. eg; the spatial output specifications. ''' return { CheckOption.spatial_output_qajson: self.qajson_spatial_checkbox.isChecked(), CheckOption.spatial_output_export: self.export_spatial_checkbox.isChecked(), CheckOption.spatial_output_export_location: self.output_folder_input.text() } def _click_run(self): self.run_checks.emit() def _click_stop(self): if self.check_executor is None: logger.warn("Check executor does not exist, cannot stop") return self._log_message("Stopping check execution") self.check_executor.stop() @QtCore.Slot(float) def _on_progress(self, progress): self.progress_bar.setValue(int(progress * 100)) @QtCore.Slot() def _on_qajson_update(self): self.prj.qa_json = self.check_executor.qa_json @QtCore.Slot(object) def _on_check_tool_started(self, tpl): check_tool_name, check_number, total_check_count = tpl self.check_name_text_label.setText("{} ({}/{})".format( check_tool_name, check_number, total_check_count)) @QtCore.Slot() def _on_checks_complete(self): run_time = time.perf_counter() - self.start_time self._log_message( f"Execution time for all checks = {run_time:.2f} sec") self._log_message("\n\n") self.set_run_stop_buttons_enabled(False) self.prj.qa_json = self.check_executor.qa_json @QtCore.Slot(str) def _on_status_change(self, status): self.status_name_text_label.setText(status)