class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str, int) accepted = pyqtSignal() enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object, dict) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.geomwidget = None self.defaults = defaults self.bindingvalues = CaseInsensitiveDict() self.editingmode = kwargs.get("editmode", False) self.widgetidlookup = {} self._has_save_buttons = False self.star_all_button = QAction(QIcon(":/icons/save_default_all"), "Star All", self, triggered=self.star_all) def open_large_widget(self, widgettype, lastvalue, callback, config=None): self.showlargewidget.emit(widgettype, lastvalue, callback, config) def form_actions(self): builtin = [] if self._has_save_buttons: builtin.append(self.star_all_button) useractions = self.user_form_actions() if not useractions: useractions = [] return builtin + useractions def star_all(self): """ Star or unstar all buttons on the form. :param star_all: True to star all buttons on the form. """ checked = self.star_all_button.text() == "Star All" buttons = self._field_save_buttons() for button in buttons: button.setChecked(checked) if checked: self.star_all_button.setText("Star None") else: self.star_all_button.setText("Star All") @property def is_capturing(self): return self.editingmode == False @is_capturing.setter def is_capturing(self, value): self.editingmode = not value def updaterequired(self, value): passed = self.allpassing self.formvalidation.emit(passed) def validateall(self): widgetwrappers = self.boundwidgets.itervalues() for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ self.geomwidget = self.findcontrol("__geomwidget") widgetsconfig = copy.deepcopy(self.formconfig['widgets']) try: widgetsconfig = self.get_widgets(widgetsconfig) except AttributeError: pass layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = {field.name().lower(): field for field in layer.pendingFields().toList()} # Build a lookup for events self.events = collections.defaultdict(list) for event in self.form.events: self.events[event['source']].append(event) widgetsconfig = copy.deepcopy(widgetsconfig) self.sectionwidgets = {} currentsection = None for config in widgetsconfig: widgettype = config['widget'] if widgettype == "Section": name = config['name'] currentsection = name self.sectionwidgets[name] = [] continue field = config['field'] if not field: utils.info("Skipping widget. No field defined") continue field = field.lower() if field in self.boundwidgets: utils.warning("Can't bind the same field ({}) twice.".format(field)) continue widget = self.findcontrol(field) if widget is None: widget = roam.editorwidgets.core.createwidget(widgettype) config['hidden'] = True utils.info("No widget named {} found so we have made one.".format(field)) label = self.findcontrol("{}_label".format(field)) if label is None: utils.debug("No label found for {}".format(field)) widgetconfig = config.get('config', {}) widgetconfig['formwidget'] = self try: qgsfield = fields[field] except KeyError: utils.log("No field for ({}) found".format(field)) context = dict(project=self.form.project, form=self.form, featureform=self) try: widgetwrapper = roam.editorwidgets.core.widgetwrapper(widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig, context=context) except EditorWidgetException as ex: utils.exception(ex) continue widgetwrapper.default_events = config.get('default_events', ['capture']) readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) widgetwrapper.newstyleform = self.formconfig.get("newstyle", False) widgetwrapper.required = config.get('required', False) widgetwrapper.id = config.get('_id', '') # Only connect widgets that have events if widgetwrapper.id in self.events: widgetwrapper.valuechanged.connect(partial(self.check_for_update_events, widgetwrapper)) widgetwrapper.valuechanged.connect(self.updaterequired) try: changedslot = getattr(self, "widget_{}_changed".format(field)) widgetwrapper.valuechanged.connect(changedslot) except AttributeError: pass widgetwrapper.largewidgetrequest.connect(RoamEvents.show_widget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper try: self.widgetidlookup[config['_id']] = widgetwrapper except KeyError: pass if currentsection: self.sectionwidgets[currentsection].append(widgetwrapper) def widgets_for_section(self, name): try: return self.sectionwidgets[name] except KeyError: return [] def get_widget_from_id(self, id): try: return self.widgetidlookup[id] except KeyError: return None def check_for_update_events(self, widget, value): if not self.feature: return from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextScope # If we don't have any events for this widgets just get out now if not widget.id in self.events: return events = self.events[widget.id] events = [event for event in events if event['event'].lower() == 'update'] if not events: return feature = self.to_feature(no_defaults=True) for event in events: action = event['action'].lower() targetid = event['target'] if targetid == widget.id: utils.log("Can't connect events to the same widget. ID {}".format(targetid)) continue widget = self.get_widget_from_id(targetid) if not widget: utils.log("Can't find widget for id {} in form".format(targetid)) continue condition = event['condition'] expression = event['value'] context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable("value", value) scope.setVariable("field", widget.field) context.setFeature(feature) context.appendScope(scope) conditionexp = QgsExpression(condition) exp = QgsExpression(expression) if action.lower() == "show": widget.hidden = not conditionexp.evaluate(context) if action.lower() == "hide": widget.hidden = conditionexp.evaluate(context) if action == 'widget expression': if conditionexp.evaluate(context): newvalue = self.widget_default(field, feature=feature) widget.setvalue(newvalue) if action == 'set value': if conditionexp.evaluate(context): newvalue = exp.evaluate(context) widget.setvalue(newvalue) def bindvalues(self, values, update=False): """ Bind the values to the form. """ if not update: self.bindingvalues = CaseInsensitiveDict(values) else: for key, value in values.iteritems(): try: self.bindingvalues[key] = value except KeyError: continue for field, value in values.iteritems(): value = nullcheck(value) try: wrapper = self.boundwidgets[field] if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if value and not os.path.exists(value): value = os.path.join(self.form.project.image_folder, value) self.boundwidgets[field].setvalue(value) except KeyError: utils.debug("Can't find control for field {}. Ignoring".format(field)) self.validateall() if self.geomwidget and self.feature: self.geomwidget.set_geometry(self.feature.geometry()) def bind_feature(self, feature): self.feature = feature values = self.form.values_from_feature(feature) self.bindvalues(values) def getvalues(self, no_defaults=False): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): if not no_defaults and wrapper.get_default_value_on_save: value = self.widget_default(field) else: value = wrapper.value() extradata = wrapper.extraData() values.update(extradata) # TODO this should put pulled out and unit tested. MOVE ME! # NOTE: This is photo widget stuff and really really doesn't belong here. if hasattr(wrapper, 'savetofile') and wrapper.savetofile and wrapper.saveable: if wrapper.filename and self.editingmode: name = os.path.basename(wrapper.filename) name, extension = os.path.splitext(name) if wrapper.modified: if not name.endswith("_edited"): newend = "_edited{}".format(extension) value = name + newend else: value = os.path.basename(wrapper.filename) else: value = os.path.basename(wrapper.filename) else: if wrapper.modified: value = wrapper.get_filename() else: value = '' if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name, all=False): regex = QRegExp("^{}$".format(QRegExp.escape(name))) regex.setCaseSensitivity(Qt.CaseInsensitive) try: if all: return self.findChildren(QWidget, regex) else: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _field_save_buttons(self): """ Return all the save buttons for the form. :return: """ for field in self.boundwidgets.keys(): name = "{}_save".format(field) savebutton = self.findcontrol(name) if savebutton: yield savebutton def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) self._has_save_buttons = True def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder) def widget_default(self, name, feature=None): """ Return the default value for the given widget """ try: widgetconfig = self.form.widget_by_field(name) except IndexError: raise KeyError("Widget with name {} not found on form".format(name)) if not 'default' in widgetconfig: raise KeyError('Default value not defined for this field {}'.format(name)) if not feature: feature = self.feature return defaults.widget_default(widgetconfig, feature, self.form.QGISLayer) def close_form(self, reason=None, level=1): self.rejected.emit(reason, level) def to_feature(self, no_defaults=False): """ Create a QgsFeature from the current form values """ if not self.feature: return feature = QgsFeature(self.feature.fields()) feature.setGeometry(QgsGeometry(self.feature.geometry())) try: values, _ = self.getvalues(no_defaults=no_defaults) except TypeError: values, _ = self.getvalues() self.updatefeautrefields(feature, values) self.update_geometry(feature) return feature
class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str) enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object, dict) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.defaults = defaults self.bindingvalues = {} self.editingmode = False def _installeventfilters(self, widgettype): for widget in self.findChildren(widgettype): widget.installEventFilter(self) def eventFilter(self, object, event): # Hack I really don't like this but there doesn't seem to be a better way at the # moment if event.type() == QEvent.FocusIn: RoamEvents.openkeyboard.emit() return False def updaterequired(self, field, passed): self.requiredfields[field.name()] = passed passed = self.allpassing self.formvalidation.emit(passed) def validateall(self, widgetwrappers): for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ widgetsconfig = self.formconfig['widgets'] layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = { field.name().lower(): field for field in layer.pendingFields().toList() } for config in widgetsconfig: widgettype = config['widget'] field = config['field'] if not field: utils.warning("Skipping widget. No field defined") continue field = field.lower() if field in self.boundwidgets: utils.warning( "Sorry you can't bind the same field ({}) twice.".format( field)) utils.warning( "{} for field {} has been ignored in setup".format( widget, field)) continue widget = self.findcontrol(field) if widget is None: utils.warning("No widget named {} found".format(field)) continue label = self.findcontrol("{}_label".format(field)) if label is None: utils.warning("Not label found for {}".format(field)) widgetconfig = config.get('config', {}) qgsfield = fields[field] try: widgetwrapper = roam.editorwidgets.core.widgetwrapper( widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig) except EditorWidgetException as ex: utils.warning(ex.message) continue readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) if config.get('required', False) and not widgetwrapper.hidden: # All widgets state off as false unless told otherwise self.requiredfields[field] = False widgetwrapper.setrequired() widgetwrapper.validationupdate.connect(self.updaterequired) widgetwrapper.largewidgetrequest.connect(self.showlargewidget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper def bindvalues(self, values): """ Bind the values to the form. """ self.bindingvalues = values for field, value in values.iteritems(): value = nullcheck(value) try: self.boundwidgets[field].setvalue(value) except KeyError: utils.info( "Can't find control for field {}. Ignoring".format(field)) self.validateall(self.boundwidgets.itervalues()) def getvalues(self): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): value = wrapper.value() print value if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name): regex = QRegExp("^{}$".format(name)) regex.setCaseSensitivity(Qt.CaseInsensitive) try: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder)
class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str, int) accepted = pyqtSignal() enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object, dict) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.geomwidget = None self.defaults = defaults self.bindingvalues = CaseInsensitiveDict() self.editingmode = kwargs.get("editmode", False) self.widgetidlookup = {} def open_large_widget(self, widgettype, lastvalue, callback, config=None): self.showlargewidget.emit(widgettype, lastvalue, callback, config) @property def is_capturing(self): return self.editingmode == False @is_capturing.setter def is_capturing(self, value): self.editingmode = not value def updaterequired(self, value): passed = self.allpassing self.formvalidation.emit(passed) def validateall(self): widgetwrappers = self.boundwidgets.itervalues() for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ self.geomwidget = self.findcontrol("__geomwidget") widgetsconfig = copy.deepcopy(self.formconfig['widgets']) try: widgetsconfig = self.get_widgets(widgetsconfig) except AttributeError: pass layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = {field.name().lower():field for field in layer.pendingFields().toList()} # Build a lookup for events self.events = collections.defaultdict(list) for event in self.form.events: self.events[event['source']].append(event) widgetsconfig = copy.deepcopy(widgetsconfig) self.sectionwidgets = {} currentsection = None for config in widgetsconfig: widgettype = config['widget'] if widgettype == "Section": name = config['name'] currentsection = name self.sectionwidgets[name] = [] continue field = config['field'] if not field: utils.info("Skipping widget. No field defined") continue field = field.lower() if field in self.boundwidgets: utils.warning("Can't bind the same field ({}) twice.".format(field)) continue widget = self.findcontrol(field) if widget is None: widget = roam.editorwidgets.core.createwidget(widgettype) config['hidden'] = True utils.info("No widget named {} found so we have made one.".format(field)) label = self.findcontrol("{}_label".format(field)) if label is None: utils.debug("No label found for {}".format(field)) widgetconfig = config.get('config', {}) widgetconfig['formwidget'] = self try: qgsfield = fields[field] except KeyError: utils.log("No field for ({}) found".format(field)) context = dict(project=self.form.project, form=self.form, featureform=self) try: widgetwrapper = roam.editorwidgets.core.widgetwrapper(widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig, context=context) except EditorWidgetException as ex: utils.exception(ex) continue widgetwrapper.default_events = config.get('default_events', ['capture']) readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) widgetwrapper.newstyleform = self.formconfig.get("newstyle", False) widgetwrapper.required = config.get('required', False) widgetwrapper.id = config.get('_id', '') # Only connect widgets that have events if widgetwrapper.id in self.events: widgetwrapper.valuechanged.connect(partial(self.check_for_update_events, widgetwrapper)) widgetwrapper.valuechanged.connect(self.updaterequired) try: changedslot = getattr(self, "widget_{}_changed".format(field)) widgetwrapper.valuechanged.connect(changedslot) except AttributeError: pass widgetwrapper.largewidgetrequest.connect(RoamEvents.show_widget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper try: self.widgetidlookup[config['_id']] = widgetwrapper except KeyError: pass if currentsection: self.sectionwidgets[currentsection].append(widgetwrapper) def widgets_for_section(self, name): try: return self.sectionwidgets[name] except KeyError: return [] def get_widget_from_id(self, id): try: return self.widgetidlookup[id] except KeyError: return None def check_for_update_events(self, widget, value): if not self.feature: return from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextScope # If we don't have any events for this widgets just get out now if not widget.id in self.events: return events = self.events[widget.id] events = [event for event in events if event['event'].lower() == 'update'] if not events: return feature = self.to_feature(no_defaults=True) for event in events: action = event['action'].lower() targetid = event['target'] if targetid == widget.id: utils.log("Can't connect events to the same widget. ID {}".format(targetid)) continue widget = self.get_widget_from_id(targetid) if not widget: utils.log("Can't find widget for id {} in form".format(targetid)) continue condition = event['condition'] expression = event['value'] context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable("value", value) scope.setVariable("field", widget.field) context.setFeature(feature) context.appendScope(scope) conditionexp = QgsExpression(condition) exp = QgsExpression(expression) if action.lower() == "show": widget.hidden = not conditionexp.evaluate(context) if action.lower() == "hide": widget.hidden = conditionexp.evaluate(context) if action == 'widget expression': if conditionexp.evaluate(context): newvalue = self.widget_default(field, feature=feature) widget.setvalue(newvalue) if action == 'set value': if conditionexp.evaluate(context): newvalue = exp.evaluate(context) widget.setvalue(newvalue) def bindvalues(self, values, update=False): """ Bind the values to the form. """ if not update: self.bindingvalues = CaseInsensitiveDict(values) else: for key, value in values.iteritems(): try: self.bindingvalues[key] = value except KeyError: continue for field, value in values.iteritems(): value = nullcheck(value) try: wrapper = self.boundwidgets[field] if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if value and not os.path.exists(value): value = os.path.join(self.form.project.image_folder, value) self.boundwidgets[field].setvalue(value) except KeyError: utils.debug("Can't find control for field {}. Ignoring".format(field)) self.validateall() if self.geomwidget and self.feature: self.geomwidget.set_geometry(self.feature.geometry()) def bind_feature(self, feature): self.feature = feature values = self.form.values_from_feature(feature) self.bindvalues(values) def getvalues(self, no_defaults=False): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): if not no_defaults and wrapper.get_default_value_on_save: value = self.widget_default(field) else: value = wrapper.value() extradata = wrapper.extraData() values.update(extradata) # TODO this should put pulled out and unit tested. MOVE ME! # NOTE: This is photo widget stuff and really really doesn't belong here. if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if wrapper.filename and self.editingmode: name = os.path.basename(wrapper.filename) name, extension = os.path.splitext(name) if wrapper.modified: if not name.endswith("_edited"): newend = "_edited{}".format(extension) value = name + newend else: value = os.path.basename(wrapper.filename) else: value = os.path.basename(wrapper.filename) else: if wrapper.modified: value = wrapper.get_filename() else: value = '' if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name): regex = QRegExp("^{}$".format(QRegExp.escape(name))) regex.setCaseSensitivity(Qt.CaseInsensitive) try: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder) def widget_default(self, name, feature=None): """ Return the default value for the given widget """ try: widgetconfig = self.form.widget_by_field(name) except IndexError: raise KeyError("Widget with name {} not found on form".format(name)) if not 'default' in widgetconfig: raise KeyError('Default value not defined for this field {}'.format(name)) if not feature: feature = self.feature return defaults.widget_default(widgetconfig, feature, self.form.QGISLayer) def close_form(self, reason=None, level=1): self.rejected.emit(reason, level) def to_feature(self, no_defaults=False): """ Create a QgsFeature from the current form values """ if not self.feature: return feature = QgsFeature(self.feature.fields()) feature.setGeometry(QgsGeometry(self.feature.geometry())) try: values, _ = self.getvalues(no_defaults=no_defaults) except TypeError: values, _ = self.getvalues() self.updatefeautrefields(feature, values) self.update_geometry(feature) return feature
class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str, int) enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object, dict) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.defaults = defaults self.bindingvalues = CaseInsensitiveDict() self.editingmode = kwargs.get("editmode", False) def open_large_widget(self, widgettype, lastvalue, callback, config=None): self.showlargewidget.emit(widgettype, lastvalue, callback, config) @property def is_capturing(self): return self.editingmode == False @is_capturing.setter def is_capturing(self, value): self.editingmode = not value def updaterequired(self, value): passed = self.allpassing self.formvalidation.emit(passed) def validateall(self): widgetwrappers = self.boundwidgets.itervalues() for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ widgetsconfig = self.formconfig['widgets'] layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = {field.name().lower():field for field in layer.pendingFields().toList()} for config in widgetsconfig: widgettype = config['widget'] field = config['field'] if not field: utils.info("Skipping widget. No field defined") continue field = field.lower() if field in self.boundwidgets: utils.warning("Sorry you can't bind the same field ({}) twice.".format(field)) utils.warning("{} for field {} has been ignored in setup".format(widget, field)) continue widget = self.findcontrol(field) if widget is None: widget = roam.editorwidgets.core.createwidget(widgettype) config['hidden'] = True utils.info("No widget named {} found so we have made one.".format(field)) label = self.findcontrol("{}_label".format(field)) if label is None: utils.debug("No label found for {}".format(field)) widgetconfig = config.get('config', {}) qgsfield = fields[field] try: widgetwrapper = roam.editorwidgets.core.widgetwrapper(widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig) except EditorWidgetException as ex: utils.exception(ex) continue readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) widgetwrapper.required = config.get('required', False) widgetwrapper.valuechanged.connect(self.updaterequired) widgetwrapper.largewidgetrequest.connect(RoamEvents.show_widget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper def bindvalues(self, values, update=False): """ Bind the values to the form. """ if not update: self.bindingvalues = CaseInsensitiveDict(values) else: for key, value in values.iteritems(): try: self.bindingvalues[key] = value except KeyError: continue for field, value in values.iteritems(): value = nullcheck(value) try: wrapper = self.boundwidgets[field] if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if value and not os.path.exists(value): value = os.path.join(self.form.project.image_folder, value) self.boundwidgets[field].setvalue(value) except KeyError: utils.debug("Can't find control for field {}. Ignoring".format(field)) self.validateall() def bind_feature(self, feature): self.feature = feature values = self.form.values_from_feature(feature) self.bindvalues(values) def getvalues(self): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): value = wrapper.value() # TODO this should put pulled out and unit tested if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if wrapper.filename and self.editingmode: name = os.path.basename(wrapper.filename) name, extension = os.path.splitext(name) if wrapper.modified: if not name.endswith("_edited"): newend = "_edited{}".format(extension) value = name + newend else: value = os.path.basename(wrapper.filename) else: value = os.path.basename(wrapper.filename) else: if wrapper.modified: value = wrapper.get_filename() else: value = '' if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name): regex = QRegExp("^{}$".format(name)) regex.setCaseSensitivity(Qt.CaseInsensitive) try: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder) def widget_default(self, name): """ Return the default value for the given widget """ try: widgetconfig = self.form.widget_by_field(name) except IndexError: raise KeyError("Widget with name {} not found on form".format(name)) if not 'default' in widgetconfig: raise KeyError('Default value not defined for this field {}'.format(name)) return defaults.widget_default(widgetconfig, self.feature, self.form.QGISLayer) def close_form(self, reason=None, level=1): self.rejected.emit(reason, level)
class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str) enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.defaults = defaults self.bindingvalues = {} self.editingmode = False def _installeventfilters(self, widgettype): for widget in self.findChildren(widgettype): widget.installEventFilter(self) def eventFilter(self, object, event): # Hack I really don't like this but there doesn't seem to be a better way at the # moment if event.type() == QEvent.FocusIn: RoamEvents.openkeyboard.emit() return False def updaterequired(self, field, passed): self.requiredfields[field.name()] = passed passed = self.allpassing self.formvalidation.emit(passed) def validateall(self, widgetwrappers): for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ widgetsconfig = self.formconfig['widgets'] layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = {field.name().lower():field for field in layer.pendingFields().toList()} for config in widgetsconfig: widgettype = config['widget'] field = config['field'].lower() if field in self.boundwidgets: utils.warning("Sorry you can't bind the same field ({}) twice.".format(field)) utils.warning("{} for field {} has been ignored in setup".format(widget, field)) continue widget = self.findcontrol(field) if widget is None: utils.warning("No widget named {} found".format(field)) continue label = self.findcontrol("{}_label".format(field)) if label is None: utils.warning("Not label found for {}".format(field)) widgetconfig = config.get('config', {}) qgsfield = fields[field] try: widgetwrapper = roam.editorwidgets.core.widgetwrapper(widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig) except EditorWidgetException as ex: utils.warning(ex.message) continue readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) if config.get('required', False) and not widgetwrapper.hidden: # All widgets state off as false unless told otherwise self.requiredfields[field] = False widgetwrapper.setrequired() widgetwrapper.validationupdate.connect(self.updaterequired) widgetwrapper.largewidgetrequest.connect(self.showlargewidget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper def bindvalues(self, values): """ Bind the values to the form. """ self.bindingvalues = values for field, value in values.iteritems(): value = nullcheck(value) try: self.boundwidgets[field].setvalue(value) except KeyError: utils.info("Can't find control for field {}. Ignoring".format(field)) self.validateall(self.boundwidgets.itervalues()) def getvalues(self): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): value = wrapper.value() if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name): regex = QRegExp("^{}$".format(name)) regex.setCaseSensitivity(Qt.CaseInsensitive) try: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder)
class FeatureFormBase(QWidget): requiredfieldsupdated = pyqtSignal(bool) formvalidation = pyqtSignal(bool) helprequest = pyqtSignal(str) showwidget = pyqtSignal(QWidget) loadform = pyqtSignal() rejected = pyqtSignal(str, int) enablesave = pyqtSignal(bool) showlargewidget = pyqtSignal(object, object, object, dict) def __init__(self, form, formconfig, feature, defaults, parent, *args, **kwargs): super(FeatureFormBase, self).__init__(parent) self.form = form self.formconfig = formconfig self.boundwidgets = CaseInsensitiveDict() self.requiredfields = CaseInsensitiveDict() self.feature = feature self.geomwidget = None self.defaults = defaults self.bindingvalues = CaseInsensitiveDict() self.editingmode = kwargs.get("editmode", False) def open_large_widget(self, widgettype, lastvalue, callback, config=None): self.showlargewidget.emit(widgettype, lastvalue, callback, config) @property def is_capturing(self): return self.editingmode == False @is_capturing.setter def is_capturing(self, value): self.editingmode = not value def updaterequired(self, value): passed = self.allpassing self.formvalidation.emit(passed) def validateall(self): widgetwrappers = self.boundwidgets.itervalues() for wrapper in widgetwrappers: wrapper.validate() def setupui(self): """ Setup the widget in the form """ self.geomwidget = self.findcontrol("__geomwidget") widgetsconfig = self.formconfig['widgets'] layer = self.form.QGISLayer # Crash in QGIS if you lookup a field that isn't found. # We just make a dict with all fields lower because QgsFields is case sensitive. fields = { field.name().lower(): field for field in layer.pendingFields().toList() } for config in widgetsconfig: widgettype = config['widget'] field = config['field'] if not field: utils.info("Skipping widget. No field defined") continue field = field.lower() if field in self.boundwidgets: utils.warning( "Can't bind the same field ({}) twice.".format(field)) continue widget = self.findcontrol(field) if widget is None: widget = roam.editorwidgets.core.createwidget(widgettype) config['hidden'] = True utils.info( "No widget named {} found so we have made one.".format( field)) label = self.findcontrol("{}_label".format(field)) if label is None: utils.debug("No label found for {}".format(field)) widgetconfig = config.get('config', {}) qgsfield = fields[field] try: widgetwrapper = roam.editorwidgets.core.widgetwrapper( widgettype=widgettype, layer=self.form.QGISLayer, field=qgsfield, widget=widget, label=label, config=widgetconfig) except EditorWidgetException as ex: utils.exception(ex) continue readonlyrules = config.get('read-only-rules', []) if self.editingmode and 'editing' in readonlyrules: widgetwrapper.readonly = True elif 'insert' in readonlyrules or 'always' in readonlyrules: widgetwrapper.readonly = True widgetwrapper.hidden = config.get('hidden', False) widgetwrapper.required = config.get('required', False) widgetwrapper.valuechanged.connect(self.updaterequired) widgetwrapper.largewidgetrequest.connect( RoamEvents.show_widget.emit) self._bindsavebutton(field) self.boundwidgets[field] = widgetwrapper def bindvalues(self, values, update=False): """ Bind the values to the form. """ if not update: self.bindingvalues = CaseInsensitiveDict(values) else: for key, value in values.iteritems(): try: self.bindingvalues[key] = value except KeyError: continue for field, value in values.iteritems(): value = nullcheck(value) try: wrapper = self.boundwidgets[field] if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if value and not os.path.exists(value): value = os.path.join(self.form.project.image_folder, value) self.boundwidgets[field].setvalue(value) except KeyError: utils.debug( "Can't find control for field {}. Ignoring".format(field)) self.validateall() if self.geomwidget and self.feature: self.geomwidget.set_geometry(self.feature.geometry()) def bind_feature(self, feature): self.feature = feature values = self.form.values_from_feature(feature) self.bindvalues(values) def getvalues(self): def shouldsave(field): name = "{}_save".format(field) button = self.findcontrol(name) if not button is None: return button.isChecked() savedvalues = {} values = CaseInsensitiveDict(self.bindingvalues) for field, wrapper in self.boundwidgets.iteritems(): value = wrapper.value() # TODO this should put pulled out and unit tested if hasattr(wrapper, 'savetofile') and wrapper.savetofile: if wrapper.filename and self.editingmode: name = os.path.basename(wrapper.filename) name, extension = os.path.splitext(name) if wrapper.modified: if not name.endswith("_edited"): newend = "_edited{}".format(extension) value = name + newend else: value = os.path.basename(wrapper.filename) else: value = os.path.basename(wrapper.filename) else: if wrapper.modified: value = wrapper.get_filename() else: value = '' print value if shouldsave(field): savedvalues[field] = value values[field] = value return values, savedvalues def findcontrol(self, name): regex = QRegExp("^{}$".format(QRegExp.escape(name))) regex.setCaseSensitivity(Qt.CaseInsensitive) try: widget = self.findChildren(QWidget, regex)[0] except IndexError: widget = None return widget def _bindsavebutton(self, field): name = "{}_save".format(field) button = self.findcontrol(name) if button is None: return button.setCheckable(not self.editingmode) button.setIcon(QIcon(":/icons/save_default")) button.setIconSize(QSize(24, 24)) button.setChecked(field in self.defaults) button.setVisible(not self.editingmode) def createhelplinks(self): def createhelplink(label, folder): def getHelpFile(): # TODO We could just use the tooltip from the control to show help # rather then having to save out a html file. name = label.objectName() if name.endswith("_label"): name = name[:-6] filename = "{}.html".format(name) filepath = os.path.join(folder, "help", filename) if os.path.exists(filepath): return filepath else: return None if label is None: return helpfile = getHelpFile() if helpfile: text = '<a href="{}">{}<a>'.format(helpfile, label.text()) label.setText(text) label.linkActivated.connect(self.helprequest.emit) for label in self.findChildren(QLabel): createhelplink(label, self.form.folder) def widget_default(self, name): """ Return the default value for the given widget """ try: widgetconfig = self.form.widget_by_field(name) except IndexError: raise KeyError( "Widget with name {} not found on form".format(name)) if not 'default' in widgetconfig: raise KeyError( 'Default value not defined for this field {}'.format(name)) return defaults.widget_default(widgetconfig, self.feature, self.form.QGISLayer) def close_form(self, reason=None, level=1): self.rejected.emit(reason, level)