class FormWidget(ui_formwidget.Ui_Form, WidgetBase): def __init__(self, parent=None): super(FormWidget, self).__init__(parent) self.setupUi(self) self.form = None self.iconlabel.mouseReleaseEvent = self.change_icon self._currentwidgetid = '' self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.widgetmodel.rowsMoved.connect(self._widget_moved) self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=True) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.layerCombo.setModel(self.formlayers) self.useablewidgets.setModel(self.possiblewidgetsmodel) self.fieldList.setModel(self.fieldsmodel) self.userwidgets.setModel(self.widgetmodel) self.userwidgets.selectionModel().currentChanged.connect(self.load_widget) self.widgetmodel.rowsRemoved.connect(self.set_widget_config_state) self.widgetmodel.rowsInserted.connect(self.set_widget_config_state) self.widgetmodel.modelReset.connect(self.set_widget_config_state) self.addWidgetButton.pressed.connect(self.newwidget) self.addSectionButton.pressed.connect(self.add_section) self.removeWidgetButton.pressed.connect(self.removewidget) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.expressionButton.clicked.connect(self.opendefaultexpression) self.expressionButton_2.clicked.connect(self.opendefaultexpression_advanced) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) for item, data in defaultevents: self.defaultEventsCombo.addItem(item, data) self.loadwidgettypes() self.formLabelText.textChanged.connect(self.form_name_changed) self.newStyleCheck.stateChanged.connect(self.form_style_changed) self.layerCombo.currentIndexChanged.connect(self.layer_updated) # Gross but ok for now. self.blockWidgets = [ self.fieldList, self.nameText, self.sectionNameText, self.useablewidgets, self.hiddenCheck, self.requiredCheck, self.readonlyCombo, self.defaultEventsCombo, self.defaultvalueText, self.defaultLayerCombo, self.defaultFieldCombo, self.defaultValueExpression, self.savevalueCheck ] for widget in self.blockWidgets: self._connect_save_event(widget) self.blockWidgetSignels(True) self.useablewidgets.currentIndexChanged.connect(self.swapwidgetconfig) menu = QMenu("Field Actions") action = menu.addAction("Auto add all fields") action.triggered.connect(self.auto_add_fields) self.addWidgetButton.setMenu(menu) self.addWidgetButton.setPopupMode(QToolButton.MenuButtonPopup) self.defaultLayerCombo.layerChanged.connect(self.defaultFieldCombo.setLayer) self.addEvent.pressed.connect(self.addEventItem) self.btnDeleteForm.pressed.connect(ConfigEvents.deleteForm.emit) def _widget_moved(self, sourceParent, start, end, destParent, destRow): index = self.widgetmodel.index(destRow, 0, destParent) self.userwidgets.setCurrentIndex(index) def on_closing(self): self.blockWidgetSignels(True) def unload_project(self): self.blockWidgetSignels(True) def _connect_save_event(self, widget): if hasattr(widget, "textChanged"): widget.textChanged.connect(self._save_current_widget) if hasattr(widget, "currentIndexChanged"): widget.currentIndexChanged.connect(self._save_current_widget) if hasattr(widget, "stateChanged"): widget.stateChanged.connect(self._save_current_widget) def change_icon(self, *args): """ Change the icon for the form """ icon, _ = QFileDialog.getOpenFileName(self, "Select form icon image", filter="Images (*.png *.svg)") if not icon: return ext = os.path.splitext(icon)[1] shutil.copy(icon, os.path.join(self.form.folder, "icon" + ext)) self.set_icon(self.form.icon) self.treenode.emitDataChanged() def set_icon(self, path): """ Set the forms icon preview :param path: The path to icon. """ pixmap = QPixmap(path) w = self.iconlabel.width() h = self.iconlabel.height() self.iconlabel.setPixmap(pixmap.scaled(w, h, Qt.KeepAspectRatio)) def layer_updated(self, index): """ Called when the forms layer has changed. :param index: The index of the new layer. """ if not self.selected_layer: return self.updatefields(self.selected_layer) def form_style_changed(self, newstyle): """ Called when the form style has changed from label-above style to label-beside style. :param newstyle: True if to use the new label-above style forms. """ self.form.settings['newstyle'] = newstyle self.treenode.emitDataChanged() def form_name_changed(self, text): """ Called when the forms name has changed. Also updates the tree view to reflect the new name. :param text: The new text of the label. :return: """ self.form.settings['label'] = text self.treenode.emitDataChanged() def updatewidgetname(self, index): """ Update the widget name if the field has changed. Doesn't change the name if it has been user set already. :param index: index of the new field. """ # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def opendefaultexpression_advanced(self): """ Open the default expression builder for setting advanced default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultValueExpression.text() dlg.setExpressionText(text) if dlg.exec_(): self.defaultValueExpression.setText(dlg.expressionText()) def opendefaultexpression(self): """ Open the default expression builder for setting default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def formtabchanged(self, index): """ Called when the tab widget changes tab. Normally used to control when to render the form preview on demand. :param index: The index of the new tab. """ # Don't generate the form preview if we are not on the preview tab. if index == 3: self.generate_form_preview() def generate_form_preview(self): """ Create the form preview to show to the user. """ form = self.form.copy() form.settings['widgets'] = list(self.widgetmodel.widgets()) item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) featureform = FeatureForm.from_form(form, form.settings, None, {}) from roam import defaults defaultwidgets = form.widgetswithdefaults() layer = form.QGISLayer try: values = {} feature = next(layer.getFeatures()) defaultvalues = defaults.default_values(defaultwidgets, feature, layer) values.update(defaultvalues) featureform.bindvalues(values) except StopIteration: pass self.frame_2.layout().addWidget(featureform) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ widgets = self.widgetmodel.widgets() for widget in widgets: if 'field' in widget: yield widget['field'] def openformfolder(self, url): """ Open the form folder using the OS file manager. :param url: :return: """ QDesktopServices.openUrl(QUrl.fromLocalFile(self.form.folder)) def loadwidgettypes(self): """ Load all supported widgets into the combobox for the form designer. :return: """ self.useablewidgets.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(widgeticon(widgettype)) self.useablewidgets.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.useablewidgets.blockSignals(False) def set_widget_config_state(self, *args): """ Enable or disable the widget config section based on widget count :param args: Unused. :return: """ haswidgets = self.widgetmodel.rowCount() > 0 self.widgetConfigTabs.setEnabled(haswidgets) def add_section(self): """ Add a new widget section into the form. Widget sections can be used to group widgets on the form together. """ currentindex = self.userwidgets.currentIndex() widget = {"widget": "Section", "name": "default"} index = self.widgetmodel.addwidget(widget, currentindex.parent()) self.userwidgets.setCurrentIndex(index) def newwidget(self, field=None): """ Create a new widget. Tries to match the field type to the right kind of widget as a best guess. """ mapping = {QVariant.String: "Text", QVariant.Int: "Number", QVariant.Double: "Number(Double)", QVariant.ByteArray: "Image", QVariant.Date: "Date", QVariant.DateTime: "Date"} widget = {} if not field: field = self.fieldsmodel.index(0, 0).data(Qt.UserRole) if not field: return widget['field'] = field.name() else: widget['field'] = field.name() try: widget['widget'] = mapping[field.type()] except KeyError: widget['widget'] = 'Text' # Grab the first field. widget['name'] = field.name().replace("_", " ").title() currentindex = self.userwidgets.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.userwidgets.setCurrentIndex(index) def auto_add_fields(self): """ Auto add all fields to the form config. Any missing fields will be added. """ used = list(self.usedfields()) if not self.selected_layer: return for field in self.selected_layer.fields(): if field.name().lower() in used: continue self.newwidget(field) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def set_project(self, project, treenode): """ Set the project for this widget also sets the form from the tree node. :note: This method is called from the parent node when the page and widget is loaded. :param project: The current project.j :param treenode: The current tree node. Can be used to signel a update back to the tree for it to update it self. """ roam.utils.debug("FormWidget: Set Project") self.blockWidgetSignels(True) super(FormWidget, self).set_project(project, treenode) self.formlayers.setSelectLayers(self.project.selectlayers) form = self.treenode.form self.form = form self.setform(self.form) self.blockWidgetSignels(False) def blockWidgetSignels(self, blocking): for widget in self.blockWidgets: widget.blockSignals(blocking) def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): """ Get the first layer from the forms layer combo box """ index = self.formlayers.index(0, 0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): """ Find the layer with the same name in the layer combobox widget """ index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) newstyleform = settings.setdefault('newstyle', True) self.set_icon(form.icon) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.newStyleCheck.setChecked(newstyleform) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) if formtype == "auto": formtype = "Auto Generated" index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.userwidgets.setCurrentIndex(index) # self.load_widget(index, None) for i in reversed(range(self.eventsLayout.count())): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): child = self.eventsLayout.takeAt(i) child.widget().deleteLater() events = settings.get('events', []) self.load_events(events) ## This has overhead so only do it when the tab is active. if self.formtab.currentIndex() == 3: self.generate_form_preview() def load_events(self, events): for event in events: self.addEventItem(data=event) def addEventItem(self, data=None): widget = EventWidget(self.form.QGISLayer, self.widgetmodel, self.eventsWidget) widget.removeItem.connect(self.removeEventItem) widget.set_data(data) self.eventsLayout.addWidget(widget) def removeEventItem(self, widget): child = self.eventsLayout.removeWidget(widget) widget.deleteLater() def swapwidgetconfig(self, index): widgetconfig, _, _ = self.current_config_widget defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def load_widget(self, index, last): """ Update the UI with the config for the current selected widget. """ def get_id(widget_config): if widget_config: return widget_config.get('_id', None) return None widget = index.data(Qt.UserRole) lastdata = last.data(Qt.UserRole) newid = get_id(widget) lastid = get_id(lastdata) if (newid is not None and lastid is not None) and get_id(widget) == get_id(lastdata): return self.blockWidgetSignels(True) if last: roam.utils.debug("Saving last widget") self._save_widget(last) widget = index.data(Qt.UserRole) if not widget: self.blockWidgetSignels(False) return try: roam.utils.debug("Loading widget: {0}".format(get_id(widget))) except KeyError: pass widgettype = widget['widget'] if widgettype == "Section": self.propertiesStack.setCurrentIndex(1) self.sectionNameText.blockSignals(True) name = widget['name'] self.sectionNameText.setText(name) self.sectionNameText.blockSignals(False) return else: self.propertiesStack.setCurrentIndex(0) widgetconfig = WidgetConfig.from_config(widget) field = widget['field'] self._currentwidgetid = widget.setdefault('_id', str(uuid.uuid4())) required = widget.setdefault('required', False) savevalue = widget.setdefault('rememberlastvalue', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) defaultevents = widget.setdefault('default_events', ['capture']) try: data = readonly[0] except IndexError: data = 'never' index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) index = self.defaultEventsCombo.findData(defaultevents) self.defaultEventsCombo.setCurrentIndex(index) self.defaultValueExpression.setText("") self.defaultFieldCombo.setLayer(None) self.defaultFieldCombo.setLayer(None) if widgetconfig.default_type == WidgetConfig.DEFAULT_LAYER_VALUE: self.defaultTab.setCurrentIndex(1) layer = default['layer'] # TODO Handle the case of many layer fall though with defaults # Not sure how to handle this in the UI just yet if isinstance(layer, list): layer = layer[0] else: defaultfield = default['field'] expression = default['expression'] self.logger.info(defaultfield) self.defaultValueExpression.setText(expression) self.logger.debug("Layer from name: {}".format(layer)) layer = roam.api.utils.layer_by_name(layer) self.logger.debug(layer) self.defaultLayerCombo.setLayer(layer) self.defaultFieldCombo.setLayer(layer) self.defaultFieldCombo.setField(defaultfield) else: self.defaultTab.setCurrentIndex(0) self.defaultvalueText.setText(default) self.nameText.setText(name) self.requiredCheck.setChecked(required) self.savevalueCheck.setChecked(savevalue) self.hiddenCheck.setChecked(hidden) if field is not None: index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) index = self.useablewidgets.findText(widgettype) if index > -1: self.useablewidgets.setCurrentIndex(index) config = widget.get('config', {}) self.updatewidgetconfig(config) self.blockWidgetSignels(False) @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.userwidgets.currentIndex() return index.data(Qt.UserRole), index @property def current_config_widget(self): """ Return the selected widget in the widget combo. """ index = self.useablewidgets.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetconfig(self, config): configwidget, _, _ = self.current_config_widget self.setconfigwidget(configwidget, config) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_current_widget) except TypeError: pass self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_current_widget) def _save_current_widget(self, *args): _, index = self.currentuserwidget self._save_widget(index) def _save_widget(self, index): # roam.utils.debug("FormWidget: Save widget") if not self.project: return widgetdata = self._get_widget_config() try: roam.utils.debug("Saving widget {} in project {}".format(widgetdata['_id'], self.project.name)) except KeyError: pass self.widgetmodel.setData(index, widgetdata, Qt.UserRole) def _get_default_config(self): if self.defaultTab.currentIndex() == 0: return self.defaultvalueText.text() else: default = {} default['layer'] = self.defaultLayerCombo.currentLayer().name() default['field'] = self.defaultFieldCombo.currentField() default['expression'] = self.defaultValueExpression.text() default['type'] = 'layer-value' return default def _get_widget_config(self): def current_field(): row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) return field configwidget, _, widgettype = self.current_config_widget if self.propertiesStack.currentIndex() == 1: return {'name': self.sectionNameText.text(), "widget": "Section"} widget = {} widget['field'] = current_field() widget[ 'default-type'] = WidgetConfig.DEFAULT_SIMPLE if self.defaultTab.currentIndex() == 0 else WidgetConfig.DEFAULT_LAYER_VALUE widget['default'] = self._get_default_config() widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['rememberlastvalue'] = self.savevalueCheck.isChecked() widget['_id'] = self._currentwidgetid widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['default_events'] = self.defaultEventsCombo.itemData(self.defaultEventsCombo.currentIndex()) widget['hidden'] = self.hiddenCheck.isChecked() widget['config'] = configwidget.getconfig() return widget @property def selected_layer(self): index = self.formlayers.index(self.layerCombo.currentIndex(), 0) layer = index.data(Qt.UserRole) return layer def write_config(self): roam.utils.debug("Write form config") if not self.selected_layer: return self._save_current_widget() self.form.settings['layer'] = self.selected_layer.name() formtype = self.formtypeCombo.currentText() self.form.settings['type'] = "auto" if formtype == "Auto Generated" else formtype self.form.settings['label'] = self.formLabelText.text() self.form.settings['newstyle'] = self.newStyleCheck.isChecked() self.form.settings['widgets'] = list(self.widgetmodel.widgets()) events = [] for i in range(self.eventsLayout.count()): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): widget = child.widget() eventdata = widget.get_data() events.append(eventdata) self.form.settings['events'] = events
class FormWidget(ui_formwidget.Ui_Form, WidgetBase): def __init__(self, parent=None): super(FormWidget, self).__init__(parent) self.setupUi(self) self.form = None self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=True) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.layerCombo.setModel(self.formlayers) self.useablewidgets.setModel(self.possiblewidgetsmodel) self.fieldList.setModel(self.fieldsmodel) self.userwidgets.setModel(self.widgetmodel) self.userwidgets.selectionModel().currentChanged.connect(self.load_widget) self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable) self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable) self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable) self.addWidgetButton.pressed.connect(self.newwidget) self.removeWidgetButton.pressed.connect(self.removewidget) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.expressionButton.clicked.connect(self.opendefaultexpression) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) self.loadwidgettypes() self.formLabelText.textChanged.connect(self.form_name_changed) self.layerCombo.currentIndexChanged.connect(self.layer_updated) self.fieldList.currentIndexChanged.connect(self._save_current_widget) self.nameText.textChanged.connect(self._save_current_widget) self.useablewidgets.currentIndexChanged.connect(self._save_current_widget) self.useablewidgets.currentIndexChanged.connect(self.swapwidgetconfig) menu = QMenu("Field Actions") action = menu.addAction("Auto add all fields") action.triggered.connect(self.auto_add_fields) self.addWidgetButton.setMenu(menu) self.addWidgetButton.setPopupMode(QToolButton.MenuButtonPopup) def layer_updated(self, index): if not self.selected_layer: return self.updatefields(self.selected_layer) def form_name_changed(self, text): self.form.settings['label'] = self.formLabelText.text() self.treenode.emitDataChanged() def updatewidgetname(self, index): # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def opendefaultexpression(self): layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def formtabchanged(self, index): def setformpreview(form): item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) featureform = FeatureForm.from_form(form, form.settings, None, {}) from roam import defaults defaultwidgets = form.widgetswithdefaults() layer = form.QGISLayer try: values = {} feature = layer.getFeatures().next() defaultvalues = defaults.default_values(defaultwidgets, feature, layer) values.update(defaultvalues) featureform.bindvalues(values) except StopIteration: pass self.frame_2.layout().addWidget(featureform) if index == 1: self.form.settings['widgets'] = list(self.widgetmodel.widgets()) setformpreview(self.form) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ widgets = self.widgetmodel.widgets() for widget in widgets: yield widget['field'] def openformfolder(self, url): QDesktopServices.openUrl(QUrl.fromLocalFile(self.form.folder)) def loadwidgettypes(self): self.useablewidgets.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.useablewidgets.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.useablewidgets.blockSignals(False) def setwidgetconfigvisiable(self, *args): haswidgets = self.widgetmodel.rowCount() > 0 self.widgetConfigTabs.setVisible(haswidgets) def newwidget(self, field=None): """ Create a new widget. The default is a list. """ mapping = {QVariant.String: "Text", QVariant.Int: "Number", QVariant.Double: "Number(Double)", QVariant.ByteArray: "Image", QVariant.Date: "Date", QVariant.DateTime: "Date"} widget = {} if not field: field = self.fieldsmodel.index(0, 0).data(Qt.UserRole) if not field: return widget['field'] = field.name() else: widget['field'] = field.name() try: widget['widget'] = mapping[field.type()] except KeyError: widget['widget'] = 'Text' # Grab the first field. widget['name'] = field.name().replace("_", " ").title() currentindex = self.userwidgets.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.userwidgets.setCurrentIndex(index) def auto_add_fields(self): used = list(self.usedfields()) for field in self.selected_layer.pendingFields(): if field.name() in used: continue self.newwidget(field) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def set_project(self, project, treenode): super(FormWidget, self).set_project(project, treenode) self.formlayers.setSelectLayers(self.project.selectlayers) form = self.treenode.form self.form = form self.setform(self.form) def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): index = self.formlayers.index(0, 0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.userwidgets.setCurrentIndex(index) self.load_widget(index, None) def swapwidgetconfig(self, index): widgetconfig, _, _ = self.current_config_widget defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def load_widget(self, index, last): """ Update the UI with the config for the current selected widget. """ self.fieldList.blockSignals(True) self.nameText.blockSignals(True) self.useablewidgets.blockSignals(True) if last: self._save_widget(last) widget = index.data(Qt.UserRole) if not widget: self.fieldList.blockSignals(False) self.nameText.blockSignals(False) self.useablewidgets.blockSignals(False) return widgettype = widget['widget'] field = widget['field'] required = widget.setdefault('required', False) savevalue = widget.setdefault('rememberlastvalue', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) try: data = readonly[0] except IndexError: data = 'never' index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) if not isinstance(default, dict): self.defaultvalueText.setText(default) self.defaultvalueText.setEnabled(True) self.expressionButton.setEnabled(True) else: # TODO Handle the more advanced default values. self.defaultvalueText.setText("Advanced default set in config") self.defaultvalueText.setEnabled(False) self.expressionButton.setEnabled(False) self.nameText.setText(name) self.requiredCheck.setChecked(required) self.savevalueCheck.setChecked(savevalue) self.hiddenCheck.setChecked(hidden) if field is not None: index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) index = self.useablewidgets.findText(widgettype) if index > -1: self.useablewidgets.setCurrentIndex(index) config = widget.get('config', {}) self.updatewidgetconfig(config) self.fieldList.blockSignals(False) self.nameText.blockSignals(False) self.useablewidgets.blockSignals(False) @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.userwidgets.currentIndex() return index.data(Qt.UserRole), index @property def current_config_widget(self): """ Return the selected widget in the widget combo. """ index = self.useablewidgets.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetconfig(self, config): configwidget, _, _ = self.current_config_widget self.setconfigwidget(configwidget, config) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_current_widget) except TypeError: pass self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_current_widget) def _save_current_widget(self, *args): _, index = self.currentuserwidget self._save_widget(index) def _save_widget(self, index): widgetdata = self._get_widget_config() self.widgetmodel.setData(index, widgetdata, Qt.UserRole) def _get_widget_config(self): def current_field(): row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) return field configwidget, _, widgettype = self.current_config_widget widget = {} widget['field'] = current_field() widget['default'] = self.defaultvalueText.text() widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['rememberlastvalue'] = self.savevalueCheck.isChecked() widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['hidden'] = self.hiddenCheck.isChecked() widget['config'] = configwidget.getconfig() return widget @property def selected_layer(self): index = self.formlayers.index(self.layerCombo.currentIndex(), 0) layer = index.data(Qt.UserRole) return layer def write_config(self): if not self.selected_layer: return self._save_current_widget() self.form.settings['layer'] = self.selected_layer.name() self.form.settings['type'] = self.formtypeCombo.currentText() self.form.settings['label'] = self.formLabelText.text() self.form.settings['widgets'] = list(self.widgetmodel.widgets())
class ProjectWidget(Ui_Form, QWidget): SampleWidgetRole = Qt.UserRole + 1 projectsaved = pyqtSignal() projectupdated = pyqtSignal() projectloaded = pyqtSignal(object) selectlayersupdated = pyqtSignal(list) projectlocationchanged = pyqtSignal(str) def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) self.setupUi(self) self.project = None self.mapisloaded = False self.bar = None self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling()) self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=False) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.selectlayermodel = CaptureLayersModel(watchregistry=False) self.selectlayerfilter = LayerTypeFilter() self.selectlayerfilter.setSourceModel(self.selectlayermodel) self.selectlayermodel.dataChanged.connect(self.selectlayerschanged) self.layerCombo.setModel(self.formlayers) self.widgetCombo.setModel(self.possiblewidgetsmodel) self.selectLayers.setModel(self.selectlayerfilter) self.selectLayers_2.setModel(self.selectlayerfilter) self.fieldList.setModel(self.fieldsmodel) self.widgetlist.setModel(self.widgetmodel) self.widgetlist.selectionModel().currentChanged.connect(self.updatecurrentwidget) self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable) self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable) self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable) self.titleText.textChanged.connect(self.updatetitle) QgsProject.instance().readProject.connect(self._readproject) self.loadwidgettypes() self.addWidgetButton.pressed.connect(self.newwidget) self.removeWidgetButton.pressed.connect(self.removewidget) self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__)) self.openProjectFolderButton.pressed.connect(self.openprojectfolder) self.openinQGISButton.pressed.connect(self.openinqgis) self.filewatcher = QFileSystemWatcher() self.filewatcher.fileChanged.connect(self.qgisprojectupdated) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.projectupdatedlabel.linkActivated.connect(self.reloadproject) self.projectupdatedlabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) self.expressionButton.clicked.connect(self.opendefaultexpression) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) self.setpage(4) self.form = None self.projectlocations.currentIndexChanged[str].connect(self.projectlocationchanged.emit) def setaboutinfo(self): self.versionLabel.setText(roam.__version__) self.qgisapiLabel.setText(str(QGis.QGIS_VERSION)) def checkcapturelayers(self): haslayers = self.project.hascapturelayers() self.formslayerlabel.setVisible(not haslayers) return haslayers def opendefaultexpression(self): layer = self.currentform.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def openformfolder(self, url): openfolder(url) def selectlayerschanged(self, *args): self.formlayers.setSelectLayers(self.project.selectlayers) self.checkcapturelayers() self.selectlayersupdated.emit(self.project.selectlayers) def formtabchanged(self, index): # preview if index == 1: self.form.settings['widgets'] = list(self.widgetmodel.widgets()) self.setformpreview(self.form) def setprojectfolders(self, folders): for folder in folders: self.projectlocations.addItem(folder) def setpage(self, page): self.stackedWidget.setCurrentIndex(page) def reloadproject(self, *args): self.setproject(self.project) def qgisprojectupdated(self, path): self.projectupdatedlabel.show() self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> " "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>") def openinqgis(self): projectfile = self.project.projectfile qgislocation = r'C:\OSGeo4W\bin\qgis.bat' qgislocation = roam.config.settings.setdefault('configmanager', {}) \ .setdefault('qgislocation', qgislocation) try: openqgis(projectfile, qgislocation) except WindowsError: self.bar.pushMessage("Looks like I couldn't find QGIS", "Check qgislocation in settings.config", QgsMessageBar.WARNING) def openprojectfolder(self): folder = self.project.folder openfolder(folder) def setwidgetconfigvisiable(self, *args): haswidgets = self.widgetmodel.rowCount() > 0 self.widgetframe.setEnabled(haswidgets) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def newwidget(self): """ Create a new widget. The default is a list. """ widget = {} widget['widget'] = 'List' # Grab the first field. widget['field'] = self.fieldsmodel.index(0, 0).data(QgsFieldModel.FieldNameRole) currentindex = self.widgetlist.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.widgetlist.setCurrentIndex(index) def loadwidgettypes(self): self.widgetCombo.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.widgetCombo.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.widgetCombo.blockSignals(False) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ for widget in self.currentform.widgets: yield widget['field'] @property def currentform(self): """ Return the current selected form. """ return self.form @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.widgetlist.currentIndex() return index.data(Qt.UserRole), index @property def currentwidgetconfig(self): """ Return the selected widget in the widget combo. """ index = self.widgetCombo.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetname(self, index): # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def _save_widgetfield(self, index): """ Save the selected field for the current widget. Shows a error if the field is already used but will allow the user to still set it in the case of extra logic for that field in the forms Python logic. """ widget, index = self.currentuserwidget row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) showwarning = field in self.usedfields() self.fieldwarninglabel.setVisible(showwarning) widget['field'] = field self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectedwidget(self, index): configwidget, index, widgettype = self.currentwidgetconfig widget, index = self.currentuserwidget if not widget: return widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['config'] = configwidget.getconfig() widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['hidden'] = self.hiddenCheck.isChecked() self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_default(self): widget, index = self.currentuserwidget default = self.defaultvalueText.text() widget['default'] = default self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectionlayers(self, index, layer, value): config = self.project.settings self.selectlayermodel.dataChanged.emit(index, index) def _save_formtype(self, index): formtype = self.formtypeCombo.currentText() form = self.currentform form.settings['type'] = formtype def _save_formname(self, text): """ Save the form label to the settings file. """ try: form = self.currentform if form is None: return form.settings['label'] = text self.projectupdated.emit() except IndexError: return def _save_layer(self, index): """ Save the selected layer to the settings file. """ index = self.formlayers.index(index, 0) layer = index.data(Qt.UserRole) if not layer: return form = self.currentform if form is None: return form.settings['layer'] = layer.name() self.updatefields(layer) def setsplash(self, splash): pixmap = QPixmap(splash) w = self.splashlabel.width() h = self.splashlabel.height() self.splashlabel.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio)) def setproject(self, project, loadqgis=True): """ Set the widgets active project. """ self.disconnectsignals() self.mapisloaded = False self.filewatcher.removePaths(self.filewatcher.files()) self.projectupdatedlabel.hide() self._closeqgisproject() if project.valid: self.startsettings = copy.deepcopy(project.settings) self.project = project self.projectlabel.setText(project.name) self.versionText.setText(project.version) self.selectlayermodel.config = project.settings self.formlayers.setSelectLayers(self.project.selectlayers) self.setsplash(project.splash) self.loadqgisproject(project, self.project.projectfile) self.filewatcher.addPath(self.project.projectfile) self.projectloaded.emit(self.project) def loadqgisproject(self, project, projectfile): QDir.setCurrent(os.path.dirname(project.projectfile)) fileinfo = QFileInfo(project.projectfile) QgsProject.instance().read(fileinfo) def _closeqgisproject(self): if self.canvas.isDrawing(): return self.canvas.freeze(True) self.formlayersmodel.removeall() self.selectlayermodel.removeall() QgsMapLayerRegistry.instance().removeAllMapLayers() self.canvas.freeze(False) def loadmap(self): if self.mapisloaded: return # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2. # Refresh will stop the canvas timer # Repaint will redraw the widget. # loadmap is only called once per project load so it's safe to do this here. self.canvas.refresh() self.canvas.repaint() parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile) canvasnode = parser.canvasnode self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) self.canvas.updateScale() self.canvas.refresh() self.mapisloaded = True def _readproject(self, doc): self.formlayersmodel.refresh() self.selectlayermodel.refresh() self._updateforproject(self.project) def _updateforproject(self, project): self.titleText.setText(project.name) self.descriptionText.setPlainText(project.description) def swapwidgetconfig(self, index): widgetconfig, _, _ = self.currentwidgetconfig defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def updatetitle(self, text): self.project.settings['title'] = text self.projectlabel.setText(text) self.projectupdated.emit() def updatewidgetconfig(self, config): widgetconfig, index, widgettype = self.currentwidgetconfig self.setconfigwidget(widgetconfig, config) def setformpreview(self, form): def removewidget(): item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) removewidget() featureform = FeatureForm.from_form(form, form.settings, None, {}) self.frame_2.layout().addWidget(featureform) def connectsignals(self): self.formLabelText.textChanged.connect(self._save_formname) self.layerCombo.currentIndexChanged.connect(self._save_layer) self.formtypeCombo.currentIndexChanged.connect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.connect(self._save_widgetfield) self.requiredCheck.toggled.connect(self._save_selectedwidget) self.defaultvalueText.textChanged.connect(self._save_default) self.widgetCombo.currentIndexChanged.connect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.connect(self.swapwidgetconfig) self.nameText.textChanged.connect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.connect(self._save_selectedwidget) self.hiddenCheck.toggled.connect(self._save_selectedwidget) def disconnectsignals(self): try: self.formLabelText.textChanged.disconnect(self._save_formname) self.layerCombo.currentIndexChanged.disconnect(self._save_layer) self.formtypeCombo.currentIndexChanged.disconnect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.disconnect(self._save_widgetfield) self.requiredCheck.toggled.disconnect(self._save_selectedwidget) self.defaultvalueText.textChanged.disconnect(self._save_default) self.widgetCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.disconnect(self.swapwidgetconfig) self.nameText.textChanged.disconnect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.hiddenCheck.toggled.disconnect(self._save_selectedwidget) except TypeError: pass def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): index = self.formlayers.index(0,0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer self.disconnectsignals() self.form = form settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.widgetlist.setCurrentIndex(index) self.updatecurrentwidget(index, None) self.connectsignals() def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_selectedwidget) except TypeError: pass #self.descriptionLabel.setText(configwidget.description) self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_selectedwidget) def updatecurrentwidget(self, index, _): """ Update the UI with the config for the current selected widget. """ if not index.isValid(): return widget = index.data(Qt.UserRole) widgettype = widget['widget'] field = widget['field'] required = widget.setdefault('required', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) try: data = readonly[0] except: data = 'never' self.readonlyCombo.blockSignals(True) index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) self.readonlyCombo.blockSignals(False) self.defaultvalueText.blockSignals(True) if not isinstance(default, dict): self.defaultvalueText.setText(default) else: # TODO Handle the more advanced default values. pass self.defaultvalueText.blockSignals(False) self.nameText.blockSignals(True) self.nameText.setText(name) self.nameText.blockSignals(False) self.requiredCheck.blockSignals(True) self.requiredCheck.setChecked(required) self.requiredCheck.blockSignals(False) self.hiddenCheck.blockSignals(True) self.hiddenCheck.setChecked(hidden) self.hiddenCheck.blockSignals(False) if not field is None: self.fieldList.blockSignals(True) index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) self.fieldList.blockSignals(False) index = self.widgetCombo.findText(widgettype) self.widgetCombo.blockSignals(True) if index > -1: self.widgetCombo.setCurrentIndex(index) self.widgetCombo.blockSignals(False) self.updatewidgetconfig(config=widget.setdefault('config', {})) def _saveproject(self): """ Save the project config to disk. """ title = self.titleText.text() description = self.descriptionText.toPlainText() version = str(self.versionText.text()) settings = self.project.settings settings['title'] = title settings['description'] = description settings['version'] = version form = self.currentform if form: form.settings['widgets'] = list(self.widgetmodel.widgets()) logger.debug(form.settings) self.project.save() self.projectsaved.emit()
class ProjectWidget(Ui_Form, QWidget): SampleWidgetRole = Qt.UserRole + 1 projectsaved = pyqtSignal() projectupdated = pyqtSignal() projectloaded = pyqtSignal(object) selectlayersupdated = pyqtSignal(list) def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) self.setupUi(self) self.project = None self.mapisloaded = False self.bar = None self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling()) self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=False) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.selectlayermodel = CaptureLayersModel(watchregistry=False) self.selectlayerfilter = LayerTypeFilter() self.selectlayerfilter.setSourceModel(self.selectlayermodel) self.selectlayermodel.dataChanged.connect(self.selectlayerschanged) self.layerCombo.setModel(self.formlayers) self.widgetCombo.setModel(self.possiblewidgetsmodel) self.selectLayers.setModel(self.selectlayerfilter) self.selectLayers_2.setModel(self.selectlayerfilter) self.fieldList.setModel(self.fieldsmodel) self.widgetlist.setModel(self.widgetmodel) self.widgetlist.selectionModel().currentChanged.connect(self.updatecurrentwidget) self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable) self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable) self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable) self.titleText.textChanged.connect(self.updatetitle) QgsProject.instance().readProject.connect(self._readproject) self.loadwidgettypes() self.addWidgetButton.pressed.connect(self.newwidget) self.removeWidgetButton.pressed.connect(self.removewidget) self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__)) self.openProjectFolderButton.pressed.connect(self.openprojectfolder) self.openinQGISButton.pressed.connect(self.openinqgis) self.filewatcher = QFileSystemWatcher() self.filewatcher.fileChanged.connect(self.qgisprojectupdated) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.projectupdatedlabel.linkActivated.connect(self.reloadproject) self.projectupdatedlabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) self.expressionButton.clicked.connect(self.opendefaultexpression) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) self.setpage(4) self.form = None def setaboutinfo(self): self.versionLabel.setText(roam.__version__) self.qgisapiLabel.setText(str(QGis.QGIS_VERSION)) def checkcapturelayers(self): haslayers = self.project.hascapturelayers() self.formslayerlabel.setVisible(not haslayers) return haslayers def opendefaultexpression(self): layer = self.currentform.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def openformfolder(self, url): openfolder(url) def selectlayerschanged(self, *args): self.formlayers.setSelectLayers(self.project.selectlayers) self.checkcapturelayers() self.selectlayersupdated.emit(self.project.selectlayers) def formtabchanged(self, index): # preview if index == 1: self.form.settings['widgets'] = list(self.widgetmodel.widgets()) self.setformpreview(self.form) def setpage(self, page): self.stackedWidget.setCurrentIndex(page) def reloadproject(self, *args): self.setproject(self.project) def qgisprojectupdated(self, path): self.projectupdatedlabel.show() self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> " "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>") def openinqgis(self): projectfile = self.project.projectfile qgislocation = r'C:\OSGeo4W\bin\qgis.bat' qgislocation = roam.config.settings.setdefault('configmanager', {}) \ .setdefault('qgislocation', qgislocation) try: openqgis(projectfile, qgislocation) except WindowsError: self.bar.pushMessage("Looks like I couldn't find QGIS", "Check qgislocation in roam.config", QgsMessageBar.WARNING) def openprojectfolder(self): folder = self.project.folder openfolder(folder) def setwidgetconfigvisiable(self, *args): haswidgets = self.widgetmodel.rowCount() > 0 self.widgetframe.setEnabled(haswidgets) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def newwidget(self): """ Create a new widget. The default is a list. """ widget = {} widget['widget'] = 'Text' # Grab the first field. widget['field'] = self.fieldsmodel.index(0, 0).data(QgsFieldModel.FieldNameRole) currentindex = self.widgetlist.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.widgetlist.setCurrentIndex(index) def loadwidgettypes(self): self.widgetCombo.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.widgetCombo.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.widgetCombo.blockSignals(False) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ for widget in self.currentform.widgets: yield widget['field'] @property def currentform(self): """ Return the current selected form. """ return self.form @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.widgetlist.currentIndex() return index.data(Qt.UserRole), index @property def currentwidgetconfig(self): """ Return the selected widget in the widget combo. """ index = self.widgetCombo.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetname(self, index): # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def _save_widgetfield(self, index): """ Save the selected field for the current widget. Shows a error if the field is already used but will allow the user to still set it in the case of extra logic for that field in the forms Python logic. """ widget, index = self.currentuserwidget row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) showwarning = field in self.usedfields() self.fieldwarninglabel.setVisible(showwarning) widget['field'] = field self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectedwidget(self, index): configwidget, index, widgettype = self.currentwidgetconfig widget, index = self.currentuserwidget if not widget: return widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['config'] = configwidget.getconfig() widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['hidden'] = self.hiddenCheck.isChecked() self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_default(self): widget, index = self.currentuserwidget default = self.defaultvalueText.text() widget['default'] = default self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectionlayers(self, index, layer, value): config = self.project.settings self.selectlayermodel.dataChanged.emit(index, index) def _save_formtype(self, index): formtype = self.formtypeCombo.currentText() form = self.currentform form.settings['type'] = formtype def _save_formname(self, text): """ Save the form label to the settings file. """ try: form = self.currentform if form is None: return form.settings['label'] = text self.projectupdated.emit() except IndexError: return def _save_layer(self, index): """ Save the selected layer to the settings file. """ index = self.formlayers.index(index, 0) layer = index.data(Qt.UserRole) if not layer: return form = self.currentform if form is None: return form.settings['layer'] = layer.name() self.updatefields(layer) def setsplash(self, splash): pixmap = QPixmap(splash) w = self.splashlabel.width() h = self.splashlabel.height() self.splashlabel.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio)) def setproject(self, project, loadqgis=True): """ Set the widgets active project. """ self.disconnectsignals() self.mapisloaded = False self.filewatcher.removePaths(self.filewatcher.files()) self.projectupdatedlabel.hide() self._closeqgisproject() if project.valid: self.startsettings = copy.deepcopy(project.settings) self.project = project self.projectlabel.setText(project.name) self.versionText.setText(project.version) self.selectlayermodel.config = project.settings self.formlayers.setSelectLayers(self.project.selectlayers) self.setsplash(project.splash) self.loadqgisproject(project, self.project.projectfile) self.filewatcher.addPath(self.project.projectfile) self.projectloaded.emit(self.project) def loadqgisproject(self, project, projectfile): QDir.setCurrent(os.path.dirname(project.projectfile)) fileinfo = QFileInfo(project.projectfile) QgsProject.instance().read(fileinfo) def _closeqgisproject(self): if self.canvas.isDrawing(): return self.canvas.freeze(True) self.formlayersmodel.removeall() self.selectlayermodel.removeall() QgsMapLayerRegistry.instance().removeAllMapLayers() self.canvas.freeze(False) def loadmap(self): if self.mapisloaded: return # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2. # Refresh will stop the canvas timer # Repaint will redraw the widget. # loadmap is only called once per project load so it's safe to do this here. self.canvas.refresh() self.canvas.repaint() parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile) canvasnode = parser.canvasnode self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) self.canvas.updateScale() self.canvas.refresh() self.mapisloaded = True def _readproject(self, doc): self.formlayersmodel.refresh() self.selectlayermodel.refresh() self._updateforproject(self.project) def _updateforproject(self, project): self.titleText.setText(project.name) self.descriptionText.setPlainText(project.description) def swapwidgetconfig(self, index): widgetconfig, _, _ = self.currentwidgetconfig defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def updatetitle(self, text): self.project.settings['title'] = text self.projectlabel.setText(text) self.projectupdated.emit() def updatewidgetconfig(self, config): widgetconfig, index, widgettype = self.currentwidgetconfig self.setconfigwidget(widgetconfig, config) def setformpreview(self, form): def removewidget(): item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) removewidget() featureform = FeatureForm.from_form(form, form.settings, None, {}) self.frame_2.layout().addWidget(featureform) def connectsignals(self): self.formLabelText.textChanged.connect(self._save_formname) self.layerCombo.currentIndexChanged.connect(self._save_layer) self.formtypeCombo.currentIndexChanged.connect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.connect(self._save_widgetfield) self.requiredCheck.toggled.connect(self._save_selectedwidget) self.defaultvalueText.textChanged.connect(self._save_default) self.widgetCombo.currentIndexChanged.connect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.connect(self.swapwidgetconfig) self.nameText.textChanged.connect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.connect(self._save_selectedwidget) self.hiddenCheck.toggled.connect(self._save_selectedwidget) def disconnectsignals(self): try: self.formLabelText.textChanged.disconnect(self._save_formname) self.layerCombo.currentIndexChanged.disconnect(self._save_layer) self.formtypeCombo.currentIndexChanged.disconnect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.disconnect(self._save_widgetfield) self.requiredCheck.toggled.disconnect(self._save_selectedwidget) self.defaultvalueText.textChanged.disconnect(self._save_default) self.widgetCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.disconnect(self.swapwidgetconfig) self.nameText.textChanged.disconnect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.hiddenCheck.toggled.disconnect(self._save_selectedwidget) except TypeError: pass def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): index = self.formlayers.index(0,0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer self.disconnectsignals() self.form = form settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.widgetlist.setCurrentIndex(index) self.updatecurrentwidget(index, None) self.connectsignals() def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_selectedwidget) except TypeError: pass #self.descriptionLabel.setText(configwidget.description) self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_selectedwidget) def updatecurrentwidget(self, index, _): """ Update the UI with the config for the current selected widget. """ if not index.isValid(): return widget = index.data(Qt.UserRole) widgettype = widget['widget'] field = widget['field'] required = widget.setdefault('required', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) try: data = readonly[0] except: data = 'never' self.readonlyCombo.blockSignals(True) index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) self.readonlyCombo.blockSignals(False) self.defaultvalueText.blockSignals(True) if not isinstance(default, dict): self.defaultvalueText.setText(default) self.defaultvalueText.setEnabled(True) self.expressionButton.setEnabled(True) else: # TODO Handle the more advanced default values. self.defaultvalueText.setText("Advanced default set in config") self.defaultvalueText.setEnabled(False) self.expressionButton.setEnabled(False) self.defaultvalueText.blockSignals(False) self.nameText.blockSignals(True) self.nameText.setText(name) self.nameText.blockSignals(False) self.requiredCheck.blockSignals(True) self.requiredCheck.setChecked(required) self.requiredCheck.blockSignals(False) self.hiddenCheck.blockSignals(True) self.hiddenCheck.setChecked(hidden) self.hiddenCheck.blockSignals(False) if not field is None: self.fieldList.blockSignals(True) index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) self.fieldList.blockSignals(False) index = self.widgetCombo.findText(widgettype) self.widgetCombo.blockSignals(True) if index > -1: self.widgetCombo.setCurrentIndex(index) self.widgetCombo.blockSignals(False) self.updatewidgetconfig(config=widget.setdefault('config', {})) def _saveproject(self): """ Save the project config to disk. """ title = self.titleText.text() description = self.descriptionText.toPlainText() version = str(self.versionText.text()) settings = self.project.settings settings['title'] = title settings['description'] = description settings['version'] = version form = self.currentform if form: form.settings['widgets'] = list(self.widgetmodel.widgets()) logger.debug(form.settings) self.project.save() self.projectsaved.emit()
class FormWidget(ui_formwidget.Ui_Form, WidgetBase): def __init__(self, parent=None): super(FormWidget, self).__init__(parent) self.setupUi(self) self.form = None self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=True) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.layerCombo.setModel(self.formlayers) self.useablewidgets.setModel(self.possiblewidgetsmodel) self.fieldList.setModel(self.fieldsmodel) self.userwidgets.setModel(self.widgetmodel) self.userwidgets.selectionModel().currentChanged.connect(self.load_widget) self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable) self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable) self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable) self.addWidgetButton.pressed.connect(self.newwidget) self.removeWidgetButton.pressed.connect(self.removewidget) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.expressionButton.clicked.connect(self.opendefaultexpression) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) self.loadwidgettypes() self.formLabelText.textChanged.connect(self.form_name_changed) self.layerCombo.currentIndexChanged.connect(self.layer_updated) self.fieldList.currentIndexChanged.connect(self._save_current_widget) self.nameText.textChanged.connect(self._save_current_widget) self.useablewidgets.currentIndexChanged.connect(self._save_current_widget) self.useablewidgets.currentIndexChanged.connect(self.swapwidgetconfig) menu = QMenu("Field Actions") action = menu.addAction("Auto add all fields") action.triggered.connect(self.auto_add_fields) self.addWidgetButton.setMenu(menu) self.addWidgetButton.setPopupMode(QToolButton.MenuButtonPopup) self.defaultLayerCombo.layerChanged.connect(self.default_layer_changed) def default_layer_changed(self, layer): self.defaultFieldCombo.setLayer(layer) def layer_updated(self, index): if not self.selected_layer: return self.updatefields(self.selected_layer) def form_name_changed(self, text): self.form.settings['label'] = self.formLabelText.text() self.treenode.emitDataChanged() def updatewidgetname(self, index): # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def opendefaultexpression(self): layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def formtabchanged(self, index): def setformpreview(form): item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) featureform = FeatureForm.from_form(form, form.settings, None, {}) from roam import defaults defaultwidgets = form.widgetswithdefaults() layer = form.QGISLayer try: values = {} feature = layer.getFeatures().next() defaultvalues = defaults.default_values(defaultwidgets, feature, layer) values.update(defaultvalues) featureform.bindvalues(values) except StopIteration: pass self.frame_2.layout().addWidget(featureform) if index == 1: self.form.settings['widgets'] = list(self.widgetmodel.widgets()) setformpreview(self.form) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ widgets = self.widgetmodel.widgets() for widget in widgets: yield widget['field'] def openformfolder(self, url): QDesktopServices.openUrl(QUrl.fromLocalFile(self.form.folder)) def loadwidgettypes(self): self.useablewidgets.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.useablewidgets.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.useablewidgets.blockSignals(False) def setwidgetconfigvisiable(self, *args): haswidgets = self.widgetmodel.rowCount() > 0 self.widgetConfigTabs.setVisible(haswidgets) def newwidget(self, field=None): """ Create a new widget. The default is a list. """ mapping = {QVariant.String: "Text", QVariant.Int: "Number", QVariant.Double: "Number(Double)", QVariant.ByteArray: "Image", QVariant.Date: "Date", QVariant.DateTime: "Date"} widget = {} if not field: field = self.fieldsmodel.index(0, 0).data(Qt.UserRole) if not field: return widget['field'] = field.name() else: widget['field'] = field.name() try: widget['widget'] = mapping[field.type()] except KeyError: widget['widget'] = 'Text' # Grab the first field. widget['name'] = field.name().replace("_", " ").title() currentindex = self.userwidgets.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.userwidgets.setCurrentIndex(index) def auto_add_fields(self): used = list(self.usedfields()) for field in self.selected_layer.pendingFields(): if field.name() in used: continue self.newwidget(field) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def set_project(self, project, treenode): super(FormWidget, self).set_project(project, treenode) self.formlayers.setSelectLayers(self.project.selectlayers) form = self.treenode.form self.form = form self.setform(self.form) def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): index = self.formlayers.index(0, 0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.userwidgets.setCurrentIndex(index) self.load_widget(index, None) def swapwidgetconfig(self, index): widgetconfig, _, _ = self.current_config_widget defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def load_widget(self, index, last): """ Update the UI with the config for the current selected widget. """ self.fieldList.blockSignals(True) self.nameText.blockSignals(True) self.useablewidgets.blockSignals(True) if last: self._save_widget(last) widget = index.data(Qt.UserRole) if not widget: self.fieldList.blockSignals(False) self.nameText.blockSignals(False) self.useablewidgets.blockSignals(False) return widgettype = widget['widget'] field = widget['field'] required = widget.setdefault('required', False) savevalue = widget.setdefault('rememberlastvalue', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) try: data = readonly[0] except IndexError: data = 'never' index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) if not isinstance(default, dict): self.defaultTab.setCurrentIndex(0) self.defaultvalueText.setText(default) else: self.defaultTab.setCurrentIndex(1) layer = default['layer'] # TODO Handle the case of many layer fall though with defaults # Not sure how to handle this in the UI just yet if isinstance(layer, list): layer = layer[0] if isinstance(layer, basestring): field = default['field'] expression = default['expression'] self.defaultValueExpression.setText(expression) layer = roam.api.utils.layer_by_name(layer) self.defaultLayerCombo.setLayer(layer) self.defaultFieldCombo.setLayer(layer) self.defaultFieldCombo.setField(field) self.nameText.setText(name) self.requiredCheck.setChecked(required) self.savevalueCheck.setChecked(savevalue) self.hiddenCheck.setChecked(hidden) if field is not None: index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) index = self.useablewidgets.findText(widgettype) if index > -1: self.useablewidgets.setCurrentIndex(index) config = widget.get('config', {}) self.updatewidgetconfig(config) self.fieldList.blockSignals(False) self.nameText.blockSignals(False) self.useablewidgets.blockSignals(False) @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.userwidgets.currentIndex() return index.data(Qt.UserRole), index @property def current_config_widget(self): """ Return the selected widget in the widget combo. """ index = self.useablewidgets.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetconfig(self, config): configwidget, _, _ = self.current_config_widget self.setconfigwidget(configwidget, config) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_current_widget) except TypeError: pass self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_current_widget) def _save_current_widget(self, *args): _, index = self.currentuserwidget self._save_widget(index) def _save_widget(self, index): widgetdata = self._get_widget_config() self.widgetmodel.setData(index, widgetdata, Qt.UserRole) def _get_default_config(self): if self.defaultTab.currentIndex() == 0: return self.defaultvalueText.text() else: default = {} default['layer'] = self.defaultLayerCombo.currentLayer().name() default['field'] = self.defaultFieldCombo.currentField() default['expression'] = self.defaultValueExpression.text() default['type'] = 'layer-value' return default def _get_widget_config(self): def current_field(): row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) return field configwidget, _, widgettype = self.current_config_widget widget = {} widget['field'] = current_field() widget['default'] = self._get_default_config() widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['rememberlastvalue'] = self.savevalueCheck.isChecked() widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['hidden'] = self.hiddenCheck.isChecked() widget['config'] = configwidget.getconfig() return widget @property def selected_layer(self): index = self.formlayers.index(self.layerCombo.currentIndex(), 0) layer = index.data(Qt.UserRole) return layer def write_config(self): if not self.selected_layer: return self._save_current_widget() self.form.settings['layer'] = self.selected_layer.name() self.form.settings['type'] = self.formtypeCombo.currentText() self.form.settings['label'] = self.formLabelText.text() self.form.settings['widgets'] = list(self.widgetmodel.widgets())
class FormWidget(ui_formwidget.Ui_Form, WidgetBase): def __init__(self, parent=None): super(FormWidget, self).__init__(parent) self.setupUi(self) self.form = None self.iconlabel.mouseReleaseEvent = self.change_icon self._currentwidgetid = '' self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=True) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.layerCombo.setModel(self.formlayers) self.useablewidgets.setModel(self.possiblewidgetsmodel) self.fieldList.setModel(self.fieldsmodel) self.userwidgets.setModel(self.widgetmodel) self.userwidgets.selectionModel().currentChanged.connect(self.load_widget) self.widgetmodel.rowsRemoved.connect(self.set_widget_config_state) self.widgetmodel.rowsInserted.connect(self.set_widget_config_state) self.widgetmodel.modelReset.connect(self.set_widget_config_state) self.addWidgetButton.pressed.connect(self.newwidget) self.addSectionButton.pressed.connect(self.add_section) self.removeWidgetButton.pressed.connect(self.removewidget) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.expressionButton.clicked.connect(self.opendefaultexpression) self.expressionButton_2.clicked.connect(self.opendefaultexpression_advanced) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) for item, data in defaultevents: self.defaultEventsCombo.addItem(item, data) self.loadwidgettypes() self.formLabelText.textChanged.connect(self.form_name_changed) self.newStyleCheck.stateChanged.connect(self.form_style_changed) self.layerCombo.currentIndexChanged.connect(self.layer_updated) # Gross but ok for now. self.blockWidgets = [ self.fieldList, self.nameText, self.sectionNameText, self.useablewidgets, self.hiddenCheck, self.requiredCheck, self.readonlyCombo, self.defaultEventsCombo, self.defaultvalueText, self.defaultLayerCombo, self.defaultFieldCombo, self.defaultValueExpression, self.savevalueCheck ] for widget in self.blockWidgets: self._connect_save_event(widget) self.blockWidgetSignels(True) self.useablewidgets.currentIndexChanged.connect(self.swapwidgetconfig) menu = QMenu("Field Actions") action = menu.addAction("Auto add all fields") action.triggered.connect(self.auto_add_fields) self.addWidgetButton.setMenu(menu) self.addWidgetButton.setPopupMode(QToolButton.MenuButtonPopup) self.defaultLayerCombo.layerChanged.connect(self.defaultFieldCombo.setLayer) self.addEvent.pressed.connect(self.addEventItem) self.btnDeleteForm.pressed.connect(ConfigEvents.deleteForm.emit) def unload_project(self): print "UNLOAD PROJECT!!!" self.blockWidgetSignels(True) def _connect_save_event(self, widget): if hasattr(widget, "textChanged"): widget.textChanged.connect(self._save_current_widget) if hasattr(widget, "currentIndexChanged"): widget.currentIndexChanged.connect(self._save_current_widget) if hasattr(widget, "stateChanged"): widget.stateChanged.connect(self._save_current_widget) def change_icon(self, *args): """ Change the icon for the form """ icon = QFileDialog.getOpenFileName(self, "Select form icon image", filter="Images (*.png *.svg)") if not icon: return ext = os.path.splitext(icon)[1] shutil.copy(icon, os.path.join(self.form.folder, "icon" + ext)) self.set_icon(self.form.icon) self.treenode.emitDataChanged() def set_icon(self, path): """ Set the forms icon preview :param path: The path to icon. """ pixmap = QPixmap(path) w = self.iconlabel.width() h = self.iconlabel.height() self.iconlabel.setPixmap(pixmap.scaled(w, h, Qt.KeepAspectRatio)) def layer_updated(self, index): """ Called when the forms layer has changed. :param index: The index of the new layer. """ if not self.selected_layer: return self.updatefields(self.selected_layer) def form_style_changed(self, newstyle): """ Called when the form style has changed from label-above style to label-beside style. :param newstyle: True if to use the new label-above style forms. """ self.form.settings['newstyle'] = newstyle self.treenode.emitDataChanged() def form_name_changed(self, text): """ Called when the forms name has changed. Also updates the tree view to reflect the new name. :param text: The new text of the label. :return: """ self.form.settings['label'] = text self.treenode.emitDataChanged() def updatewidgetname(self, index): """ Update the widget name if the field has changed. Doesn't change the name if it has been user set already. :param index: index of the new field. """ # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def opendefaultexpression_advanced(self): """ Open the default expression builder for setting advanced default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultValueExpression.text() dlg.setExpressionText(text) if dlg.exec_(): self.defaultValueExpression.setText(dlg.expressionText()) def opendefaultexpression(self): """ Open the default expression builder for setting default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def formtabchanged(self, index): """ Called when the tab widget changes tab. Normally used to control when to render the form preview on demand. :param index: The index of the new tab. """ # Don't generate the form preview if we are not on the preview tab. if index == 3: self.generate_form_preview() def generate_form_preview(self): """ Create the form preview to show to the user. """ form = self.form.copy() form.settings['widgets'] = list(self.widgetmodel.widgets()) item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) featureform = FeatureForm.from_form(form, form.settings, None, {}) from roam import defaults defaultwidgets = form.widgetswithdefaults() layer = form.QGISLayer try: values = {} feature = layer.getFeatures().next() defaultvalues = defaults.default_values(defaultwidgets, feature, layer) values.update(defaultvalues) featureform.bindvalues(values) except StopIteration: pass self.frame_2.layout().addWidget(featureform) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ widgets = self.widgetmodel.widgets() for widget in widgets: if 'field' in widget: yield widget['field'] def openformfolder(self, url): """ Open the form folder using the OS file manager. :param url: :return: """ QDesktopServices.openUrl(QUrl.fromLocalFile(self.form.folder)) def loadwidgettypes(self): """ Load all supported widgets into the combobox for the form designer. :return: """ self.useablewidgets.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.useablewidgets.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.useablewidgets.blockSignals(False) def set_widget_config_state(self, *args): """ Enable or disable the widget config section based on widget count :param args: Unused. :return: """ haswidgets = self.widgetmodel.rowCount() > 0 self.widgetConfigTabs.setEnabled(haswidgets) def add_section(self): """ Add a new widget section into the form. Widget sections can be used to group widgets on the form together. """ currentindex = self.userwidgets.currentIndex() widget = {"widget": "Section", "name": "default"} index = self.widgetmodel.addwidget(widget, currentindex.parent()) self.userwidgets.setCurrentIndex(index) def newwidget(self, field=None): """ Create a new widget. Tries to match the field type to the right kind of widget as a best guess. """ mapping = {QVariant.String: "Text", QVariant.Int: "Number", QVariant.Double: "Number(Double)", QVariant.ByteArray: "Image", QVariant.Date: "Date", QVariant.DateTime: "Date"} widget = {} if not field: field = self.fieldsmodel.index(0, 0).data(Qt.UserRole) if not field: return widget['field'] = field.name() else: widget['field'] = field.name() try: widget['widget'] = mapping[field.type()] except KeyError: widget['widget'] = 'Text' # Grab the first field. widget['name'] = field.name().replace("_", " ").title() currentindex = self.userwidgets.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.userwidgets.setCurrentIndex(index) def auto_add_fields(self): """ Auto add all fields to the form config. Any missing fields will be added. """ used = list(self.usedfields()) for field in self.selected_layer.pendingFields(): if field.name() in used: continue self.newwidget(field) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def set_project(self, project, treenode): """ Set the project for this widget also sets the form from the tree node. :note: This method is called from the parent node when the page and widget is loaded. :param project: The current project.j :param treenode: The current tree node. Can be used to signel a update back to the tree for it to update it self. """ roam.utils.debug("FormWidget: Set Project") self.blockWidgetSignels(True) super(FormWidget, self).set_project(project, treenode) self.formlayers.setSelectLayers(self.project.selectlayers) form = self.treenode.form self.form = form self.setform(self.form) self.blockWidgetSignels(False) def blockWidgetSignels(self, blocking): for widget in self.blockWidgets: widget.blockSignals(blocking) def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): """ Get the first layer from the forms layer combo box """ index = self.formlayers.index(0, 0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): """ Find the layer with the same name in the layer combobox widget """ index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) newstyleform = settings.setdefault('newstyle', True) self.set_icon(form.icon) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.newStyleCheck.setChecked(newstyleform) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) if formtype == "auto": formtype = "Auto Generated" index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.userwidgets.setCurrentIndex(index) # self.load_widget(index, None) for i in reversed(range(self.eventsLayout.count())): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): child = self.eventsLayout.takeAt(i) child.widget().deleteLater() events = settings.get('events', []) self.load_events(events) ## This has overhead so only do it when the tab is active. if self.formtab.currentIndex() == 3: self.generate_form_preview() def load_events(self, events): for event in events: self.addEventItem(data=event) def addEventItem(self, data=None): widget = EventWidget(self.form.QGISLayer, self.widgetmodel, self.eventsWidget) widget.removeItem.connect(self.removeEventItem) widget.set_data(data) self.eventsLayout.addWidget(widget) def removeEventItem(self, widget): child = self.eventsLayout.removeWidget(widget) widget.deleteLater() def swapwidgetconfig(self, index): widgetconfig, _, _ = self.current_config_widget defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def load_widget(self, index, last): """ Update the UI with the config for the current selected widget. """ self.blockWidgetSignels(True) if last: roam.utils.debug("Saving last widget") self._save_widget(last) widget = index.data(Qt.UserRole) if not widget: self.blockWidgetSignels(False) return try: roam.utils.debug("Loading widget: {0}".format(widget['_id'])) except KeyError: pass widgettype = widget['widget'] if widgettype == "Section": self.propertiesStack.setCurrentIndex(1) self.sectionNameText.blockSignals(True) name = widget['name'] self.sectionNameText.setText(name) self.sectionNameText.blockSignals(False) return else: self.propertiesStack.setCurrentIndex(0) field = widget['field'] self._currentwidgetid = widget.setdefault('_id', str(uuid.uuid4())) required = widget.setdefault('required', False) savevalue = widget.setdefault('rememberlastvalue', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) defaultevents = widget.setdefault('default_events', ['capture']) try: data = readonly[0] except IndexError: data = 'never' index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) index = self.defaultEventsCombo.findData(defaultevents) self.defaultEventsCombo.setCurrentIndex(index) if not isinstance(default, dict): self.defaultTab.setCurrentIndex(0) self.defaultvalueText.setText(default) else: self.defaultTab.setCurrentIndex(1) layer = default['layer'] # TODO Handle the case of many layer fall though with defaults # Not sure how to handle this in the UI just yet if isinstance(layer, list): layer = layer[0] if isinstance(layer, basestring): defaultfield = default['field'] expression = default['expression'] self.defaultValueExpression.setText(expression) layer = roam.api.utils.layer_by_name(layer) # self.defaultLayerCombo.setLayer(layer) self.defaultFieldCombo.setLayer(layer) self.defaultFieldCombo.setField(defaultfield) self.nameText.setText(name) self.requiredCheck.setChecked(required) self.savevalueCheck.setChecked(savevalue) self.hiddenCheck.setChecked(hidden) if field is not None: index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) index = self.useablewidgets.findText(widgettype) if index > -1: self.useablewidgets.setCurrentIndex(index) config = widget.get('config', {}) self.updatewidgetconfig(config) self.blockWidgetSignels(False) @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.userwidgets.currentIndex() return index.data(Qt.UserRole), index @property def current_config_widget(self): """ Return the selected widget in the widget combo. """ index = self.useablewidgets.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetconfig(self, config): configwidget, _, _ = self.current_config_widget self.setconfigwidget(configwidget, config) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_current_widget) except TypeError: pass self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_current_widget) def _save_current_widget(self, *args): _, index = self.currentuserwidget self._save_widget(index) def _save_widget(self, index): # roam.utils.debug("FormWidget: Save widget") print("SENDER: {}".format(self.sender().objectName())) if not self.project: return widgetdata = self._get_widget_config() try: roam.utils.debug("Saving widget {} in project {}".format(widgetdata['_id'], self.project.name)) except KeyError: pass self.widgetmodel.setData(index, widgetdata, Qt.UserRole) def _get_default_config(self): if self.defaultTab.currentIndex() == 0: return self.defaultvalueText.text() else: default = {} default['layer'] = self.defaultLayerCombo.currentLayer().name() default['field'] = self.defaultFieldCombo.currentField() default['expression'] = self.defaultValueExpression.text() default['type'] = 'layer-value' return default def _get_widget_config(self): def current_field(): row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) return field configwidget, _, widgettype = self.current_config_widget if self.propertiesStack.currentIndex() == 1: return {'name': self.sectionNameText.text(), "widget": "Section"} widget = {} widget['field'] = current_field() widget['default'] = self._get_default_config() widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['rememberlastvalue'] = self.savevalueCheck.isChecked() widget['_id'] = self._currentwidgetid widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['default_events'] = self.defaultEventsCombo.itemData(self.defaultEventsCombo.currentIndex()) widget['hidden'] = self.hiddenCheck.isChecked() widget['config'] = configwidget.getconfig() return widget @property def selected_layer(self): index = self.formlayers.index(self.layerCombo.currentIndex(), 0) layer = index.data(Qt.UserRole) return layer def write_config(self): roam.utils.debug("Write form config") if not self.selected_layer: return self._save_current_widget() self.form.settings['layer'] = self.selected_layer.name() formtype = self.formtypeCombo.currentText() self.form.settings['type'] = "auto" if formtype == "Auto Generated" else formtype self.form.settings['label'] = self.formLabelText.text() self.form.settings['newstyle'] = self.newStyleCheck.isChecked() self.form.settings['widgets'] = list(self.widgetmodel.widgets()) events = [] for i in range(self.eventsLayout.count()): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): widget = child.widget() eventdata = widget.get_data() events.append(eventdata) self.form.settings['events'] = events