class MieterwechselView(QWidget, ModifyInfo): # IccView ): """ Enthält ein paar Felder für das aktive ("alte") Mietverhältnis, sowie eine MietverhaeltnisView für das Folge-MV. Die Methode onChange() der MietverhaeltnisView wird umgebogen auf die onChange()-Methode dieser View. """ mieterwechsel_save = Signal() def __init__(self): QWidget.__init__(self) ModifyInfo.__init__(self) self._layout = QGridLayout() self._btnSave = QPushButton() # altes Mietverhältnis self._edAlterMieter = BaseEdit() self._edAlteNettomiete = FloatEdit() self._edAlteNKV = FloatEdit() self._edAlteKaution = IntEdit() self._sdEndeMietverh = SmartDateEdit() self._sdEndeMietverh.textChanged.connect(self.onChange) # neues Mietverhältnis self._neuesMietvhView = MietverhaeltnisView(enableBrowsing=False) self._neuesMietvhView.dataChanged.connect(self.onChange) self._mieterwechsel: XMieterwechsel = None self._createGui() self.connectWidgetsToChangeSlot() def onChange(self): if not self._btnSave.isEnabled(): self.setSaveButtonEnabled() def isChanged(self) -> bool: return super().isChanged() or self._neuesMietvhView.isChanged() def resetChangeFlag(self): super().resetChangeFlag() self._neuesMietvhView.resetChangeFlag() def setSaveButtonEnabled(self, enabled: bool = True): self._btnSave.setEnabled(enabled) def _createGui(self): self._createSaveButton() r = self._createGuiAlterMieter() r += 1 self._layout.addWidget(QLabel(""), r, 0) r += 1 self._createGuiNeuerMieter(r) self.setLayout(self._layout) def _createSaveButton(self): btn = self._btnSave btn.clicked.connect(self.mieterwechsel_save.emit) btn.setFlat(True) btn.setEnabled(False) btn.setToolTip("Änderungen dieser View speichern") icon = QIcon("./images/save_30.png") btn.setIcon(icon) size = QSize(32, 32) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) hbox = QHBoxLayout() hbox.addWidget(btn) self._layout.addLayout(hbox, 0, 0, alignment=Qt.AlignLeft) def _createGuiAlterMieter(self) -> int: l = self._layout r = 1 c = 0 lbl = BaseLabel("Aktuelles / letztes Mietverhältnis ") lbl.setFont(QFont("Arial", 12, QFont.Bold)) l.addWidget(lbl, r, c, 1, 2) r += 1 lbl = QLabel("Mieter: ") l.addWidget(lbl, r, c) self._edAlterMieter.setReadOnly(True) l.addWidget(self._edAlterMieter, r, c + 1) r += 1 lbl = BaseLabel("Nettomiete / NKV : ") l.addWidget(lbl, r, c) c += 1 hbox = QHBoxLayout() self._edAlteNettomiete.setReadOnly(True) self._edAlteNettomiete.setMaximumWidth(100) hbox.addWidget(self._edAlteNettomiete) self._edAlteNKV.setReadOnly(True) self._edAlteNKV.setMaximumWidth(100) hbox.addWidget(self._edAlteNKV) l.addLayout(hbox, r, c, Qt.AlignLeft) c = 0 r += 1 lbl = BaseLabel("Kaution: ") l.addWidget(lbl, r, c) c += 1 self._edAlteKaution.setReadOnly(True) self._edAlteKaution.setMaximumWidth(100) l.addWidget(self._edAlteKaution, r, c) c = 0 r += 1 lbl = QLabel("Ende des Mietverhältnisses: ") l.addWidget(lbl, r, c) self._sdEndeMietverh.setMaximumWidth(100) l.addWidget(self._sdEndeMietverh, r, c + 1) return r def _createGuiNeuerMieter(self, r: int): c = 0 l = self._layout lbl = BaseLabel("Neues Mietverhältnis") lbl.setFont(QFont("Arial", 12, QFont.Bold)) l.addWidget(lbl, r, c) r += 1 l.addWidget(self._neuesMietvhView, r, c, 1, 2) def setMieterwechselData(self, xmieterwechsel: XMieterwechsel): self._mieterwechsel = xmieterwechsel self._setAltesMietverhaeltnisFields(xmieterwechsel.mietverhaeltnis_alt) self._setNeuesMietverhaeltnisFields( xmieterwechsel.mietverhaeltnis_next) self.setSaveButtonEnabled(False) self.resetChangeFlag() def getMieterwechselData(self) -> XMieterwechsel: return self._mieterwechsel def _setAltesMietverhaeltnisFields(self, xmv: XMietverhaeltnis): self._edAlterMieter.setText(xmv.name + ", " + xmv.vorname) self._edAlteNettomiete.setFloatValue(xmv.nettomiete) self._edAlteNKV.setFloatValue(xmv.nkv) if xmv.kaution: self._edAlteKaution.setIntValue(xmv.kaution) self._sdEndeMietverh.setDateFromIsoString(xmv.bis) def _setNeuesMietverhaeltnisFields(self, xmv: XMietverhaeltnis): self._neuesMietvhView.setMietverhaeltnisData(xmv) self._neuesMietvhView.resetChangeFlag() def getMieterwechselDataCopyWithChanges(self) -> XMieterwechsel: xmwcopy = copy.copy(self._mieterwechsel) mvneu: XMietverhaeltnis = self._neuesMietvhView.getMietverhaeltnisCopyWithChanges( ) xmwcopy.mietverhaeltnis_next = mvneu xmwcopy.mietverhaeltnis_alt.bis = self._sdEndeMietverh.getDate() return xmwcopy def applyChanges(self): if self.isChanged(): self._neuesMietvhView.applyChanges() # nur das Ende-Datum ist bei den Daten des alten MV änderbar self._mieterwechsel.mietverhaeltnis_alt.bis = self._sdEndeMietverh.getDate( ) def getModel(self): return None
class BrowserWin(QWidget): def __init__(self, *args, **kwargs): super(BrowserWin, self).__init__(*args, **kwargs) # parent Maya window self.setParent(mainWindow) self.setWindowFlags(Qt.Window) # Window settings self.setWindowTitle('AC_AssetBrowser') # Build window self.mainLayout = QVBoxLayout() self.btnLayout = QHBoxLayout() self.radioLayout = QHBoxLayout() # radio buttons load import self.radioLabel = QLabel("Action: ") self.importRadioBtn = QRadioButton("Import File") self.openRadioBtn = QRadioButton("Open File") self.saveRadioBtn = QRadioButton("Save File") # Find asset directories to load from and populate the drop down self.fileType = QComboBox() self.__populate_list(self.fileType) self.curr_cat = self.fileType.currentText() # list of assets in self.list self.fileList = QListWidget() self.fileList.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__populate_list(self.fileList, directory=os.path.join(DIRECTORY, self.curr_cat)) self.fileName = QLineEdit() self.loadBtn = QPushButton("Load Asset") self.publishBtn = QPushButton("Publish") self.closeBtn = QPushButton("Close") # Add widgets to layouts self.radioLayout.addWidget(self.radioLabel) self.radioLayout.addWidget(self.importRadioBtn) self.radioLayout.addWidget(self.openRadioBtn) self.radioLayout.addWidget(self.saveRadioBtn) self.mainLayout.addLayout(self.radioLayout) self.mainLayout.addWidget(self.fileType) self.mainLayout.addWidget(self.fileList) self.mainLayout.addWidget(self.fileName) self.btnLayout.addWidget(self.loadBtn) self.btnLayout.addWidget(self.publishBtn) self.btnLayout.addWidget(self.closeBtn) self.mainLayout.addLayout(self.btnLayout) self.setLayout(self.mainLayout) # Set state of widgets self.importRadioBtn.toggle() self.fileName.setPlaceholderText("file_name") self.fileName.setEnabled(False) self.publishBtn.setEnabled(False) # Signals self.fileType.currentIndexChanged.connect(self.selectionChanged) self.loadBtn.clicked.connect(self.loadBtnCmd) self.publishBtn.clicked.connect(self.publishBtnCmd) self.closeBtn.clicked.connect(self.closeBtnCmd) self.importRadioBtn.toggled.connect(self.onImportToggled) self.openRadioBtn.toggled.connect(self.onOpenToggled) self.saveRadioBtn.toggled.connect(self.onSaveToggled) def __populate_list(self, destination, directory=DIRECTORY): _dirs = os.listdir(directory) _items = [_dir for _dir in _dirs] return destination.addItems(_items) def selectionChanged(self): self.curr_cat = self.fileType.currentText() self.fileList.clear() self.__populate_list(self.fileList, directory=os.path.join(DIRECTORY, self.curr_cat)) def loadBtnCmd(self): if self.importRadioBtn.isChecked(): selected_files = self.fileList.selectedItems() for _file in selected_files: asset_file = os.path.join(DIRECTORY, self.curr_cat, _file.text()) cmds.file(asset_file, i=True) elif self.openRadioBtn.isChecked(): selected_file = self.fileList.currentItem() asset_file = os.path.join(DIRECTORY, self.curr_cat, selected_file.text()) cmds.file(asset_file, o=True, force=True) else: print("Did you mean to publish this asset?") def publishBtnCmd(self): if self.saveRadioBtn.isChecked() and self.fileName.text() is not None: path_to_save = os.path.join(DIRECTORY, self.curr_cat, self.fileName.text()) cmds.file(rn="{}.ma".format(path_to_save)) cmds.file(save=True) self.fileList.clear() self.__populate_list(self.fileList, directory=os.path.join( DIRECTORY, self.curr_cat)) def closeBtnCmd(self): self.close() def onSaveToggled(self): items = self.fileList.selectedItems() for item in items: item.setSelected(False) self.fileName.setEnabled(not self.fileName.isEnabled()) self.publishBtn.setEnabled(not self.publishBtn.isEnabled()) def onImportToggled(self): if self.importRadioBtn.isChecked(): self.fileList.setSelectionMode(QAbstractItemView.ExtendedSelection) def onOpenToggled(self): if self.openRadioBtn.isChecked(): items = self.fileList.selectedItems() items.pop() for item in items: item.setSelected(False) self.fileList.setSelectionMode(QAbstractItemView.SingleSelection)
class RandomDatasetGenerator(QDialog): logger = logging.getLogger("root.ui.RandomGeneratorWidget") gui_logger = logging.getLogger("GUI") def __init__(self, parent=None): super().__init__(parent=parent, f=Qt.Window) self.setWindowTitle(self.tr("Dataset Generator")) self.last_n_components = 0 self.components = [] # typing.List[RandomGeneratorComponentWidget] self.component_series = [] self.init_ui() self.target = LOESS self.minimum_size_input.setValue(0.02) self.maximum_size_input.setValue(2000.0) self.n_classes_input.setValue(101) self.precision_input.setValue(4) self.file_dialog = QFileDialog(parent=self) self.update_timer = QTimer() self.update_timer.timeout.connect(lambda: self.update_chart(True)) self.cancel_flag = False def init_ui(self): self.setAttribute(Qt.WA_StyledBackground, True) self.main_layout = QGridLayout(self) # self.main_layout.setContentsMargins(0, 0, 0, 0) self.sampling_group = QGroupBox(self.tr("Sampling")) # self.control_group.setFixedSize(400, 160) self.control_layout = QGridLayout(self.sampling_group) self.minimum_size_label = QLabel(self.tr("Minimum Size [μm]")) self.minimum_size_input = QDoubleSpinBox() self.minimum_size_input.setDecimals(2) self.minimum_size_input.setRange(1e-4, 1e6) self.minimum_size_input.setValue(0.0200) self.maximum_size_label = QLabel(self.tr("Maximum Size [μm]")) self.maximum_size_input = QDoubleSpinBox() self.maximum_size_input.setDecimals(2) self.maximum_size_input.setRange(1e-4, 1e6) self.maximum_size_input.setValue(2000.0000) self.control_layout.addWidget(self.minimum_size_label, 0, 0) self.control_layout.addWidget(self.minimum_size_input, 0, 1) self.control_layout.addWidget(self.maximum_size_label, 0, 2) self.control_layout.addWidget(self.maximum_size_input, 0, 3) self.n_classes_label = QLabel(self.tr("N<sub>classes</sub>")) self.n_classes_input = QSpinBox() self.n_classes_input.setRange(10, 1e4) self.n_classes_input.setValue(101) self.precision_label = QLabel(self.tr("Data Precision")) self.precision_input = QSpinBox() self.precision_input.setRange(2, 8) self.precision_input.setValue(4) self.control_layout.addWidget(self.n_classes_label, 1, 0) self.control_layout.addWidget(self.n_classes_input, 1, 1) self.control_layout.addWidget(self.precision_label, 1, 2) self.control_layout.addWidget(self.precision_input, 1, 3) self.component_number_label = QLabel(self.tr("N<sub>components</sub>")) self.component_number_input = QSpinBox() self.component_number_input.setRange(1, 10) self.component_number_input.valueChanged.connect( self.on_n_components_changed) self.preview_button = QPushButton(qta.icon("mdi.animation-play"), self.tr("Preview")) self.preview_button.clicked.connect(self.on_preview_clicked) self.control_layout.addWidget(self.component_number_label, 2, 0) self.control_layout.addWidget(self.component_number_input, 2, 1) self.control_layout.addWidget(self.preview_button, 2, 2, 1, 2) self.main_layout.addWidget(self.sampling_group, 0, 0) self.save_group = QGroupBox(self.tr("Save")) # self.save_group.setFixedHeight(160) self.save_layout = QGridLayout(self.save_group) self.n_samples_label = QLabel(self.tr("N<sub>samples</sub>")) self.n_samples_input = QSpinBox() self.n_samples_input.setRange(100, 100000) self.save_layout.addWidget(self.n_samples_label, 0, 0) self.save_layout.addWidget(self.n_samples_input, 0, 1) self.cancel_button = QPushButton(qta.icon("mdi.cancel"), self.tr("Cancel")) self.cancel_button.setEnabled(False) self.cancel_button.clicked.connect(self.on_cancel_clicked) self.generate_button = QPushButton(qta.icon("mdi.cube-send"), self.tr("Generate")) self.generate_button.clicked.connect(self.on_generate_clicked) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setOrientation(Qt.Horizontal) self.progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.save_layout.addWidget(self.cancel_button, 1, 0) self.save_layout.addWidget(self.generate_button, 1, 1) self.save_layout.addWidget(self.progress_bar, 2, 0, 1, 2) self.main_layout.addWidget(self.save_group, 0, 1) self.param_group = QGroupBox("Random Parameter") # self.param_group.setFixedWidth(400) self.param_layout = QGridLayout(self.param_group) self.main_layout.addWidget(self.param_group, 1, 0) self.preview_group = QGroupBox(self.tr("Preview")) self.chart_layout = QGridLayout(self.preview_group) self.chart = MixedDistributionChart(parent=self, toolbar=False) self.chart_layout.addWidget(self.chart, 0, 0) self.chart.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.main_layout.addWidget(self.preview_group, 1, 1) @staticmethod def to_points(x, y): return [QPointF(x_value, y_value) for x_value, y_value in zip(x, y)] def on_n_components_changed(self, n_components: int): if self.last_n_components < n_components: for component_index in range(self.last_n_components, n_components): component = RandomGeneratorComponentWidget( name=f"AC{component_index+1}") component.value_changed.connect(self.on_value_changed) self.param_layout.addWidget(component, component_index + 1, 0) self.components.append(component) if self.last_n_components > n_components: for i in range(n_components, self.last_n_components): before_component = self.components[i] before_component.value_changed.disconnect( self.on_value_changed) self.param_layout.removeWidget(before_component) # if not hide, the widget will still display on screen before_component.hide() self.components.pop(n_components) self.last_n_components = n_components def on_preview_clicked(self): if self.update_timer.isActive(): self.preview_button.setText(self.tr("Preview")) self.update_timer.stop() self.update_chart() else: self.preview_button.setText(self.tr("Stop")) self.update_timer.start(200) def on_cancel_clicked(self): self.cancel_flag = True def on_generate_clicked(self): if self.update_timer.isActive(): self.preview_button.setText(self.tr("Preview")) self.update_timer.stop() self.update_chart() filename, _ = self.file_dialog.getSaveFileName( self, self.tr("Choose a filename to save the generated dataset"), None, "Microsoft Excel (*.xlsx)") if filename is None or filename == "": return n_samples = self.n_samples_input.value() dataset = self.get_random_dataset(n_samples) # generate samples self.cancel_button.setEnabled(True) self.generate_button.setEnabled(False) format_str = self.tr("Generating {0} samples: %p%").format(n_samples) self.progress_bar.setFormat(format_str) self.progress_bar.setValue(0) def cancel(): self.progress_bar.setFormat(self.tr("Task canceled")) self.progress_bar.setValue(0) self.cancel_button.setEnabled(False) self.generate_button.setEnabled(True) self.cancel_flag = False samples = [] for i in range(n_samples): if self.cancel_flag: cancel() return sample = dataset.get_sample(i) samples.append(sample) progress = (i + 1) / n_samples * 50 self.progress_bar.setValue(progress) QCoreApplication.processEvents() # save file to excel file format_str = self.tr("Writing {0} samples to excel file: %p%").format( n_samples) self.progress_bar.setFormat(format_str) self.progress_bar.setValue(50) wb = openpyxl.Workbook() prepare_styles(wb) ws = wb.active ws.title = self.tr("README") description = \ """ This Excel file was generated by QGrain ({0}). Please cite: Liu, Y., Liu, X., Sun, Y., 2021. QGrain: An open-source and easy-to-use software for the comprehensive analysis of grain size distributions. Sedimentary Geology 423, 105980. https://doi.org/10.1016/j.sedgeo.2021.105980 It contanins n_components + 3 sheets: 1. The first sheet is the random settings which were used to generate random parameters. 2. The second sheet is the generated dataset. 3. The third sheet is random parameters which were used to calulate the component distributions and their mixture. 4. The left sheets are the component distributions of all samples. Artificial dataset Using skew normal distribution as the base distribution of each component (i.e. end-member). Skew normal distribution has three parameters, shape, location and scale. Where shape controls the skewness, location and scale are simliar to that of the Normal distribution. When shape = 0, it becomes Normal distribution. The weight parameter controls the fraction of the component, where fraction_i = weight_i / sum(weight_i). By assigning the mean and std of each parameter, random parameters was generate by the `scipy.stats.truncnorm.rvs` function of Scipy. Sampling settings Minimum size [μm]: {1}, Maximum size [μm]: {2}, N_classes: {3}, Precision: {4}, Noise: {5}, N_samples: {6} """.format(QGRAIN_VERSION, self.minimum_size_input.value(), self.maximum_size_input.value(), self.n_classes_input.value(), self.precision_input.value(), self.precision_input.value()+1, n_samples) def write(row, col, value, style="normal_light"): cell = ws.cell(row + 1, col + 1, value=value) cell.style = style lines_of_desc = description.split("\n") for row, line in enumerate(lines_of_desc): write(row, 0, line, style="description") ws.column_dimensions[column_to_char(0)].width = 200 ws = wb.create_sheet(self.tr("Random Settings")) write(0, 0, self.tr("Parameter"), style="header") ws.merge_cells(start_row=1, start_column=1, end_row=2, end_column=1) write(0, 1, self.tr("Shape"), style="header") ws.merge_cells(start_row=1, start_column=2, end_row=1, end_column=3) write(0, 3, self.tr("Location"), style="header") ws.merge_cells(start_row=1, start_column=4, end_row=1, end_column=5) write(0, 5, self.tr("Scale"), style="header") ws.merge_cells(start_row=1, start_column=6, end_row=1, end_column=7) write(0, 7, self.tr("Weight"), style="header") ws.merge_cells(start_row=1, start_column=8, end_row=1, end_column=9) ws.column_dimensions[column_to_char(0)].width = 16 for col in range(1, 9): ws.column_dimensions[column_to_char(col)].width = 16 if col % 2 == 0: write(1, col, self.tr("Mean"), style="header") else: write(1, col, self.tr("STD"), style="header") for row, comp_params in enumerate(self.target, 2): if row % 2 == 1: style = "normal_dark" else: style = "normal_light" write(row, 0, self.tr("Component{0}").format(row - 1), style=style) for i, key in enumerate(["shape", "loc", "scale", "weight"]): mean, std = comp_params[key] write(row, i * 2 + 1, mean, style=style) write(row, i * 2 + 2, std, style=style) ws = wb.create_sheet(self.tr("Dataset")) write(0, 0, self.tr("Sample Name"), style="header") ws.column_dimensions[column_to_char(0)].width = 24 for col, value in enumerate(dataset.classes_μm, 1): write(0, col, value, style="header") ws.column_dimensions[column_to_char(col)].width = 10 for row, sample in enumerate(samples, 1): if row % 2 == 0: style = "normal_dark" else: style = "normal_light" write(row, 0, sample.name, style=style) for col, value in enumerate(sample.distribution, 1): write(row, col, value, style=style) if self.cancel_flag: cancel() return progress = 50 + (row / n_samples) * 10 self.progress_bar.setValue(progress) QCoreApplication.processEvents() ws = wb.create_sheet(self.tr("Parameters")) write(0, 0, self.tr("Sample Name"), style="header") ws.merge_cells(start_row=1, start_column=1, end_row=2, end_column=1) ws.column_dimensions[column_to_char(0)].width = 24 for i in range(dataset.n_components): write(0, 4 * i + 1, self.tr("Component{0}").format(i + 1), style="header") ws.merge_cells(start_row=1, start_column=4 * i + 2, end_row=1, end_column=4 * i + 5) for j, header_name in enumerate([ self.tr("Shape"), self.tr("Location"), self.tr("Scale"), self.tr("Weight") ]): write(1, 4 * i + 1 + j, header_name, style="header") ws.column_dimensions[column_to_char(4 * i + 1 + j)].width = 16 for row, sample in enumerate(samples, 2): if row % 2 == 1: style = "normal_dark" else: style = "normal_light" write(row, 0, sample.name, style=style) for i, comp_param in enumerate(sample.parameter.components): write(row, 4 * i + 1, comp_param.shape, style=style) write(row, 4 * i + 2, comp_param.loc, style=style) write(row, 4 * i + 3, comp_param.scale, style=style) write(row, 4 * i + 4, comp_param.weight, style=style) if self.cancel_flag: cancel() return progress = 60 + (row / n_samples) * 10 self.progress_bar.setValue(progress) QCoreApplication.processEvents() for i in range(dataset.n_components): ws = wb.create_sheet(self.tr("Component{0}").format(i + 1)) write(0, 0, self.tr("Sample Name"), style="header") ws.column_dimensions[column_to_char(0)].width = 24 for col, value in enumerate(dataset.classes_μm, 1): write(0, col, value, style="header") ws.column_dimensions[column_to_char(col)].width = 10 for row, sample in enumerate(samples, 1): if row % 2 == 0: style = "normal_dark" else: style = "normal_light" write(row, 0, sample.name, style=style) for col, value in enumerate(sample.components[i].distribution, 1): write(row, col, value, style=style) if self.cancel_flag: cancel() return progress = 70 + ( (i * n_samples + row) / n_samples * dataset.n_components) * 30 self.progress_bar.setValue(progress) QCoreApplication.processEvents() wb.save(filename) wb.close() self.progress_bar.setValue(100) self.progress_bar.setFormat(self.tr("Task finished")) self.cancel_button.setEnabled(False) self.generate_button.setEnabled(True) @property def target(self): return [comp.target for comp in self.components] @target.setter def target(self, values): if len(values) != len(self.components): self.component_number_input.setValue(len(values)) for comp, comp_target in zip(self.components, values): comp.blockSignals(True) comp.target = comp_target comp.blockSignals(False) self.update_chart() def get_random_sample(self): dataset = self.get_random_dataset(n_samples=1) sample = dataset.get_sample(0) sample.name = self.tr("Artificial Sample") return sample def get_random_mean(self): dataset = self.get_random_dataset(n_samples=1) random_setting = RandomSetting(self.target) sample = dataset.get_sample_by_params(self.tr("Artificial Sample"), random_setting.mean_param) return sample def get_random_dataset(self, n_samples): min_μm = self.minimum_size_input.value() max_μm = self.maximum_size_input.value() n_classes = self.n_classes_input.value() if min_μm == max_μm: return if min_μm > max_μm: min_μm, max_μm = max_μm, min_μm precision = self.precision_input.value() noise = precision + 1 dataset = get_random_dataset(target=self.target, n_samples=n_samples, min_μm=min_μm, max_μm=max_μm, n_classes=n_classes, precision=precision, noise=noise) return dataset def on_value_changed(self): self.update_chart() def update_chart(self, random=False): if not random: sample = self.get_random_mean() else: sample = self.get_random_sample() self.chart.show_model(sample.view_model) def closeEvent(self, event): if self.cancel_button.isEnabled(): self.on_cancel_clicked() event.accept()
class WorkspaceConfigurationEditor(QDialog): def __init__(self, workpsaceConfiguration, mainApplicatoin, switch=False): super(WorkspaceConfigurationEditor, self).__init__() self.setWindowIcon(QIcon(main.resource_path("resources/app_icon"))) self.workspaceConfiguration: WorkspaceConfiguration = workpsaceConfiguration self.mainApplication = mainApplicatoin self.switch = switch self.workspaceDirectory = None self.setWindowTitle("Choose a workspace directory") self.setStyleSheet("background-color: #2D2D30; color: white;") self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) #self.setWindowFlag(Qt.WindowStaysOnTopHint, True) self.setFixedSize(500, 150) self.comboBox = QComboBox() self.updateComboBox() self.btnOpen = QPushButton("Open workspace") self.cbDefault = QCheckBox("Set as default workspace") self.btnBrowse = QPushButton("Browse...") self.vbox = QVBoxLayout() self.hbox = QHBoxLayout() self.hbox2 = QHBoxLayout() self.hbox2.addWidget(self.comboBox, 4) self.hbox2.addWidget(self.btnBrowse, 1) self.vbox.addLayout(self.hbox2) self.hbox.addWidget(self.cbDefault) self.hbox.addWidget(self.btnOpen) self.vbox.addSpacing(20) self.vbox.addLayout(self.hbox) self.setLayout(self.vbox) self.btnOpen.clicked.connect(self.loadWorkpace) self.btnBrowse.clicked.connect(self.browseWorkspace) self.btnOpen.setFocus() if len(self.workspaceConfiguration.getWorkspaces()) == 0: self.btnOpen.setEnabled(False) def updateComboBox(self): self.comboBox.clear() self.comboBox.addItems(list(self.workspaceConfiguration.getWorkspaces())) if self.workspaceConfiguration.defaultWorkspace: self.comboBox.setCurrentText(self.workspaceConfiguration.getDefaultWorkspace()) def browseWorkspace(self): directory = QFileDialog.getExistingDirectory() if directory == "": return wsname = directory[directory.rindex(os.path.sep) + 1:] regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if ' ' in directory or regex.search(wsname): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Workspace path/name cannot contain whitespace special characters.") msg.setWindowTitle("Workspace creation error") msg.exec_() return self.workspaceConfiguration.addWorkspace(directory) self.updateComboBox() if not self.btnOpen.isEnabled(): self.btnOpen.setEnabled(True) def loadWorkpace(self): if self.cbDefault.isChecked(): self.workspaceConfiguration.setDefaultWorkspace(self.comboBox.currentText()) if not self.switch: success = self.mainApplication.openWorkspaceAction(self.comboBox.currentText()) if not success: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Failed to load '{}'.".format(self.comboBox.currentText())) msg.setWindowTitle("Failed to load workspace.") msg.exec_() self.workspaceConfiguration.removeWorkspace(self.comboBox.currentText()) self.updateComboBox() return else: self.workspaceDirectory = self.comboBox.currentText() self.accept() def closeEvent(self, arg__1): self.reject()
class Login(QWidget): device = None deviceList = [] def __init__(self): QWidget.__init__(self) self.theme = 2 screen = QDesktopWidget() logging.info("screen size:" + str(screen.width()) + "," + str(screen.height())) self.m_width = screen.height() / 2 self.m_height = screen.height() / 2 self.resize(self.m_width, self.m_height) # self.setFixedSize(self.m_width, self.m_height) size = self.geometry() self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) # delete title box self.setWindowFlags(Qt.FramelessWindowHint) # self.setAttribute(Qt.WA_TranslucentBackground) logging.info("login widget size:" + str(self.size())) self.bitmap = QBitmap(self.size()) painter = QPainter(self.bitmap) painter.fillRect(self.rect(), Qt.white) painter.setBrush(QColor(0, 0, 0)) painter.setRenderHint(QPainter.Antialiasing, True) painter.drawRoundedRect(self.rect(), 10, 10) self.setMask(self.bitmap) self.layout = QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, self.m_height / 15) self.layout.setSpacing(self.m_height / 15) self.topframe = QFrame(self) self.topframe.setObjectName("topframe") if self.theme == 1: self.topframe.setStyleSheet( "#topframe{background-color:qlineargradient(" "spread:pad," "x1:0,y1:0,x2:1,y2:1," "stop:0 #5efce8," "stop:1 #736efe);}") elif self.theme == 2: self.topframe.setStyleSheet( "#topframe{background-color:qlineargradient(" "spread:pad," "x1:0,y1:0,x2:1,y2:0," "stop:0 #3a6186," "stop:1 #89253e);}") elif self.theme == 3: self.topframe.setStyleSheet( "#topframe{background-color:qlineargradient(" "spread:pad," "x1:0,y1:0,x2:0,y2:1," "stop:0 #19547b," "stop:1 #ffd89b);}") else: self.topframe.setStyleSheet( "#topframe{background-color:qlineargradient(" "spread:pad," "x1:0,y1:0,x2:1,y2:1," "stop:0 #ff8177," "stop:1 #b12a5b);}") self.layout.addWidget(self.topframe) # setup checkbox self.combobox = QComboBox(self) self.combobox.setObjectName("combobox") self.combobox.setFixedSize(self.m_width / 1.5, self.m_height / 10) self.combobox.setStyleSheet( "QComboBox{border: 2px solid gray;" "border-radius:5px;" "background-color:rgb(255, 255, 255);" "color:rgb(0, 0, 0);" "padding: 1px 20px;}" "QComboBox:drop-down{subcontrol-origin: padding;" "subcontrol-position: top right;" "width: 50px;border-left-style:solid;" "border-top-right-radius: 3px;" "border-bottom-right-radius: 3px;" "border-left: 2px solid gray;" "background-color: rgba(100, 25, 100, 0);}" "QComboBox:down-arrow{border-image:url(icon/arrow-1.png);}" "QComboBox:item:selected{background: rgb(232, 241, 250);color: rgb(2, 65, 132);}" "QStyledItemDelegate{border: 100px solid rgb(161,161,161);}") # self.combobox.move(200, 200) self.layout.addWidget(self.combobox, 0, Qt.AlignHCenter) # setup login button self.loginbtn = QPushButton("ENTER", self) self.loginbtn.setObjectName("loginbtn") self.loginbtn.setFixedSize(self.m_width / 1.5, self.m_height / 10) self.loginbtn.setContentsMargins(200, 20, 20, 20) self.loginbtn.clicked.connect(self.login_event) self.loginbtn.setStyleSheet( "QPushButton{border-radius:%dpx;" "background-color:#89253e;" "color:rgb(0, 0, 0);}" "QPushButton:hover{color:rgb(0, 255, 0);}" % (self.m_height / 20)) self.layout.addWidget(self.loginbtn, 0, Qt.AlignHCenter) # setup exit button self.exitbtn = QPushButton(self) # self.exitbtn.setText("Close") self.exitbtn.setToolTip("Close the widget") self.exitbtn.setFixedSize(40, 40) self.exitbtn.setIcon(QIcon("icon/close.png")) self.exitbtn.setIconSize(QSize(40, 40)) self.exitbtn.clicked.connect(self.exit_event) logging.info("topframesize:" + str(self.topframe.size())) self.exitbtn.move(self.width() - 40, 0) # self.exitbtn.setGeometry(self.topframe.width()-40, 0, 100, 40) self.exitbtn.setStyleSheet("background-color: #600") self.exitbtn.setStyleSheet("background-color: transparent") self.exitbtn.isEnabled() self.logoImage = QPixmap("icon/silabslogo.png") self.logo = self.logoImage.scaled(self.m_width / 5, self.m_height / 5, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.logoLable = QLabel(self) self.logoLable.setObjectName("logoLable") self.logoLable.setAlignment(Qt.AlignCenter) self.logoLable.setPixmap(self.logo) self.logoLable.setFixedSize(self.m_width / 4, self.m_height / 4) # self.logo.setScaledContents(True) self.logoLable.setStyleSheet("#logoLable{border: 2px solid gray;" "border-radius:75px;" "background-color:rgb(100, 100, 100);" "color:rgb(0, 0, 0);}") self.logoLable.move(self.m_width / 2 - self.m_width / 8, self.m_height / 6) self.m_drag = False self.m_DragPosition = 0 self.MainWindow = 0 def login_event(self): if self.combobox.currentText() == "None": return self.hide() self.device = self.deviceList[self.combobox.currentIndex()] self.device.open() command = '\xAA' + '\xF2' + '\x01' + '\x00' play_thread = USBWriteThread(self.device, command) play_thread.start() self.MainWindow = OscGui(self.device) self.MainWindow.show() def exit_event(self): exit(0) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.m_drag = True self.m_DragPosition = event.globalPos() - self.pos() event.accept() self.setCursor(QCursor(Qt.OpenHandCursor)) def mouseMoveEvent(self, event): if Qt.LeftButton and self.m_drag: self.move(event.globalPos() - self.m_DragPosition) event.accept() def mouseReleaseEvent(self, event): self.m_drag = False self.setCursor(QCursor(Qt.ArrowCursor))
class PurchaseGroup(QGroupBox, Generic[TransactionItemType]): def __init__( self, item: TransactionItemType, recruiter: UnitTransactionFrame[TransactionItemType], ) -> None: super().__init__() self.item = item self.recruiter = recruiter self.setProperty("style", "buy-box") self.setMaximumHeight(72) self.setMinimumHeight(36) layout = QHBoxLayout() self.setLayout(layout) self.sell_button = QPushButton("-") self.sell_button.setProperty("style", "btn-sell") self.sell_button.setDisabled(not recruiter.enable_sale(item)) self.sell_button.setMinimumSize(16, 16) self.sell_button.setMaximumSize(16, 16) self.sell_button.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ) self.sell_button.clicked.connect( lambda: self.recruiter.recruit_handler(RecruitType.SELL, self.item) ) self.amount_bought = QLabel() self.amount_bought.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ) self.buy_button = QPushButton("+") self.buy_button.setProperty("style", "btn-buy") self.buy_button.setDisabled(not recruiter.enable_purchase(item)) self.buy_button.setMinimumSize(16, 16) self.buy_button.setMaximumSize(16, 16) self.buy_button.clicked.connect( lambda: self.recruiter.recruit_handler(RecruitType.BUY, self.item) ) self.buy_button.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) layout.addWidget(self.sell_button) layout.addWidget(self.amount_bought) layout.addWidget(self.buy_button) self.update_state() @property def pending_units(self) -> int: return self.recruiter.pending_delivery_quantity(self.item) def update_state(self) -> None: self.buy_button.setEnabled(self.recruiter.enable_purchase(self.item)) self.buy_button.setToolTip( self.recruiter.purchase_tooltip(self.buy_button.isEnabled()) ) self.sell_button.setEnabled(self.recruiter.enable_sale(self.item)) self.sell_button.setToolTip( self.recruiter.sell_tooltip(self.sell_button.isEnabled()) ) self.amount_bought.setText(f"<b>{self.pending_units}</b>")
class MietverhaeltnisView(QWidget, ModifyInfo): save = Signal() dataChanged = Signal() nextMv = Signal() prevMv = Signal() def __init__(self, mietverhaeltnis: XMietverhaeltnis = None, withSaveButton: bool = False, enableBrowsing=False, parent=None): QWidget.__init__(self, parent) ModifyInfo.__init__(self) self._mietverhaeltnis: XMietverhaeltnis = None self._withSaveButton = withSaveButton self._enableBrowsing = enableBrowsing # ob die Browse-Buttons angezeigt werden self._layout = QGridLayout() self._btnSave = QPushButton() self._btnVor = QPushButton() self._btnRueck = QPushButton() self._sdBeginnMietverh = SmartDateEdit() self._sdEndeMietverh = SmartDateEdit() self._edMieterName_1 = BaseEdit() self._edMieterVorname_1 = BaseEdit() self._edMieterName_2 = BaseEdit() self._edMieterVorname_2 = BaseEdit() self._edMieterTelefon = BaseEdit() self._edMieterMobil = BaseEdit() self._edMieterMailto = BaseEdit() self._edAnzPers = IntEdit() self._edNettomiete = FloatEdit() self._edNkv = FloatEdit() self._edKaution = IntEdit() self._sdKautionBezahltAm = SmartDateEdit() self._txtBemerkung1 = MultiLineEdit() self._txtBemerkung2 = MultiLineEdit() self._createGui() if mietverhaeltnis: self.setMietverhaeltnisData(mietverhaeltnis) #self.connectWidgetsToChangeSlot( self.onChange ) self.connectWidgetsToChangeSlot(self.onChange, self.onResetChangeFlag) def onChange(self, newcontent: str = None): if not self._btnSave.isEnabled(): self.setSaveButtonEnabled() self.dataChanged.emit() def onResetChangeFlag(self): self.setSaveButtonEnabled(False) def setSaveButtonEnabled(self, enabled: bool = True): self._btnSave.setEnabled(enabled) def _createGui(self): hbox = QHBoxLayout() if self._withSaveButton: self._createSaveButton(hbox) if self._enableBrowsing: self._createVorRueckButtons(hbox) self._layout.addLayout(hbox, 0, 0, alignment=Qt.AlignLeft) self._addHorizontalLine(1) self._createFelder(2) self.setLayout(self._layout) def _createSaveButton(self, hbox): btn = self._btnSave btn.clicked.connect(self.save.emit) btn.setFlat(True) btn.setEnabled(False) btn.setToolTip("Änderungen am Mietverhältnis speichern") icon = ImageFactory.inst().getSaveIcon() #icon = QIcon( "./images/save_30.png" ) btn.setIcon(icon) size = QSize(32, 32) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) hbox.addWidget(btn) def _createVorRueckButtons(self, hbox): self._prepareButton(self._btnRueck, ImageFactory.inst().getPrevIcon(), "Zum vorigen Mietverhältnis blättern", self.prevMv) hbox.addWidget(self._btnRueck) self._prepareButton(self._btnVor, ImageFactory.inst().getNextIcon(), "Zum nächsten Mietverhältnis blättern", self.nextMv) hbox.addWidget(self._btnVor) def _prepareButton(self, btn: QPushButton, icon: QIcon, tooltip: str, signal: Signal): btn.setFlat(True) btn.setEnabled(True) btn.setToolTip(tooltip) btn.setIcon(icon) size = QSize(32, 32) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) btn.clicked.connect(signal.emit) def _addHorizontalLine(self, r: int): hline = HLine() self._layout.addWidget(hline, r, 0, 1, 2) return r + 1 def _createFelder(self, r): c = 0 l = self._layout lbl = QLabel("Beginn: ") l.addWidget(lbl, r, c) c += 1 self._sdBeginnMietverh.setMaximumWidth(100) l.addWidget(self._sdBeginnMietverh, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Ende: "), r, c) c += 1 self._sdEndeMietverh.setMaximumWidth(100) l.addWidget(self._sdEndeMietverh, r, c) c = 0 r += 1 lbl = BaseLabel("Name / Vorname 1. Mieter: ") l.addWidget(lbl, r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._edMieterName_1) hbox.addWidget(self._edMieterVorname_1) l.addLayout(hbox, r, c) c = 0 r += 1 lbl = BaseLabel("Name / Vorname 2. Mieter: ") l.addWidget(lbl, r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._edMieterName_2) hbox.addWidget(self._edMieterVorname_2) l.addLayout(hbox, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Telefon: "), r, c) c += 1 l.addWidget(self._edMieterTelefon, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Mobil: "), r, c) c += 1 l.addWidget(self._edMieterMobil, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Mailadresse: "), r, c) c += 1 l.addWidget(self._edMieterMailto, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Anzahl Personen i.d. Whg: "), r, c) c += 1 self._edAnzPers.setMaximumWidth(20) l.addWidget(self._edAnzPers, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Nettomiete / NKV: "), r, c) c += 1 self._edNettomiete.setMaximumWidth(100) #self._edNettomiete.setEnabled( False ) self._edNkv.setMaximumWidth(100) #self._edNkv.setEnabled( False ) hbox = QHBoxLayout() hbox.addWidget(self._edNettomiete) hbox.addWidget(self._edNkv) hbox.addWidget( BaseLabel( " Änderungen der Miete und NKV über Dialog 'Sollmiete'")) l.addLayout(hbox, r, c, alignment=Qt.AlignLeft) c = 0 r += 1 l.addWidget(BaseLabel("Kaution: "), r, c) c += 1 self._edKaution.setMaximumWidth(100) l.addWidget(self._edKaution, r, c) c = 0 r += 1 l.addWidget(BaseLabel("Kaution bezahlt am: "), r, c) c += 1 self._sdKautionBezahltAm.setMaximumWidth(100) l.addWidget(self._sdKautionBezahltAm, r, c) c = 0 r += 1 l.addWidget(BaseLabel(""), r, c) r += 1 l.addWidget(BaseLabel("Bemerkungen: "), r, c) c += 1 hbox = QHBoxLayout() hbox.addWidget(self._txtBemerkung1) hbox.addWidget(self._txtBemerkung2) l.addLayout(hbox, r, c) # cols = self._layout.columnCount() # print( cols, " columns" ) def setNettoUndNkvEnabled(self, enabled: bool = True): self._edNettomiete.setEnabled(enabled) self._edNkv.setEnabled(enabled) def _guiToData(self, x: XMietverhaeltnis): """ Überträgt die Änderungen, die der User im GUI gemacht hat, in das übergebene XMietverhaeltnis-Objekt :param x: XMietverhaeltnis-Objekt, in das die geänderten Daten übertragen werden :return: """ x.von = self._sdBeginnMietverh.getDate() x.bis = self._sdEndeMietverh.getDate() x.name = self._edMieterName_1.text() x.vorname = self._edMieterVorname_1.text() x.name2 = self._edMieterName_2.text() x.vorname2 = self._edMieterVorname_2.text() x.telefon = self._edMieterTelefon.text() x.mobil = self._edMieterMobil.text() x.mailto = self._edMieterMailto.text() x.anzahl_pers = self._edAnzPers.getIntValue() x.nettomiete = self._edNettomiete.getFloatValue() x.nkv = self._edNkv.getFloatValue() x.kaution = self._edKaution.getIntValue() x.kaution_bezahlt_am = self._sdKautionBezahltAm.getDate() x.bemerkung1 = self._txtBemerkung1.toPlainText() x.bemerkung2 = self._txtBemerkung2.toPlainText() def getMietverhaeltnisCopyWithChanges(self) -> XMietverhaeltnis: """ gibt eine Kopie der Mietverhaeltnis-Schnittstellendaten mit Änderungen zurück. Diese Kopie kann für Validierungszwecke verwendet werden. :return: Kopie von XMietverhaeltnis """ mvcopy = copy.copy(self._mietverhaeltnis) self._guiToData(mvcopy) return mvcopy def applyChanges(self): """ überträgt die Änderungen, die der User im GUI gemacht hat, in das originale XMietverhaeltnis-Objekt. """ if self.isChanged(): self._guiToData(self._mietverhaeltnis) def setMietverhaeltnisData(self, mv: XMietverhaeltnis): """ Daten, die im GUI angezeigt und geändert werden können. :param mv: :return: """ self._mietverhaeltnis = mv if mv.von: self._sdBeginnMietverh.setDateFromIsoString(mv.von) if mv.bis: self._sdEndeMietverh.setDateFromIsoString(mv.bis) self._edMieterName_1.setText(mv.name) self._edMieterVorname_1.setText(mv.vorname) self._edMieterName_2.setText(mv.name2) self._edMieterVorname_2.setText(mv.vorname2) self._edMieterTelefon.setText(mv.telefon) self._edMieterMobil.setText(mv.mobil) self._edMieterMailto.setText(mv.mailto) self._edAnzPers.setIntValue(mv.anzahl_pers) self._edNettomiete.setFloatValue(mv.nettomiete) self._edNkv.setFloatValue(mv.nkv) if mv.kaution: self._edKaution.setIntValue(mv.kaution) if mv.kaution_bezahlt_am: self._sdKautionBezahltAm.setDateFromIsoString( mv.kaution_bezahlt_am) self._txtBemerkung1.setText(mv.bemerkung1) self._txtBemerkung2.setText(mv.bemerkung2) self.resetChangeFlag() def clear(self): self._sdBeginnMietverh.clear() self._sdEndeMietverh.clear() self._edMieterName_1.clear() self._edMieterVorname_1.clear() self._edMieterName_2.clear() self._edMieterVorname_2.clear() self._edMieterTelefon.clear() self._edMieterMobil.clear() self._edMieterMailto.clear() self._edAnzPers.clear() self._edNettomiete.clear() self._edNkv.clear() self._edKaution.clear() self._sdKautionBezahltAm.clear() self._txtBemerkung1.clear() self._txtBemerkung2.clear()
class PlainTextFindReplaceDialog(QDialog): """ Modeless, stay-above-parent dialog that supports find and replace. Allows for searching case (in)sensitively, and whole-word. Find triggered by Enter / Shift+Enter, or corresponding button (Next / Previous), or if Replace clicked before Next / Previous. Find wraps. Highlights all matches, operating on the closest-to-user's-cursor selection first, in the user-selected direction (Next / Previous). Highlighting / found cursors retained on navigation back to text editor, and cleared on re-find/replace if user modified the document. Presents an info label (e.g. "x of y", "No matches found", ...) While no members have a leading underscore, the only explicit public interface is the static method `find_all`. I couldn't find a reason to need to interface with anything in here, so I didn't differentiate everything as "private". """ def __init__(self, plain_text_edit: QPlainTextEdit, parent=None): """ Sets up the dialog. :param plain_text_edit: Text Editor to operate on. :param parent: QWidget parent """ super().__init__(parent=parent, f=Qt.Tool) self.plain_text_edit = plain_text_edit self.cursors_needed = True self.find_flags = QTextDocument.FindFlags() self.found_cursors: List[QTextCursor] = [] self.current_cursor = QTextCursor() # UI layout = QVBoxLayout() find_and_replace_layout = QGridLayout() layout.addLayout( find_and_replace_layout ) # if QGL is sub-layout, add to parent layout before doing stuff. self.find_line_edit = QLineEdit() find_label = QLabel("&Find") find_label.setBuddy(self.find_line_edit) options_layout = QHBoxLayout() self.match_case_check_box = QCheckBox("Match Case") self.whole_word_check_box = QCheckBox("Whole Word") options_layout.addWidget(self.match_case_check_box) options_layout.addWidget(self.whole_word_check_box) options_layout.addStretch() self.found_info_label = QLabel() self.replace_line_edit = QLineEdit() replace_label = QLabel("&Replace") replace_label.setBuddy(self.replace_line_edit) find_and_replace_layout.addWidget(find_label, 0, 0) find_and_replace_layout.addWidget(self.find_line_edit, 0, 1) find_and_replace_layout.addLayout(options_layout, 1, 1) find_and_replace_layout.setRowMinimumHeight(2, 20) find_and_replace_layout.addWidget(self.found_info_label, 2, 1) find_and_replace_layout.addWidget(replace_label, 3, 0) find_and_replace_layout.addWidget(self.replace_line_edit, 3, 1) self.btn_box = QDialogButtonBox(QDialogButtonBox.Close) self.find_next_btn = QPushButton("Next") self.find_next_btn.setEnabled(False) self.find_prev_btn = QPushButton("Previous") self.find_prev_btn.setEnabled(False) self.replace_btn = QPushButton("Replace") self.replace_btn.setEnabled(False) self.replace_all_btn = QPushButton("Replace All") self.replace_all_btn.setEnabled(False) self.btn_box.addButton(self.replace_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.replace_all_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.find_prev_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.find_next_btn, QDialogButtonBox.ActionRole) layout.addWidget(self.btn_box) self.setLayout(layout) # End UI self.btn_box.rejected.connect(self.reject) self.find_line_edit.textEdited.connect(self.handle_text_edited) self.find_next_btn.clicked.connect(self.next) self.find_prev_btn.clicked.connect(self.prev) self.replace_btn.clicked.connect(self.replace) self.replace_all_btn.clicked.connect(self.replace_all) self.plain_text_edit.document().contentsChanged.connect( self.set_cursors_needed_true) self.whole_word_check_box.stateChanged.connect( self.toggle_whole_word_flag) self.match_case_check_box.stateChanged.connect( self.toggle_match_case_flag) # SLOTS def next(self): """ Finds all matches to the user's search. First highlight after cursor. Consecutive calls advance through matches. If there are matches or not, it says so. The user's cursor advances along with the current selection. Loops back to start after the last match. :return: Side effect: Highlights all matches, differentiating (maybe moving fwd) the current selection. """ if self.cursors_needed: self.init_find() if not self.found_cursors: self.found_info_label.setText("No matches found") self.found_info_label.repaint() return if self.current_cursor >= self.found_cursors[ -1]: # loop back to start. cursor equality based on position. self.current_cursor = self.found_cursors[0] else: for cur in self.found_cursors: if cur > self.current_cursor: # next in order self.current_cursor = cur break self.update_visuals() def prev(self): """ Finds all matches to user's search. First highlight before cursor. Consecutive calls retreat through matches. If there are matches or not, it says so. The user's cursor moves along with the current selection. Loops back to end after the last (first in doc) match. :return: Side effect: Highlights all matches, differentiating (maybe moving back) the current selection. """ if self.cursors_needed: self.init_find() if not self.found_cursors: self.found_info_label.setText("No matches found") self.found_info_label.repaint() return if self.current_cursor <= self.found_cursors[0]: # loop back to end. self.current_cursor = self.found_cursors[-1] else: for cur in reversed(self.found_cursors): if cur < self.current_cursor: # prev in order self.current_cursor = cur break self.update_visuals() def replace(self): """ Replaces the word under focus by `next`. Replaces with the Replace line edit's text, and advances to next word. If no word under focus via this dialog, calls `next`. :return: Side effect: replaces word in text edit """ if self.cursors_needed: self.next() return if not self.found_cursors: return self.plain_text_edit.document().contentsChanged.disconnect( self.set_cursors_needed_true) # don't dup work. self.current_cursor.insertText(self.replace_line_edit.text()) self.plain_text_edit.document().contentsChanged.connect( self.set_cursors_needed_true) self.found_cursors.remove(self.current_cursor) self.next() def replace_all(self): """ Replaces all instances of Find's text with Replace's text. :return: Side effect: replaces words in text edit. Indicates success to user via info label on dialog. """ if self.cursors_needed: self.init_find() for cur in self.found_cursors: cur.insertText(self.replace_line_edit.text()) self.found_info_label.setText("Made {} replacements".format( len(self.found_cursors))) self.found_info_label.repaint() def handle_text_edited(self, text): """ Modifies button states, clears info text, and sets self.cursors_needed to True. :param text: The find_line_edit's text. :return: Side effect: btn enabled / default """ self.found_info_label.clear() self.cursors_needed = True find_enabled = text != "" self.find_next_btn.setEnabled(find_enabled) self.find_prev_btn.setEnabled(find_enabled) self.replace_btn.setEnabled(find_enabled) self.replace_all_btn.setEnabled(find_enabled) self.find_next_btn.setDefault(find_enabled) self.btn_box.button( QDialogButtonBox.Close).setDefault(not find_enabled) def set_cursors_needed_true(self): self.cursors_needed = True def toggle_match_case_flag(self, state: int): self.found_info_label.clear( ) # User will be performing a new search upon toggle, so want this reset. self.cursors_needed = True if state == Qt.Unchecked: self.find_flags &= ~QTextDocument.FindCaseSensitively elif state == Qt.Checked: self.find_flags |= QTextDocument.FindCaseSensitively def toggle_whole_word_flag(self, state: int): self.found_info_label.clear( ) # User will be performing a new search upon toggle, so want this reset. self.cursors_needed = True if state == Qt.Unchecked: self.find_flags &= ~QTextDocument.FindWholeWords elif state == Qt.Checked: self.find_flags |= QTextDocument.FindWholeWords # END SLOTS def init_find(self): """Sets up internal state for the case when cursors are needed (e.g. first find, user modifies doc...)""" self.found_cursors = self.find_all(self.find_line_edit.text(), self.plain_text_edit.document(), self.find_flags) self.cursors_needed = False self.current_cursor = self.plain_text_edit.textCursor( ) # returns copy of self.plain_text_edit.setExtraSelections([]) def update_visuals(self): """ Moves text editor's cursor to match currently highlighted `find` word, performs `find` highlighting, indicates index on dialog. """ # x of y words indicator idx = self.found_cursors.index(self.current_cursor) + 1 self.found_info_label.setText("{} of {} matches".format( idx, len(self.found_cursors))) self.found_info_label.repaint() # move along text editor's viewport next_pte_cursor = QTextCursor(self.current_cursor) next_pte_cursor.clearSelection() self.plain_text_edit.setTextCursor(next_pte_cursor) #highlighting normal_color = QColor(Qt.yellow).lighter() current_color = QColor(Qt.magenta).lighter() extra_selections: List[QTextEdit.ExtraSelection] = [] for cur in self.found_cursors: selection = QTextEdit.ExtraSelection() selection.cursor = cur if cur == self.current_cursor: selection.format.setBackground(current_color) else: selection.format.setBackground(normal_color) extra_selections.append(selection) self.plain_text_edit.setExtraSelections(extra_selections) @staticmethod def find_all( text: str, document: QTextDocument, flags=QTextDocument.FindFlags() ) -> List[QTextCursor]: """ Finds all occurrences of `text` in `document`, in order. :param text: Text to find. :param document: Document to search. :param flags: Conditions to set on the search: none or (whole word and/or match case) :return: Ordered list of all found instances. """ cursor = QTextCursor(document) # default pos == 0 found: List[QTextCursor] = [] while True: cursor = document.find(text, cursor, flags) if cursor.isNull(): return found else: found.append(cursor) def done(self, arg__1: int): self.plain_text_edit.setExtraSelections([]) super().done(arg__1) def keyPressEvent(self, arg__1: QKeyEvent): # Shift+Enter triggers find previous, if the corresponding btn is enabled. if (arg__1.key() in [Qt.Key_Return, Qt.Key_Enter] and arg__1.modifiers() == Qt.ShiftModifier and self.find_prev_btn.isEnabled()): self.prev() else: super().keyPressEvent(arg__1)
class QGroundObjectTemplateLayout(QGroupBox): close_dialog_signal = Signal() def __init__( self, game: Game, ground_object: TheaterGroundObject, layout: QTgoLayout, layout_changed_signal: Signal(QTgoLayout), current_group_value: int, ): super().__init__() # Connect to the signal to handle template updates self.game = game self.ground_object = ground_object self.layout_changed_signal = layout_changed_signal self.layout_model = layout self.layout_changed_signal.connect(self.load_for_layout) self.current_group_value = current_group_value self.buy_button = QPushButton("Buy") self.buy_button.setEnabled(False) self.buy_button.clicked.connect(self.buy_group) self.template_layout = QGridLayout() self.setLayout(self.template_layout) self.template_grid = QGridLayout() self.template_layout.addLayout(self.template_grid, 0, 0, 1, 2) self.template_layout.addWidget(self.buy_button, 1, 1) stretch = QVBoxLayout() stretch.addStretch() self.template_layout.addLayout(stretch, 2, 0) # Load Layout self.load_for_layout(self.layout_model) def load_for_layout(self, layout: QTgoLayout) -> None: self.layout_model = layout # Clean the current grid self.layout_model.groups = defaultdict(list) for id in range(self.template_grid.count()): self.template_grid.itemAt(id).widget().deleteLater() for group_name, groups in self.layout_model.layout.groups.items(): self.add_theater_group(group_name, self.layout_model.force_group, groups) self.group_template_changed() @property def cost(self) -> int: return self.layout_model.price - self.current_group_value @property def affordable(self) -> bool: return self.cost <= self.game.blue.budget def add_theater_group(self, group_name: str, force_group: ForceGroup, groups: list[TgoLayoutGroup]) -> None: group_box = QGroupBox(group_name) vbox_layout = QVBoxLayout() for group in groups: try: group_row = QTgoLayoutGroupRow(force_group, group) except LayoutException: continue self.layout_model.groups[group_name].append(group_row.group_layout) group_row.group_template_changed.connect( self.group_template_changed) vbox_layout.addWidget(group_row) group_box.setLayout(vbox_layout) self.template_grid.addWidget(group_box) def group_template_changed(self) -> None: price = self.layout_model.price self.buy_button.setText( f"Buy [${price}M][-${self.current_group_value}M]") self.buy_button.setEnabled(self.affordable) if self.buy_button.isEnabled(): self.buy_button.setToolTip(f"Buy the group for ${self.cost}M") else: self.buy_button.setToolTip("Not enough money to buy this group") def buy_group(self) -> None: if not self.affordable: # Something went wrong. Buy button should be disabled! logging.error("Not enough money to buy the group") return # Change the heading of the new group to head to the conflict self.ground_object.heading = ( self.game.theater.heading_to_conflict_from( self.ground_object.position) or self.ground_object.heading) self.game.blue.budget -= self.cost self.ground_object.groups = [] for group_name, groups in self.layout_model.groups.items(): for group in groups: self.layout_model.force_group.create_theater_group_for_tgo( self.ground_object, group.layout, f"{self.ground_object.name} ({group_name})", self.game, group.dcs_unit_type, # Forced Type group.amount, # Forced Amount ) self.close_dialog_signal.emit()