class ItemWidgetBase(QFrame): checkedStateChanged = pyqtSignal() thumbnailChanged = pyqtSignal() def __init__(self, item): QFrame.__init__(self) self.item = item self.is_updating_checkbox = False self.setMouseTracking(True) self.setStyleSheet("ItemWidgetBase{border: 2px solid transparent;}") def _setup_ui(self, text, thumbnailurl): self.lockLabel = QLabel() iconSize = QSize(16, 16) self.lockLabel.setPixmap(LOCK_ICON.pixmap(iconSize)) self.checkBox = QCheckBox("") self.checkBox.stateChanged.connect(self.check_box_state_changed) self.nameLabel = QLabel(text) self.iconLabel = QLabel() self.labelZoomTo = QLabel() self.labelZoomTo.setPixmap(ZOOMTO_ICON.pixmap(QSize(18, 18))) self.labelZoomTo.setToolTip("Zoom to extent") self.labelZoomTo.mousePressEvent = self.zoom_to_extent self.labelAddPreview = QLabel() self.labelAddPreview.setPixmap(ADD_PREVIEW_ICON.pixmap(QSize(18, 18))) self.labelAddPreview.setToolTip("Add preview layer to map") self.labelAddPreview.mousePressEvent = self._add_preview_clicked layout = QHBoxLayout() layout.setMargin(0) layout.addWidget(self.checkBox) layout.addWidget(self.lockLabel) pixmap = QPixmap(PLACEHOLDER_THUMB, 'SVG') self.thumbnail = None thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.iconLabel.setPixmap(thumb) self.iconLabel.setFixedSize(48, 48) layout.addWidget(self.iconLabel) if thumbnailurl is not None: download_thumbnail(thumbnailurl, self) layout.addWidget(self.nameLabel) layout.addStretch() layout.addWidget(self.labelZoomTo) layout.addWidget(self.labelAddPreview) layout.addSpacing(10) self.setLayout(layout) self.footprint = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.footprint.setStrokeColor(PLANET_COLOR) self.footprint.setWidth(2) def set_thumbnail(self, img): self.thumbnail = QPixmap(img) thumb = self.thumbnail.scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.iconLabel.setPixmap(thumb) self.thumbnailChanged.emit() def is_selected(self): return self.checkBox.isChecked() def _geom_bbox_in_project_crs(self): transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance().crs(), QgsProject.instance()) return transform.transformBoundingBox(self.geom.boundingBox()) def _geom_in_project_crs(self): transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance().crs(), QgsProject.instance()) geom = QgsGeometry(self.geom) geom.transform(transform) return geom def show_footprint(self): self.footprint.setToGeometry(self._geom_in_project_crs()) def hide_footprint(self): self.footprint.reset(QgsWkbTypes.PolygonGeometry) def enterEvent(self, event): self.setStyleSheet( "ItemWidgetBase{border: 2px solid rgb(0, 157, 165);}") self.show_footprint() def leaveEvent(self, event): self.setStyleSheet("ItemWidgetBase{border: 2px solid transparent;}") self.hide_footprint() def zoom_to_extent(self, evt): rect = QgsRectangle(self._geom_bbox_in_project_crs()) rect.scale(1.05) iface.mapCanvas().setExtent(rect) iface.mapCanvas().refresh() def _add_preview_clicked(self, evt): self.add_preview() @waitcursor def add_preview(self): send_analytics_for_preview(self.item.images()) create_preview_group(self.name(), self.item.images()) def check_box_state_changed(self): self.checkedStateChanged.emit() self.is_updating_checkbox = True total = self.item.childCount() if self.checkBox.isTristate(): self.checkBox.setTristate(False) self.checkBox.setChecked(False) else: for i in range(total): w = self.item.treeWidget().itemWidget(self.item.child(i), 0) w.set_checked(self.checkBox.isChecked()) self.is_updating_checkbox = False def update_checkbox(self): if self.is_updating_checkbox: return selected = 0 total = self.item.childCount() for i in range(total): w = self.item.treeWidget().itemWidget(self.item.child(i), 0) if w.is_selected(): selected += 1 self.checkBox.blockSignals(True) if selected == total: self.checkBox.setTristate(False) self.checkBox.setCheckState(Qt.Checked) elif selected == 0: self.checkBox.setTristate(False) self.checkBox.setCheckState(Qt.Unchecked) else: self.checkBox.setTristate(True) self.checkBox.setCheckState(Qt.PartiallyChecked) self.checkBox.blockSignals(False) self.checkedStateChanged.emit() def set_checked(self, checked): self.checkBox.setChecked(checked) def update_thumbnail(self): thumbnails = self.scene_thumbnails() if thumbnails and None not in thumbnails: bboxes = [img[GEOMETRY] for img in self.item.images()] pixmap = createCompoundThumbnail(bboxes, thumbnails) thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.iconLabel.setPixmap(thumb) self.thumbnailChanged.emit() def scene_thumbnails(self): thumbnails = [] try: for i in range(self.item.childCount()): w = self.item.treeWidget().itemWidget(self.item.child(i), 0) thumbnails.extend(w.scene_thumbnails()) except RuntimeError: # item might not exist anymore. In this case, we just return # an empty list pass return thumbnails
class PoleRow(object): """ Creates all input fields necessary to change the properties of a pole in the cable layout. The layout is identified by the position (index) it has in the vertical layout. """ ICON_ADD_ROW = ":/plugins/SeilaplanPlugin/gui/icons/icon_addrow.png" ICON_DEL_ROW = ":/plugins/SeilaplanPlugin/gui/icons/icon_bin.png" def __init__(self, parent, widget, layout, idx, nr, name, rowType, dist, distRange, height=False, angle=False, delBtn=False, addBtn=False): self.parent = parent self.widget = widget self.layout = layout self.index = idx self.rowType = rowType self.parent.poleCount += 1 self.row = QHBoxLayout() self.row.setAlignment(Qt.AlignLeft) self.labelNr = None self.statusSwitcher = None self.fieldName = None self.fieldDist = None self.fieldHeight = None self.fieldAngle = None self.addBtn = None self.delBtn = None self.addRowToLayout() self.addBtnPlus(addBtn) if self.rowType == 'anchor': self.addSwitcher() else: self.addLabelNr(nr) self.addFieldName(name) self.addFieldDist(dist, distRange) if self.rowType not in ['anchor']: self.addFieldHeight(height) self.addFieldAngle(angle) self.addBtnDel(delBtn) def addRowToLayout(self): if self.index == self.parent.poleCount: # Add layout at the end self.layout.addLayout(self.row) else: # Insert new row between existing ones self.layout.insertLayout(self.index + 1, self.row) def addSwitcher(self): self.statusSwitcher = QCheckBox(self.widget) self.statusSwitcher.setText('') self.statusSwitcher.setFixedWidth(20) self.statusSwitcher.setChecked(True) self.row.addWidget(self.statusSwitcher) # Connect events self.statusSwitcher.stateChanged.connect( lambda newVal: self.parent.onRowChange(newVal == 2, self.index, 'active')) def addLabelNr(self, nr): self.labelNr = QLabel(self.widget) self.labelNr.setFixedWidth(20) self.labelNr.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.row.addWidget(self.labelNr) if nr: self.labelNr.setText(f"{nr}:") def updateIndex(self, idx): self.index = idx def updateLabelNr(self, label): if self.labelNr: if label: self.labelNr.setText(f"{label}:") else: self.labelNr.setText("") def addFieldName(self, value): self.fieldName = QLineEditWithFocus(self.widget) self.fieldName.setFocusPolicy(Qt.ClickFocus) self.fieldName.setFixedWidth(200) self.fieldName.setText(value) self.row.addWidget(self.fieldName) # Connect events self.fieldName.inFocus.connect( lambda x: self.parent.zoomIn(self.index)) self.fieldName.outFocus.connect(self.parent.zoomOut) self.fieldName.textChanged.connect( lambda newVal: self.parent.onRowChange(newVal, self.index, 'name')) def addFieldDist(self, value, distRange): self.fieldDist = QDoubleSpinBoxWithFocus(self.widget) self.fieldDist.setFocusPolicy(Qt.ClickFocus) self.fieldDist.setDecimals(0) self.fieldDist.setSingleStep(self.parent.pole_dist_step) self.fieldDist.setSuffix(" m") self.fieldDist.setFixedWidth(95) self.fieldDist.setRange(float(distRange[0]), float(distRange[1])) self.fieldDist.setValue(float(value)) self.row.addWidget(self.fieldDist) # Connect events self.fieldDist.inFocus.connect( lambda x: self.parent.zoomIn(self.index)) self.fieldDist.outFocus.connect(self.parent.zoomOut) self.fieldDist.valueChanged.connect( lambda newVal: self.parent.onRowChange(newVal, self.index, 'd')) def addFieldHeight(self, value): if value is False: return self.fieldHeight = QDoubleSpinBoxWithFocus(self.widget) self.fieldHeight.setFocusPolicy(Qt.ClickFocus) self.fieldHeight.setDecimals(1) self.fieldHeight.setSingleStep(self.parent.pole_height_step) # Pole rows with type fixed are only used in profile window, so before # optimization. That's why they only have 1 meter resolution. if self.rowType == 'fixed': self.fieldHeight.setDecimals(0) self.fieldHeight.setSingleStep(1) self.fieldHeight.setSuffix(" m") self.fieldHeight.setFixedWidth(95) self.fieldHeight.setRange(0.0, 50.0) if value is not None: self.fieldHeight.setValue(float(value)) self.row.addWidget(self.fieldHeight) # Connect events self.fieldHeight.inFocus.connect( lambda x: self.parent.zoomIn(self.index)) self.fieldHeight.outFocus.connect(self.parent.zoomOut) self.fieldHeight.valueChanged.connect( lambda newVal: self.parent.onRowChange(newVal, self.index, 'h')) def addFieldAngle(self, value): if value is False: return self.fieldAngle = QSpinBoxWithFocus(self.widget) self.fieldAngle.setFocusPolicy(Qt.ClickFocus) self.fieldAngle.setSuffix(" °") self.fieldAngle.setFixedWidth(60) self.fieldAngle.setRange(-180, 180) if value is not None: self.fieldAngle.setValue(int(value)) self.row.addWidget(self.fieldAngle) # Connect events self.fieldAngle.inFocus.connect( lambda x: self.parent.zoomIn(self.index)) self.fieldAngle.outFocus.connect(self.parent.zoomOut) self.fieldAngle.valueChanged.connect( lambda newVal: self.parent.onRowChange(newVal, self.index, 'angle' )) def addBtnPlus(self, createButton): if createButton is False: self.row.addSpacing(33) return self.addBtn = QPushButton(self.widget) self.addBtn.setMaximumSize(QSize(27, 27)) icon = QIcon() icon.addPixmap(QPixmap(PoleRow.ICON_ADD_ROW), QIcon.Normal, QIcon.Off) self.addBtn.setIcon(icon) self.addBtn.setIconSize(QSize(16, 16)) self.addBtn.setToolTip( self.tr('Fuegt eine neue Stuetze nach dieser hinzu')) self.addBtn.setAutoDefault(False) self.row.addWidget(self.addBtn) self.addBtn.clicked.connect(lambda x: self.parent.onRowAdd(self.index)) def addBtnDel(self, createButton): if createButton is False: self.row.addSpacing(33) return self.delBtn = QPushButton(self.widget) self.delBtn.setMaximumSize(QSize(27, 27)) icon = QIcon() icon.addPixmap(QPixmap(PoleRow.ICON_DEL_ROW), QIcon.Normal, QIcon.Off) self.delBtn.setIcon(icon) self.delBtn.setIconSize(QSize(16, 16)) self.delBtn.setToolTip(self.tr('Loescht die Stuetze')) self.delBtn.setAutoDefault(False) self.row.addWidget(self.delBtn) self.delBtn.clicked.connect(lambda x: self.parent.onRowDel(self.index)) def updateLowerDistRange(self, minimum): self.fieldDist.setMinimum(minimum) def updateUpperDistRange(self, maximum): self.fieldDist.setMaximum(maximum) def activate(self): self.statusSwitcher.blockSignals(True) self.statusSwitcher.setChecked(True) self.statusSwitcher.blockSignals(False) self.fieldName.setEnabled(True) self.fieldDist.setEnabled(True) def deactivate(self): self.statusSwitcher.blockSignals(True) self.statusSwitcher.setChecked(False) self.statusSwitcher.blockSignals(False) self.fieldName.setEnabled(False) self.fieldDist.setEnabled(False) def remove(self): # Disconnect all widgets self.fieldName.disconnect() self.fieldDist.disconnect() if self.fieldHeight: self.fieldHeight.disconnect() if self.fieldAngle: self.fieldAngle.disconnect() if self.addBtn: self.addBtn.disconnect() if self.delBtn: self.delBtn.disconnect() for i in reversed(range(self.row.count())): item = self.row.takeAt(i) widget = item.widget() if widget is not None: widget.deleteLater() else: # For spacers self.row.removeItem(item) self.layout.removeItem(self.row) self.parent.poleCount -= 1 # noinspection PyMethodMayBeStatic def tr(self, message, **kwargs): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString Parameters ---------- **kwargs """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate(type(self).__name__, message)
class ParamBox(QDialog): """ Param box of the plugin """ def __init__(self, parent=None, tree_dock=None): QWidget.__init__(self, parent) self.tree_dock = tree_dock # Init GUI self.init_gui() # Evaluate flags and tupdate the state of the Apply button self.evaluate_flags() def init_gui(self): """ """ dlg_layout = QVBoxLayout() params_layout = QVBoxLayout() params_layout.setAlignment(Qt.AlignTop) # Config files groupbox self.config_files_groupbox = QgsCollapsibleGroupBox( u"Fichier de configuration de l'arbre des ressources") config_file_groupbox_layout = QFormLayout(self.config_files_groupbox) # URL of the file self.config_file_url_label = QLabel(u"URL du fichier", self) self.config_file_url_edit = QLineEdit(self) self.config_file_url_edit.editingFinished.connect( self.config_file_url_changed) config_file_groupbox_layout.addRow(self.config_file_url_label, self.config_file_url_edit) # Download the file at startup self.download_cb = QCheckBox( u"Télécharger le fichier à chaque lancement de QGIS", self) self.download_cb.stateChanged.connect(self.download_cb_changed) config_file_groupbox_layout.addRow(self.download_cb) params_layout.addWidget(self.config_files_groupbox) # Download the file now self.download_now_label = QLabel(u"Télécharger le fichier maintenant", self) self.download_now_btnbox = QDialogButtonBox() self.download_now_btnbox.setOrientation(Qt.Horizontal) self.download_now_btnbox.setStandardButtons(QDialogButtonBox.Yes) self.download_now_btnbox.button(QDialogButtonBox.Yes).clicked.connect( self.download_file_now) config_file_groupbox_layout.addRow(self.download_now_label, self.download_now_btnbox) # Content of the resource tree groupbox self.resource_tree_groupbox = QgsCollapsibleGroupBox( u"Contenu de l'arbre des ressources") resource_tree_groupbox_layout = QFormLayout( self.resource_tree_groupbox) # Hide resources with a warn flag self.hide_resources_with_warn_status_cb = QCheckBox( u"Masquer les ressources en cours d'intégration", self) self.hide_resources_with_warn_status_cb.stateChanged.connect( self.hide_resources_with_warn_cb_changed) resource_tree_groupbox_layout.addRow( self.hide_resources_with_warn_status_cb) # Hide empty groups in the resources tree self.hide_empty_groups_cb = QCheckBox( u"Masquer les groupes de ressources vides", self) self.hide_empty_groups_cb.stateChanged.connect( self.hide_empty_groups_cb_changed) resource_tree_groupbox_layout.addRow(self.hide_empty_groups_cb) params_layout.addWidget(self.resource_tree_groupbox) dlg_layout.addLayout(params_layout) # Set values self.set_values_from_qsettings() # Bottom dialog buttons self.button_box = QDialogButtonBox() self.button_box.setOrientation(Qt.Horizontal) self.button_box.setStandardButtons(QDialogButtonBox.RestoreDefaults | QDialogButtonBox.Apply | QDialogButtonBox.Close) self.button_box.button( QDialogButtonBox.RestoreDefaults).clicked.connect( self.restore_defaults_button_clicked) self.button_box.button(QDialogButtonBox.Close).clicked.connect( self.close_button_clicked) self.button_box.button(QDialogButtonBox.Apply).clicked.connect( self.apply_button_clicked) # Dialog box title, layout, size and display title = u"Paramétrage de l'extension GéoSAS…" self.setWindowTitle(title) dlg_layout.addWidget(self.button_box) self.setLayout(dlg_layout) self.setMinimumWidth(500) self.resize(self.sizeHint()) self.setSizeGripEnabled(False) self.setFixedSize(self.size()) self.show() self.setSizeGripEnabled(False) def set_values_from_qsettings(self): """ """ # URL of the file self.config_file_url_edit.blockSignals(True) self.config_file_url_edit.setText( PluginGlobals.instance().CONFIG_FILE_URLS[0]) self.config_file_url_edit.setCursorPosition(0) self.config_file_url_edit.blockSignals(False) # Download the file at startup self.download_cb.blockSignals(True) self.download_cb.setChecked( PluginGlobals.instance().CONFIG_FILES_DOWNLOAD_AT_STARTUP) self.download_cb.blockSignals(True) # Hide resources with a warn flag self.hide_resources_with_warn_status_cb.blockSignals(True) self.hide_resources_with_warn_status_cb.setChecked( PluginGlobals.instance().HIDE_RESOURCES_WITH_WARN_STATUS) self.hide_resources_with_warn_status_cb.blockSignals(False) # Hide empty groups in the resources tree self.hide_empty_groups_cb.blockSignals(True) self.hide_empty_groups_cb.setChecked( PluginGlobals.instance().HIDE_EMPTY_GROUPS) self.hide_empty_groups_cb.blockSignals(False) def evaluate_flags(self): """ """ # Detect modifications file_url_changed = (self.config_file_url_edit.text() != PluginGlobals.instance().CONFIG_FILE_URLS[0]) download_at_startup_changed = \ (self.download_cb.isChecked() != PluginGlobals.instance().CONFIG_FILES_DOWNLOAD_AT_STARTUP) hide_resources_with_warn_status_changed = \ (self.hide_resources_with_warn_status_cb.isChecked() != PluginGlobals.instance().HIDE_RESOURCES_WITH_WARN_STATUS) hide_empty_groups_changed = \ (self.hide_empty_groups_cb.isChecked() != PluginGlobals.instance().HIDE_EMPTY_GROUPS) # Init flags self.need_update_visibility_of_tree_items = hide_empty_groups_changed or hide_resources_with_warn_status_changed self.need_update_of_tree_content = file_url_changed self.need_save = file_url_changed or download_at_startup_changed or \ hide_resources_with_warn_status_changed or \ hide_empty_groups_changed # Update state of the Apply Button self.button_box.button(QDialogButtonBox.Apply).setEnabled( self.need_save) def download_cb_changed(self, state): """ Event sent when the state of the checkbox change """ self.evaluate_flags() def config_file_url_changed(self): """ Event sent when the text of the line edit has been edited """ self.evaluate_flags() def hide_resources_with_warn_cb_changed(self, state): """ Event sent when the state of the checkbox change """ self.evaluate_flags() def hide_empty_groups_cb_changed(self, state): """ Event sent when the state of the checkbox change """ self.evaluate_flags() def download_file_now(self): """ """ # Download, read the resources tree file and update the GUI download_tree_config_file(self.config_file_url_edit.text()) self.ressources_tree = TreeNodeFactory( PluginGlobals.instance().config_file_path).root_node if self.tree_dock is not None: self.tree_dock.set_tree_content(self.ressources_tree) def save_settings(self): """ """ # URL of the file new_value = [self.config_file_url_edit.text()] PluginGlobals.instance().set_qgis_settings_value( "config_file_urls", new_value) # Download the file at startup new_value = self.download_cb.isChecked() PluginGlobals.instance().set_qgis_settings_value( "config_files_download_at_startup", new_value) # Hide resources with a warn flag new_value = self.hide_resources_with_warn_status_cb.isChecked() PluginGlobals.instance().set_qgis_settings_value( "hide_resources_with_warn_status", new_value) # Hide empty groups in the resources tree new_value = self.hide_empty_groups_cb.isChecked() PluginGlobals.instance().set_qgis_settings_value( "hide_empty_groups", new_value) # Download, read the resources tree file and update the GUI if self.need_update_of_tree_content: download_tree_config_file( PluginGlobals.instance().CONFIG_FILE_URLS[0]) self.ressources_tree = TreeNodeFactory( PluginGlobals.instance().config_file_path).root_node self.tree_dock.set_tree_content(self.ressources_tree) # Update the visibility of tree items elif self.need_update_visibility_of_tree_items and self.tree_dock is not None: self.tree_dock.update_visibility_of_tree_items() def apply_button_clicked(self): """ """ self.save_settings() self.evaluate_flags() def close_button_clicked(self): """ """ self.close() def restore_defaults_button_clicked(self): """ """ # URL of the file self.config_file_url_edit.blockSignals(True) self.config_file_url_edit.setText( PluginGlobals.instance().get_qgis_setting_default_value( "CONFIG_FILE_URLS")[0]) self.config_file_url_edit.setCursorPosition(0) self.config_file_url_edit.blockSignals(False) # Download the file at startup self.download_cb.blockSignals(True) self.download_cb.setChecked( PluginGlobals.instance().get_qgis_setting_default_value( "CONFIG_FILES_DOWNLOAD_AT_STARTUP")) self.download_cb.blockSignals(False) # Hide resources with a warn flag self.hide_resources_with_warn_status_cb.blockSignals(True) self.hide_resources_with_warn_status_cb.setChecked( PluginGlobals.instance().get_qgis_setting_default_value( "HIDE_RESOURCES_WITH_WARN_STATUS")) self.hide_resources_with_warn_status_cb.blockSignals(False) # Hide empty groups in the resources tree self.hide_empty_groups_cb.blockSignals(True) self.hide_empty_groups_cb.setChecked( PluginGlobals.instance().get_qgis_setting_default_value( "HIDE_EMPTY_GROUPS")) self.hide_empty_groups_cb.blockSignals(False) self.evaluate_flags() def closeEvent(self, evnt): """ """ if self.need_save: reply = QMessageBox.question( self, u"Sauvegarder ?", u"Voulez-vous appliquer les changements avant de fermer la fenêtre de paramétrage de l'extension ?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Yes) if reply == QMessageBox.No: evnt.accept() super(ParamBox, self).closeEvent(evnt) elif reply == QMessageBox.Yes: self.save_settings() evnt.accept() super(ParamBox, self).closeEvent(evnt) else: evnt.accept() super(ParamBox, self).closeEvent(evnt) evnt.ignore()
class LinearBarcodeLayoutItemWidget(QgsLayoutItemBaseWidget): # pylint: disable=too-few-public-methods """Widget for configuring a LinearBarcodeLayoutItem.""" def __init__(self, parent, layout_object): super().__init__(parent, layout_object) self._barcode_item = layout_object self.message_bar = None self.setPanelTitle(self.tr('Linear Barcode Properties')) self._init_widgets(layout_object) self._current_meta = self._barcode_cbo.current_metadata() self._update_gui_values() def _init_widgets(self, layout_object): """Initialize widgets""" lbl_title = QLabel() lbl_title.setStyleSheet( 'padding: 2px; font-weight: bold; background-color: ' 'rgb(200, 200, 200);') lbl_title.setText(self.tr('Linear Barcode')) self._cd_value_widget = CodeValueWidget(self) self._cd_value_widget.value_changed.connect( self._on_code_value_changed) value_groupbox = QgsCollapsibleGroupBoxBasic(self.tr('Data')) gp_layout = QVBoxLayout() gp_layout.setContentsMargins(0, 0, 0, 0) gp_layout.addWidget(self._cd_value_widget) value_groupbox.setLayout(gp_layout) # Barcode properties barcode_props_groupbox = QgsCollapsibleGroupBoxBasic( self.tr('Properties')) barcode_props_layout = QGridLayout() lbl_barcode_type = QLabel(self.tr('Linear barcode type')) self._barcode_cbo = LinearMetadataCombobox() self._barcode_cbo.metadata_changed.connect( self._on_linear_type_changed) barcode_props_layout.addWidget(lbl_barcode_type, 0, 0) barcode_props_layout.addWidget(self._barcode_cbo, 0, 1) self._chk_checksum = QCheckBox(self.tr('Add checksum')) self._chk_checksum.stateChanged.connect(self._on_add_checksum) barcode_props_layout.addWidget(self._chk_checksum, 1, 0, 1, 2) self._chk_render_txt = QCheckBox(self.tr('Render barcode text')) self._chk_render_txt.stateChanged.connect(self._on_render_text_changed) barcode_props_layout.addWidget(self._chk_render_txt, 2, 0, 1, 2) barcode_props_layout.setColumnStretch(1, 1) barcode_props_groupbox.setLayout(barcode_props_layout) # Properties widget self._prop_widget = QgsLayoutItemPropertiesWidget(self, layout_object) self._prop_widget.showBackgroundGroup(False) # Add widgets to layout layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(lbl_title) layout.addWidget(barcode_props_groupbox) layout.addWidget(value_groupbox) layout.addWidget(self._prop_widget) # Set layout self.setLayout(layout) def setDesignerInterface(self, iface): """ Use layout iface to set the message_bar. """ super().setDesignerInterface(iface) self.message_bar = iface.messageBar() def metadata(self, metadata_id): """ Gets the metadata object that corresponds to the given id. :param metadata_id: Metadata id. :type metadata_id: str :return: Returns the metadata object corresponding to the given id. :rtype: AbstractLinearBarcodeMetadata """ reg = LinearBarcodeMetadataRegistry.instance() return reg.metadata_by_typeid(metadata_id) def setNewItem(self, item): """ Set widget properties to sync with item properties. """ if item.type() != LINEAR_BARCODE_TYPE: return False self._barcode_item = item self._prop_widget.setItem(self._barcode_item) self._update_gui_values() return True def _on_linear_type_changed(self, metadata_id): """ Slot raised when the linear barcode type has been changed. """ meta = self.metadata(metadata_id) if meta is None: return self._current_meta = meta self._adapt_ui_item_checksum_properties() self.set_barcode_data() def _adapt_ui_item_checksum_properties(self): """ Update UI and barcode item properties based on the properties of the current metadata object. """ if self._current_meta is None: return if self._current_meta.supports_manual_checksum(): self._barcode_item.supports_manual_checksum = True self._chk_checksum.setEnabled(True) # Set check status based on barcode properties if self._barcode_item.add_checksum: self._chk_checksum.setCheckState(Qt.Checked) else: self._chk_checksum.setCheckState(Qt.Unchecked) else: self._barcode_item.supports_manual_checksum = False self._chk_checksum.setEnabled(False) self._barcode_item.add_checksum = False if self._current_meta.is_checksum_automatic(): self._chk_checksum.setCheckState(Qt.Checked) else: self._chk_checksum.setCheckState(Qt.Unchecked) def _on_add_checksum(self, state): """ Slot raised to add checksum to the barcode data. """ add_checksum = False if state == Qt.Checked: add_checksum = True self._barcode_item.beginCommand(self.tr('Change add checksum'), QgsLayoutItem.UndoCustomCommand) self._barcode_item.blockSignals(True) self._barcode_item.add_checksum = add_checksum self._barcode_item.blockSignals(False) self._barcode_item.endCommand() def _on_code_value_changed(self, value): """ Slot raised when the barcode value changes. """ self.set_barcode_data() def set_barcode_data(self): """ Assert if characters (computed from the expression) are valid. If not, remove invalid characters then check if the maximum number of characters are within the maximum set for the given linear barcode type. """ # Flag for the validity of barcode data is_invalid = False if self._current_meta is None: return user_value = self._cd_value_widget.code_value cd_val = self._barcode_item.evaluate_expression(user_value) if not cd_val: return # Check valid characters valid_chars = [] for ch in cd_val: if self._current_meta.is_character_allowed(ch): valid_chars.append(ch) sanitized_txt = ''.join(valid_chars) # Notify user if there were invalid chars if len(sanitized_txt) != len(cd_val): self.add_warning_message( self.tr('Barcode data contains invalid characters.')) is_invalid = True cd_val = sanitized_txt # Check max length max_length = self._current_meta.max_input_length() if max_length != -1 and len(cd_val) > max_length: self.add_warning_message( self.tr('Barcode data cannot exceed the maximum length of ' '{0} characters for {1} linear barcode type.'.format( max_length, self._current_meta.display_name()))) is_invalid = True # Highlight barcode data based on validity of the input self._cd_value_widget.highlight_invalid_data(is_invalid) if is_invalid: # pylint: disable=no-else-return return else: # Remove any warning items if still visible # Check if message_bar has already been initialized if self.message_bar is not None: self.message_bar.clearWidgets() # Set barcode value self._barcode_item.beginCommand(self.tr('Change code value'), QgsLayoutItem.UndoLabelText) self._barcode_item.blockSignals(True) try: self._barcode_item.barcode_type = self._current_meta.type_id() self._barcode_item.code_value = user_value except BarcodeException as bc_ex: self.add_warning_message(str(bc_ex)) is_invalid = True self._barcode_item.blockSignals(False) self._barcode_item.endCommand() def _on_render_text_changed(self, state): """ Slot raised when render_text has been checked/unchecked. """ render_text = False if state == Qt.Checked: render_text = True self._barcode_item.beginCommand(self.tr('Change render text'), QgsLayoutItem.UndoCustomCommand) self._barcode_item.blockSignals(True) self._barcode_item.render_text = render_text self._barcode_item.blockSignals(False) self._barcode_item.endCommand() def _update_gui_values(self): """ Update gui items based on the item properties. """ self._current_meta = self.metadata(self._barcode_item.barcode_type) # Barcode type in combobox self._barcode_cbo.blockSignals(True) self._barcode_cbo.set_current_metadata(self._barcode_item.barcode_type) self._barcode_cbo.blockSignals(False) # Checksum property self._adapt_ui_item_checksum_properties() # Render text property self._chk_render_txt.blockSignals(True) if self._barcode_item.render_text: self._chk_render_txt.setCheckState(Qt.Checked) else: self._chk_render_txt.setCheckState(Qt.Unchecked) self._chk_render_txt.blockSignals(False) # Barcode value (which could also be an expression) self._cd_value_widget.block_value_widget_signals(True) self._cd_value_widget.code_value = self._barcode_item.code_value self._cd_value_widget.value_text_edit.moveCursor( QTextCursor.End, QTextCursor.MoveAnchor) self._cd_value_widget.block_value_widget_signals(False) # Now set barcode data self.set_barcode_data() def add_warning_message(self, msg): """ Add warning message to the layout interface. :param msg: Warning message. :type msg: str """ self.message_bar.pushWarning(self.tr('Linear Barcode'), msg)