class MainQtDialogUi(GwDialog, FORM_CLASS): def __init__(self, subtag=None): super().__init__() self.txt_pass.setClearButtonEnabled(True) icon_path = os.path.dirname(__file__) + os.sep + 'icons' + os.sep + 'eye_open.png' self.action = QAction("show") if os.path.exists(icon_path): icon = QIcon(icon_path) self.action = QAction(icon, "show") self.action.triggered.connect(self.show_pass) self.txt_pass.addAction(self.action, QLineEdit.TrailingPosition) def show_pass(self): icon_path = "" text = "" if self.txt_pass.echoMode() == 0: self.txt_pass.setEchoMode(QLineEdit.Password) icon_path = os.path.dirname(__file__) + os.sep + 'icons' + os.sep + 'eye_open.png' text = "Show password" elif self.txt_pass.echoMode() == 2: self.txt_pass.setEchoMode(QLineEdit.Normal) icon_path = os.path.dirname(__file__) + os.sep + 'icons' + os.sep + 'eye_close.png' text = "Hide password" if os.path.exists(icon_path): icon = QIcon(icon_path) self.action.setIcon(icon) self.action.setText(text)
def add_action(self, text, slot, icon=None): action = QAction(self.iface.mainWindow()) action.setText(text) if icon is not None: action.setIcon(icon) action.triggered.connect(slot) self.menuToolBar.addAction(action) self.iface.addPluginToMenu("&PreCourlis", action) self.actions.append(action)
def load_application_sync(self): providers = list(roam.syncing.syncprovders()) if not providers: return actionwidget = ActionPickerWidget() actionwidget.setTile("Roam Syncing") for provider in providers: action = QAction(None) action.setText(provider.name) action.setIcon(QIcon(":/icons/sync")) action.triggered.connect(partial(self.run, action, provider)) actionwidget.addAction(action) self.syncwidgets.layout().addWidget(actionwidget)
def loadprojects(self, projects): # root = self.synctree.invisibleRootItem() self.load_application_sync() for project in projects: providers = list(project.syncprovders()) if not providers: continue actionwidget = ActionPickerWidget() actionwidget.setTile(project.name) for provider in providers: action = QAction(None) action.setText(provider.name) action.setIcon(QIcon(":/icons/sync")) action.triggered.connect(partial(self.run, action, provider)) actionwidget.addAction(action) self.syncwidgets.layout().addWidget(actionwidget) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Expanding) self.syncwidgets.layout().addItem(spacerItem)
class InfoDock(infodock_widget, QWidget): featureupdated = pyqtSignal(object, object, list) resultscleared = pyqtSignal() activeLayerChanged = pyqtSignal(object) def __init__(self, parent): super(InfoDock, self).__init__(parent) self.setupUi(self) # TODO Doesn't currently work with webview. Loop back and fix this. install_touch_scroll(self.attributesView) self.forms = {} self.layerList.currentRowChanged.connect(self.layerIndexChanged) self.attributesView.linkClicked.connect(self.handle_link) self.attributesView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) action = self.attributesView.pageAction(QWebPage.Copy) action.setShortcut(QKeySequence.Copy) self.grabGesture(Qt.SwipeGesture) self.setAttribute(Qt.WA_AcceptTouchEvents) self.editButton.pressed.connect(self.openform) self.editGeomButton.pressed.connect(self.editgeom) self.parent().installEventFilter(self) self.project = None self.startwidth = self.width() self.expaned = False self.layerList.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.expandAction = QAction(QIcon(":/icons/expand"), "Expand Panel", self) self.expandAction.triggered.connect(self.change_expanded_state) self.navigateAction = QAction(QIcon(":/icons/navigate"), "Navigate To..", self) self.navigateAction.triggered.connect(self._navigate_to_selection) self.moreActionsButton.pressed.connect(self._show_more_actions) self.navwidget.mousePressEvent = self._sink self.bottomWidget.mousePressEvent = self._sink self.navwidget.mouseReleaseEvent = self._sink self.bottomWidget.mouseReleaseEvent = self._sink self.navwidget.mouseMoveEvent = self._sink self.bottomWidget.mouseMoveEvent = self._sink self.deleteFeatureButton.pressed.connect(self.delete_feature) self.deleteFeatureButton.setCheckable(False) self.quickInspectButton.hide() self.quickInspectButton.pressed.connect(self.quick_inspect_feature) self.nextButton.pressed.connect(self.pagenext) self.prevButton.pressed.connect(self.pageback) RoamEvents.selectioncleared.connect(self.clearResults) RoamEvents.editgeometry_complete.connect(self.refreshcurrent) RoamEvents.editgeometry_invalid.connect(self.refreshcurrent) def _navigate_to_selection(self) -> None: """ Set the GPS waypoint to the active feature """ feature = self.selection.feature geom = feature.geometry() point = geom.centroid().asPoint() if GPS.waypoint == point: GPS.waypoint = None else: GPS.waypoint = point def _show_more_actions(self) -> None: """ Show the more actions selector dialog. """ dlg = PickActionDialog() self.navigateAction.setEnabled(GPS.isConnected) if not GPS.isConnected: self.navigateAction.setText("Navigate To.. (No GPS)") else: self.navigateAction.setText("Navigate To..") dlg.addactions([self.expandAction, self.navigateAction]) dlg.exec_() def delete_feature(self) -> None: """ Trigger the feature delete logic. Doesn't delete the feature here just fires the event to delete the feature. """ cursor = self.selection RoamEvents.delete_feature(cursor.form, cursor.feature) def handle_link(self, url) -> None: """ Handle any links that are fired :param url: The url that is clicked in the info dock :return: """ if url.toString().endswith("/back"): self.pageback() elif url.toString().endswith("/next"): self.pagenext() else: RoamEvents.openurl.emit(url) def _sink(self, event) -> None: """ Empty event sink to do nothing with the event. """ return def change_expanded_state(self) -> None: """ Expand or collapse the info panel view """ if self.expaned: self._collapse() else: self._expand() def mousePressEvent(self, event): pos = self.mapToParent(event.pos()) newevent = QMouseEvent(event.type(), pos, event.button(), event.buttons(), event.modifiers()) self.parent().mousePressEvent(newevent) def mouseReleaseEvent(self, event): pos = self.mapToParent(event.pos()) newevent = QMouseEvent(event.type(), pos, event.button(), event.buttons(), event.modifiers()) self.parent().mouseReleaseEvent(newevent) def mouseMoveEvent(self, event): pos = self.mapToParent(event.pos()) newevent = QMouseEvent(event.type(), pos, event.button(), event.buttons(), event.modifiers()) self.parent().mouseMoveEvent(newevent) def _expand(self) -> None: """ Expand the info panel. """ self.resize(self.parent().width() - 10, self.parent().height()) self.move(10, 0) self.expaned = True self.expandAction.setText("Collapse Panel") def _collapse(self) -> None: """ Collapse the info panel back to the samller state :return: """ self.resize(self.startwidth, self.parent().height()) self.move(self.parent().width() - self.startwidth, 0) self.expaned = False self.expandAction.setText("Expand Panel") def eventFilter(self, object, event): if event.type() == QEvent.Resize: self._collapse() return super(InfoDock, self).eventFilter(object, event) def close(self): """ Close the info panel dock. :return: """ RoamEvents.selectioncleared.emit() super(InfoDock, self).close() def event(self, event): if event.type() == QEvent.Gesture: gesture = event.gesture(Qt.SwipeGesture) if gesture: self.pagenext() return QWidget.event(self, event) @property def selection(self): """ Return the active selection in the info panel. :return: """ item = self.layerList.item(self.layerList.currentRow()) if not item: return cursor = item.data(Qt.UserRole) return cursor def openform(self): """ Fire the open feature form event to open the form for the current feature. :return: """ cursor = self.selection tools = self.project.layer_tools(cursor.layer) if 'inspection' in tools: config = tools['inspection'] form, feature = self.get_inspection_config(cursor.feature, config) editmode = False else: form = cursor.form feature = cursor.feature editmode = True RoamEvents.load_feature_form(form, feature, editmode) def quick_inspect_feature(self): """ Quick inspect the current feature """ cursor = self.selection tools = self.project.layer_tools(cursor.layer) config = tools['inspection'] form, feature = self.get_inspection_config(cursor.feature, config) editmode = False form.suppressform = True RoamEvents.load_feature_form(form, feature, editmode) # Leaking state is leaking. But this is what we have for now. form.suppressform = False def get_inspection_config(self, current_feature, config): """ Returns the inspection form and a copy of the feature for the new form. :param current_feature: The current feature to be copied :param config: The tool config :return: """ form = config['form'] newform = self.project.form_by_name(form) if config.get('mode', "copy").lower() == 'copy': geom = current_feature.geometry() mappings = config.get('field_mapping', {}) data = {} for fieldfrom, fieldto in mappings.items(): data[fieldto] = current_feature[fieldfrom] newgeom = QgsGeometry(geom) newfeature = newform.new_feature(geometry=newgeom, data=data) return newform, newfeature else: raise NotImplementedError("Only copy mode supported currently") def editgeom(self) -> None: """ Trigger the event to edit the geometry of the active feature. """ cursor = self.selection RoamEvents.editgeometry.emit(cursor.form, cursor.feature) self.editGeomButton.setEnabled(False) self.deleteFeatureButton.setEnabled(False) def pageback(self) -> None: """ Go back a page :return: """ cursor = self.selection cursor.back() self.update(cursor) def pagenext(self) -> None: """ Go to the next page. :return: """ cursor = self.selection cursor.next() self.update(cursor) def layerIndexChanged(self, index) -> None: """ Called when the selected layer item changes. Updates the panel with layer selection. :param index: The new index of the selected """ item = self.layerList.item(index) if not item: return cursor = item.data(Qt.UserRole) self.update(cursor) self.activeLayerChanged.emit(cursor.layer) def setResults(self, results, forms, project) -> None: """ Set the results for the info panel. :param results: Dict of layer and features for the selection. :param forms: The forms from the project. :param project: The active project. """ lastrow = self.layerList.currentRow() if lastrow == -1: lastrow = 0 self.clearResults() self.forms = forms self.project = project for layer, features in results.items(): if features: self._addResult(layer, features) self.layerList.setCurrentRow(lastrow) self.layerList.setMinimumWidth(self.layerList.sizeHintForColumn(0) + 20) size = 0 for n in range(self.layerList.count()): size += self.layerList.sizeHintForRow(n) self.layerList.setMinimumHeight(size) self.layerList.setMaximumHeight(size) self.navwidget.show() def show(self) -> None: """ Show or hide the layer panel based on the results count. """ if self.layerList.count() > 0: super(InfoDock, self).show() else: self.hide() def _addResult(self, layer, features): layername = layer.name() forms = self.forms.get(layername, []) if not forms: item = QListWidgetItem(QIcon(), layername, self.layerList) item.setData(Qt.UserRole, FeatureCursor(layer, features)) return for form in forms: selectname = self.project.selectlayer_name(form.layername) if selectname == layername: itemtext = "{} \n ({})".format(layername, form.label) else: itemtext = selectname icon = QIcon(form.icon) item = QListWidgetItem(icon, itemtext, self.layerList) item.setData(Qt.UserRole, FeatureCursor(layer, features, form)) def refreshcurrent(self) -> None: """ Refresh the dock with the updated selection. """ self.update(self.selection) def update(self, cursor) -> None: """ Update the data in the dock with the given cursor data. :param cursor: The cursor holding a pointer to the data and feature for the active feature. """ if cursor is None: return try: feature = cursor.feature except NoFeature as ex: utils.exception(ex) return form = cursor.form layer = cursor.layer clear_image_cache() self.countLabel.setText(str(cursor)) info1, results = self.generate_info("info1", self.project, layer, feature.id(), feature, countlabel=str(cursor)) info2, _ = self.generate_info("info2", self.project, layer, feature.id(), feature, lastresults=results[0]) if form: name = "{}".format(layer.name(), form.label) else: name = layer.name() info = dict(TITLE=name, INFO1=info1, INFO2=info2) html = updateTemplate(info, infotemplate) self.attributesView.setHtml(html, templates.baseurl) tools = self.project.layer_tools(layer) hasform = form is not None editattributes = ('edit_attributes' in tools or 'inspection' in tools) and hasform editgeom = 'edit_geom' in tools and hasform deletefeature = 'delete' in tools and hasform self.deleteFeatureButton.setVisible(deletefeature) self.quickInspectButton.setVisible('inspection' in tools) self.editButton.setVisible(editattributes) feature = cursor.feature self.editGeomButton.setVisible(editgeom) self.editGeomButton.setEnabled(True) self.featureupdated.emit(layer, feature, cursor.features) def generate_info(self, infoblock, project, layer, mapkey, feature, countlabel=None, lastresults=None): """ Generate a info block for the display. :param infoblock: The info block name to generate. :param project: The active Roam project. :param layer: The active layer. :param mapkey: The current map key of the selected feature. Normally just the primary key column from QGIS. :param feature: The selected feature. :param countlabel: The label to use as the count header. :param lastresults: The results from another info block. Normally info1 passed to info2. :returns: """ infoblockdef = project.info_query(infoblock, layer.name()) isinfo1 = infoblock == "info1" if not infoblockdef: if isinfo1: infoblockdef = {} infoblockdef['type'] = 'feature' else: return None, [] if isinfo1: caption = infoblockdef.get('caption', "Record") else: caption = infoblockdef.get('caption', "Related Record") results = [] error = None infotype = infoblockdef.get('type', 'feature') if infotype == 'sql': try: queryresults = self.results_from_query(infoblockdef, layer, feature, mapkey, lastresults=lastresults) if isinfo1 and not queryresults: # If there is no results from the query and we are a info 1 block we grab from the feature. results.append(results_from_feature(feature)) else: results = queryresults except database.DatabaseException as ex: RoamEvents.raisemessage("Query Error", ex.message, 3) utils.error(ex.message) if not isinfo1: error = "<b> Error: {} <b>".format(ex.msg) else: results.append(results_from_feature(feature)) elif infotype == 'feature': featuredata = results_from_feature(feature) excludedfields = infoblockdef.get('hidden', []) for field in excludedfields: try: del featuredata[field] except KeyError: pass results.append(featuredata) else: return None, [] blocks = [] for count, result in enumerate(results, start=1): if isinfo1 and count == 1: countblock = countblocktemplate.substitute(count=countlabel) else: countblock = '' fields = result.keys() attributes = result.values() rows = create_data_html(fields, attributes, imagepath=self.project.image_folder) try: caption = caption.format(**dict(zip(fields, attributes))) except KeyError: pass blocks.append(updateTemplate(dict(ROWS=rows, HEADER=caption, CONTROLS=countblock), infoblocktemplate)) if error: return error, [] return '<br>'.join(blocks), results def results_from_query(self, infoblockdef, layer, feature, mapkey, lastresults=None) -> list: """ Return the resutls from running a database query to get the feature results. :param infoblockdef: The info block project config section. :param layer: The QgsVectorLayer to get the connection from. :param feature: The feature to pull the map key from. :param mapkey: The mapkey to use if not set in the info block config or found in the last results. :param lastresults: Results of another info results block. Normally info 1 :return: List of query results from running the query on the layer. """ def get_key() -> str: try: keycolumn = infoblockdef['mapping']['mapkey'] if keycolumn == 'from_info1': if 'mapkey' in lastresults: return lastresults['mapkey'] else: # TODO Umm wat? Why is this returning a list? return [] else: return feature[keycolumn] except KeyError: return mapkey def get_layer() -> str: connection = infoblockdef.get('connection', "from_layer") if isinstance(connection, dict): return layer_by_name(connection['layer']) elif connection == "from_layer": return layer else: raise NotImplementedError("{} is not a supported connection type".format(connection)) if not lastresults: lastresults = {} sql = infoblockdef['query'] layer = get_layer() db = database.Database.fromLayer(layer) mapkey = get_key() attributes = values_from_feature(feature, safe_names=True) attributes['mapkey'] = mapkey # Run the SQL text though the QGIS expression engine first. sql = roam.api.utils.replace_expression_placeholders(sql, feature) results = db.query(sql, **attributes) results = list(results) return results def clearResults(self) -> None: """ Clear the results in the info panel. """ self.layerList.clear() self.attributesView.setHtml('') self.editButton.setVisible(False) self.editGeomButton.setEnabled(True) self.editButton.setEnabled(True) self.deleteFeatureButton.setEnabled(True) self.navwidget.hide()
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.values() 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.fields().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)) continue 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, main_config=config) 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) # 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.items(): try: self.bindingvalues[key] = value except KeyError: continue for field, value in values.items(): 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.items(): 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 Plugin(): """The QGIS interface implementation for the InaSAFE plugin. This class acts as the 'glue' between QGIS and our custom logic. It creates a toolbar and menu bar entry and launches the InaSAFE user interface if these are activated. """ def __init__(self, iface): """Class constructor. On instantiation, the plugin instance will be assigned a copy of the QGIS iface object which will allow this plugin to access and manipulate the running QGIS instance that spawned it. :param iface:Quantum GIS iface instance. This instance is automatically passed to the plugin by QGIS when it loads the plugin. :type iface: QgisAppInterface """ # Save reference to the QGIS interface self.iface = iface self.dock_widget = None # Actions self.action_add_layers = None self.action_add_osm_layer = None self.action_add_petabencana_layer = None self.action_batch_runner = None self.action_dock = None self.action_extent_selector = None self.action_field_mapping = None self.action_multi_exposure = None self.action_function_centric_wizard = None self.action_import_dialog = None self.action_keywords_wizard = None self.action_minimum_needs = None self.action_minimum_needs_config = None self.action_multi_buffer = None self.action_options = None self.action_run_tests = None self.action_save_scenario = None self.action_shake_converter = None self.action_show_definitions = None self.action_toggle_rubberbands = None self.action_metadata_converter = None self.translator = None self.toolbar = None self.wizard = None self.actions = [] # list of all QActions we create for InaSAFE self.message_bar_item = None # Flag indicating if toolbar should show only common icons or not self.full_toolbar = False # print self.tr('InaSAFE') # For enable/disable the keyword editor icon self.iface.currentLayerChanged.connect(self.layer_changed) developer_mode = setting('developer_mode', False, expected_type=bool) self.hide_developer_buttons = (inasafe_release_status == 'final' and not developer_mode) # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('Plugin', message) def add_action(self, action, add_to_toolbar=True, add_to_legend=False): """Add a toolbar icon to the InaSAFE toolbar. :param action: The action that should be added to the toolbar. :type action: QAction :param add_to_toolbar: Flag indicating whether the action should also be added to the InaSAFE toolbar. Defaults to True. :type add_to_toolbar: bool :param add_to_legend: Flag indicating whether the action should also be added to the layer legend menu. Default to False. :type add_to_legend: bool """ # store in the class list of actions for easy plugin unloading self.actions.append(action) self.iface.addPluginToMenu(self.tr('InaSAFE'), action) if add_to_toolbar: self.toolbar.addAction(action) if add_to_legend: # The id is the action name without spaces, tabs ... self.iface.addCustomActionForLayerType(action, self.tr('InaSAFE'), QgsMapLayer.VectorLayer, True) self.iface.addCustomActionForLayerType(action, self.tr('InaSAFE'), QgsMapLayer.RasterLayer, True) def _create_dock_toggle_action(self): """Create action for plugin dockable window (show/hide).""" # pylint: disable=W0201 icon = resources_path('img', 'icons', 'icon.svg') self.action_dock = QAction(QIcon(icon), self.tr('Toggle InaSAFE Dock'), self.iface.mainWindow()) self.action_dock.setObjectName('InaSAFEDockToggle') self.action_dock.setStatusTip(self.tr('Show/hide InaSAFE dock widget')) self.action_dock.setWhatsThis(self.tr('Show/hide InaSAFE dock widget')) self.action_dock.setCheckable(True) self.action_dock.setChecked(True) self.action_dock.triggered.connect(self.toggle_dock_visibility) self.add_action(self.action_dock) # -------------------------------------- # Create action for keywords creation wizard # ------------------------------------- def _create_keywords_wizard_action(self): """Create action for keywords creation wizard.""" icon = resources_path('img', 'icons', 'show-keyword-wizard.svg') self.action_keywords_wizard = QAction( QIcon(icon), self.tr('Keywords Creation Wizard'), self.iface.mainWindow()) self.action_keywords_wizard.setStatusTip( self.tr('Open InaSAFE keywords creation wizard')) self.action_keywords_wizard.setWhatsThis( self.tr('Open InaSAFE keywords creation wizard')) self.action_keywords_wizard.setEnabled(False) self.action_keywords_wizard.triggered.connect( self.show_keywords_wizard) self.add_action(self.action_keywords_wizard, add_to_legend=True) def _create_analysis_wizard_action(self): """Create action for IF-centric wizard.""" icon = resources_path('img', 'icons', 'show-wizard.svg') self.action_function_centric_wizard = QAction( QIcon(icon), self.tr('Impact Function Centric Wizard'), self.iface.mainWindow()) self.action_function_centric_wizard.setStatusTip( self.tr('Open InaSAFE impact function centric wizard')) self.action_function_centric_wizard.setWhatsThis( self.tr('Open InaSAFE impact function centric wizard')) self.action_function_centric_wizard.setEnabled(True) self.action_function_centric_wizard.triggered.connect( self.show_function_centric_wizard) self.add_action(self.action_function_centric_wizard) def _create_options_dialog_action(self): """Create action for options dialog.""" icon = resources_path('img', 'icons', 'configure-inasafe.svg') self.action_options = QAction(QIcon(icon), self.tr('Options'), self.iface.mainWindow()) self.action_options.setStatusTip( self.tr('Open InaSAFE options dialog')) self.action_options.setWhatsThis( self.tr('Open InaSAFE options dialog')) self.action_options.triggered.connect(self.show_options) self.add_action(self.action_options, add_to_toolbar=self.full_toolbar) def _create_minimum_needs_action(self): """Create action for minimum needs dialog.""" icon = resources_path('img', 'icons', 'show-minimum-needs.svg') self.action_minimum_needs = QAction( QIcon(icon), self.tr('Minimum Needs Calculator'), self.iface.mainWindow()) self.action_minimum_needs.setStatusTip( self.tr('Open InaSAFE minimum needs calculator')) self.action_minimum_needs.setWhatsThis( self.tr('Open InaSAFE minimum needs calculator')) self.action_minimum_needs.triggered.connect(self.show_minimum_needs) self.add_action(self.action_minimum_needs, add_to_toolbar=self.full_toolbar) def _create_multi_buffer_action(self): """Create action for multi buffer dialog.""" icon = resources_path('img', 'icons', 'show-multi-buffer.svg') self.action_multi_buffer = QAction(QIcon(icon), self.tr('Multi Buffer'), self.iface.mainWindow()) self.action_multi_buffer.setStatusTip( self.tr('Open InaSAFE multi buffer')) self.action_multi_buffer.setWhatsThis( self.tr('Open InaSAFE multi buffer')) self.action_multi_buffer.triggered.connect(self.show_multi_buffer) self.add_action(self.action_multi_buffer, add_to_toolbar=self.full_toolbar) def _create_minimum_needs_options_action(self): """Create action for global minimum needs dialog.""" icon = resources_path('img', 'icons', 'show-global-minimum-needs.svg') self.action_minimum_needs_config = QAction( QIcon(icon), self.tr('Minimum Needs Configuration'), self.iface.mainWindow()) self.action_minimum_needs_config.setStatusTip( self.tr('Open InaSAFE minimum needs configuration')) self.action_minimum_needs_config.setWhatsThis( self.tr('Open InaSAFE minimum needs configuration')) self.action_minimum_needs_config.triggered.connect( self.show_minimum_needs_configuration) self.add_action(self.action_minimum_needs_config, add_to_toolbar=self.full_toolbar) def _create_shakemap_converter_action(self): """Create action for converter dialog.""" icon = resources_path('img', 'icons', 'show-converter-tool.svg') self.action_shake_converter = QAction(QIcon(icon), self.tr('Shakemap Converter'), self.iface.mainWindow()) self.action_shake_converter.setStatusTip( self.tr('Open InaSAFE Converter')) self.action_shake_converter.setWhatsThis( self.tr('Open InaSAFE Converter')) self.action_shake_converter.triggered.connect( self.show_shakemap_importer) self.add_action(self.action_shake_converter, add_to_toolbar=self.full_toolbar) def _create_batch_runner_action(self): """Create action for batch runner dialog.""" icon = resources_path('img', 'icons', 'show-batch-runner.svg') self.action_batch_runner = QAction(QIcon(icon), self.tr('Batch Runner'), self.iface.mainWindow()) self.action_batch_runner.setStatusTip(self.tr('Open Batch Runner')) self.action_batch_runner.setWhatsThis(self.tr('Open Batch Runner')) self.action_batch_runner.triggered.connect(self.show_batch_runner) self.add_action(self.action_batch_runner, add_to_toolbar=self.full_toolbar) def _create_save_scenario_action(self): """Create action for save scenario dialog.""" icon = resources_path('img', 'icons', 'save-as-scenario.svg') self.action_save_scenario = QAction(QIcon(icon), self.tr('Save Current Scenario'), self.iface.mainWindow()) message = self.tr('Save current scenario to text file') self.action_save_scenario.setStatusTip(message) self.action_save_scenario.setWhatsThis(message) # noinspection PyUnresolvedReferences self.action_save_scenario.triggered.connect(self.save_scenario) self.add_action(self.action_save_scenario, add_to_toolbar=self.full_toolbar) def _create_osm_downloader_action(self): """Create action for import OSM Dialog.""" icon = resources_path('img', 'icons', 'show-osm-download.svg') self.action_import_dialog = QAction( QIcon(icon), self.tr('OpenStreetMap Downloader'), self.iface.mainWindow()) self.action_import_dialog.setStatusTip( self.tr('OpenStreetMap Downloader')) self.action_import_dialog.setWhatsThis( self.tr('OpenStreetMap Downloader')) self.action_import_dialog.triggered.connect(self.show_osm_downloader) self.add_action(self.action_import_dialog, add_to_toolbar=True) def _create_geonode_uploader_action(self): """Create action for Geonode uploader dialog.""" icon = resources_path('img', 'icons', 'geonode.png') label = tr('Geonode Uploader') self.action_geonode = QAction(QIcon(icon), label, self.iface.mainWindow()) self.action_geonode.setStatusTip(label) self.action_geonode.setWhatsThis(label) self.action_geonode.triggered.connect(self.show_geonode_uploader) self.add_action(self.action_geonode, add_to_toolbar=False) def _create_add_osm_layer_action(self): """Create action for import OSM Dialog.""" icon = resources_path('img', 'icons', 'add-osm-tiles-layer.svg') self.action_add_osm_layer = QAction( QIcon(icon), self.tr('Add OpenStreetMap Tile Layer'), self.iface.mainWindow()) self.action_add_osm_layer.setStatusTip( self.tr('Add OpenStreetMap Tile Layer')) self.action_add_osm_layer.setWhatsThis( self.tr('Use this to add an OSM layer to your map. ' 'It needs internet access to function.')) self.action_add_osm_layer.triggered.connect(self.add_osm_layer) self.add_action(self.action_add_osm_layer, add_to_toolbar=True) def _create_show_definitions_action(self): """Create action for showing definitions / help.""" icon = resources_path('img', 'icons', 'show-inasafe-help.svg') self.action_show_definitions = QAction(QIcon(icon), self.tr('InaSAFE Help'), self.iface.mainWindow()) self.action_show_definitions.setStatusTip(self.tr('Show InaSAFE Help')) self.action_show_definitions.setWhatsThis( self. tr('Use this to show a document describing all InaSAFE concepts.')) self.action_show_definitions.triggered.connect(self.show_definitions) self.add_action(self.action_show_definitions, add_to_toolbar=True) def _create_metadata_converter_action(self): """Create action for showing metadata converter dialog.""" icon = resources_path('img', 'icons', 'show-metadata-converter.svg') self.action_metadata_converter = QAction( QIcon(icon), self.tr('InaSAFE Metadata Converter'), self.iface.mainWindow()) self.action_metadata_converter.setStatusTip( self.tr('Convert metadata from version 4.3 to version 3.5.')) self.action_metadata_converter.setWhatsThis( self.tr('Use this tool to convert metadata 4.3 to version 3.5')) self.action_metadata_converter.triggered.connect( self.show_metadata_converter) self.add_action(self.action_metadata_converter, add_to_toolbar=self.full_toolbar) def _create_field_mapping_action(self): """Create action for showing field mapping dialog.""" icon = resources_path('img', 'icons', 'show-mapping-tool.svg') self.action_field_mapping = QAction( QIcon(icon), self.tr('InaSAFE Field Mapping Tool'), self.iface.mainWindow()) self.action_field_mapping.setStatusTip( self.tr('Assign field mapping to layer.')) self.action_field_mapping.setWhatsThis( self.tr('Use this tool to assign field mapping in layer.')) self.action_field_mapping.setEnabled(False) self.action_field_mapping.triggered.connect(self.show_field_mapping) self.add_action(self.action_field_mapping, add_to_toolbar=self.full_toolbar) def _create_multi_exposure_action(self): """Create action for showing the multi exposure tool.""" self.action_multi_exposure = QAction( QIcon(resources_path('img', 'icons', 'show-multi-exposure.svg')), self.tr('InaSAFE Multi Exposure Tool'), self.iface.mainWindow()) self.action_multi_exposure.setStatusTip( self.tr('Open the multi exposure tool.')) self.action_multi_exposure.setWhatsThis( self.tr('Open the multi exposure tool.')) self.action_multi_exposure.setEnabled(True) self.action_multi_exposure.triggered.connect(self.show_multi_exposure) self.add_action(self.action_multi_exposure, add_to_toolbar=self.full_toolbar) def _create_add_petabencana_layer_action(self): """Create action for import OSM Dialog.""" icon = resources_path('img', 'icons', 'add-petabencana-layer.svg') self.action_add_petabencana_layer = QAction( QIcon(icon), self.tr('Add PetaBencana Flood Layer'), self.iface.mainWindow()) self.action_add_petabencana_layer.setStatusTip( self.tr('Add PetaBencana Flood Layer')) self.action_add_petabencana_layer.setWhatsThis( self.tr('Use this to add a PetaBencana layer to your map. ' 'It needs internet access to function.')) self.action_add_petabencana_layer.triggered.connect( self.add_petabencana_layer) self.add_action(self.action_add_petabencana_layer, add_to_toolbar=self.full_toolbar) def _create_rubber_bands_action(self): """Create action for toggling rubber bands.""" icon = resources_path('img', 'icons', 'toggle-rubber-bands.svg') self.action_toggle_rubberbands = QAction( QIcon(icon), self.tr('Toggle Scenario Outlines'), self.iface.mainWindow()) message = self.tr('Toggle rubber bands showing scenario extents.') self.action_toggle_rubberbands.setStatusTip(message) self.action_toggle_rubberbands.setWhatsThis(message) # Set initial state self.action_toggle_rubberbands.setCheckable(True) flag = setting('showRubberBands', False, expected_type=bool) self.action_toggle_rubberbands.setChecked(flag) # noinspection PyUnresolvedReferences self.action_toggle_rubberbands.triggered.connect( self.dock_widget.toggle_rubber_bands) self.add_action(self.action_toggle_rubberbands) def _create_analysis_extent_action(self): """Create action for analysis extent dialog.""" icon = resources_path('img', 'icons', 'set-extents-tool.svg') self.action_extent_selector = QAction(QIcon(icon), self.tr('Set Analysis Area'), self.iface.mainWindow()) self.action_extent_selector.setStatusTip( self.tr('Set the analysis area for InaSAFE')) self.action_extent_selector.setWhatsThis( self.tr('Set the analysis area for InaSAFE')) self.action_extent_selector.triggered.connect( self.show_extent_selector) self.add_action(self.action_extent_selector) def _create_test_layers_action(self): """Create action for adding layers (developer mode, non final only).""" if self.hide_developer_buttons: return icon = resources_path('img', 'icons', 'add-test-layers.svg') self.action_add_layers = QAction(QIcon(icon), self.tr('Add Test Layers'), self.iface.mainWindow()) self.action_add_layers.setStatusTip(self.tr('Add test layers')) self.action_add_layers.setWhatsThis(self.tr('Add test layers')) self.action_add_layers.triggered.connect(self.add_test_layers) self.add_action(self.action_add_layers) def _create_run_test_action(self): """Create action for running tests (developer mode, non final only).""" if self.hide_developer_buttons: return default_package = str(setting('testPackage', 'safe', expected_type=str)) msg = self.tr('Run tests in %s' % default_package) self.test_button = QToolButton() self.test_button.setMenu(QMenu()) self.test_button.setPopupMode(QToolButton.MenuButtonPopup) icon = resources_path('img', 'icons', 'run-tests.svg') self.action_run_tests = QAction(QIcon(icon), msg, self.iface.mainWindow()) self.action_run_tests.setStatusTip(msg) self.action_run_tests.setWhatsThis(msg) self.action_run_tests.triggered.connect(self.run_tests) self.test_button.menu().addAction(self.action_run_tests) self.test_button.setDefaultAction(self.action_run_tests) self.action_select_package = QAction(QIcon(icon), self.tr('Select package'), self.iface.mainWindow()) self.action_select_package.setStatusTip(self.tr('Select Test Package')) self.action_select_package.setWhatsThis(self.tr('Select Test Package')) self.action_select_package.triggered.connect(self.select_test_package) self.test_button.menu().addAction(self.action_select_package) self.toolbar.addWidget(self.test_button) self.add_action(self.action_run_tests, add_to_toolbar=False) self.add_action(self.action_select_package, add_to_toolbar=False) def _create_dock(self): """Create dockwidget and tabify it with the legend.""" # Import dock here as it needs to be imported AFTER i18n is set up from safe.gui.widgets.dock import Dock self.dock_widget = Dock(self.iface) self.dock_widget.setObjectName('InaSAFE-Dock') self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget) legend_tab = self.iface.mainWindow().findChild(QApplication, 'Legend') if legend_tab: self.iface.mainWindow().tabifyDockWidget(legend_tab, self.dock_widget) self.dock_widget.raise_() # noinspection PyPep8Naming def initGui(self): """Gui initialisation procedure (for QGIS plugin api). .. note:: Don't change the name of this method from initGui! This method is called by QGIS and should be used to set up any graphical user interface elements that should appear in QGIS by default (i.e. before the user performs any explicit action with the plugin). """ self.toolbar = self.iface.addToolBar('InaSAFE') self.toolbar.setObjectName('InaSAFEToolBar') self.dock_widget = None # Now create the actual dock self._create_dock() # And all the menu actions # Configuration Group self._create_dock_toggle_action() self._create_options_dialog_action() self._create_minimum_needs_options_action() self._create_analysis_extent_action() self._create_rubber_bands_action() self._add_spacer_to_menu() self._create_keywords_wizard_action() self._create_analysis_wizard_action() self._add_spacer_to_menu() self._create_field_mapping_action() self._create_multi_exposure_action() self._create_metadata_converter_action() self._create_osm_downloader_action() self._create_add_osm_layer_action() self._create_add_petabencana_layer_action() self._create_geonode_uploader_action() self._create_shakemap_converter_action() self._create_minimum_needs_action() self._create_multi_buffer_action() self._create_test_layers_action() self._create_run_test_action() self._add_spacer_to_menu() self._create_batch_runner_action() self._create_save_scenario_action() self._add_spacer_to_menu() self._create_show_definitions_action() # Hook up a slot for when the dock is hidden using its close button # or view-panels # self.dock_widget.visibilityChanged.connect(self.toggle_inasafe_action) # Also deal with the fact that on start of QGIS dock may already be # hidden. self.action_dock.setChecked(self.dock_widget.isVisible()) self.iface.initializationCompleted.connect( partial(self.show_welcome_message)) def _add_spacer_to_menu(self): """Create a spacer to the menu to separate action groups.""" separator = QAction(self.iface.mainWindow()) separator.setSeparator(True) self.iface.addPluginToMenu(self.tr('InaSAFE'), separator) @staticmethod def clear_modules(): """Unload inasafe functions and try to return QGIS to before InaSAFE. .. todo:: I think this function can be removed. TS. """ # next lets force remove any inasafe related modules modules = [] for module in sys.modules: if 'inasafe' in module: # Check if it is really one of our modules i.e. exists in the # plugin directory tokens = module.split('.') path = '' for myToken in tokens: path += os.path.sep + myToken parent = os.path.abspath( os.path.join(__file__, os.path.pardir, os.path.pardir)) full_path = os.path.join(parent, path + '.py') if os.path.exists(os.path.abspath(full_path)): LOGGER.debug('Removing: %s' % module) modules.append(module) for module in modules: del (sys.modules[module]) for module in sys.modules: if 'inasafe' in module: print(module) # Lets also clean up all the path additions that were made package_path = os.path.abspath( os.path.join(os.path.dirname(__file__), os.path.pardir)) LOGGER.debug('Path to remove: %s' % package_path) # We use a list comprehension to ensure duplicate entries are removed LOGGER.debug(sys.path) sys.path = [y for y in sys.path if package_path not in y] LOGGER.debug(sys.path) def unload(self): """GUI breakdown procedure (for QGIS plugin api). .. note:: Don't change the name of this method from unload! This method is called by QGIS and should be used to *remove* any graphical user interface elements that should appear in QGIS. """ # Remove the plugin menu item and icon if self.wizard: self.wizard.deleteLater() for myAction in self.actions: self.iface.removePluginMenu(self.tr('InaSAFE'), myAction) self.iface.removeToolBarIcon(myAction) self.iface.removeCustomActionForLayerType(myAction) self.iface.mainWindow().removeDockWidget(self.dock_widget) self.iface.mainWindow().removeToolBar(self.toolbar) self.dock_widget.setVisible(False) self.dock_widget.destroy() self.iface.currentLayerChanged.disconnect(self.layer_changed) # Unload QGIS expressions loaded by the plugin. for qgis_expression in list(qgis_expressions().keys()): QgsExpression.unregisterFunction(qgis_expression) def toggle_inasafe_action(self, checked): """Check or un-check the toggle inaSAFE toolbar button. This slot is called when the user hides the inaSAFE panel using its close button or using view->panels. :param checked: True if the dock should be shown, otherwise False. :type checked: bool """ self.action_dock.setChecked(checked) # Run method that performs all the real work def toggle_dock_visibility(self): """Show or hide the dock widget.""" if self.dock_widget.isVisible(): self.dock_widget.setVisible(False) else: self.dock_widget.setVisible(True) self.dock_widget.raise_() def add_test_layers(self): """Add standard test layers.""" from safe.test.utilities import load_standard_layers load_standard_layers() rect = QgsRectangle(106.806, -6.195, 106.837, -6.167) self.iface.mapCanvas().setExtent(rect) def select_test_package(self): """Select the test package.""" default_package = 'safe' user_package = str( setting('testPackage', default_package, expected_type=str)) test_package, _ = QInputDialog.getText( self.iface.mainWindow(), self.tr('Select the python test package'), self.tr('Select the python test package'), QLineEdit.Normal, user_package) if test_package == '': test_package = default_package set_setting('testPackage', test_package) msg = self.tr('Run tests in %s' % test_package) self.action_run_tests.setWhatsThis(msg) self.action_run_tests.setText(msg) def run_tests(self): """Run unit tests in the python console.""" from qgis.PyQt.QtWidgets import QDockWidget main_window = self.iface.mainWindow() action = main_window.findChild(QAction, 'mActionShowPythonDialog') action.trigger() package = str(setting('testPackage', 'safe', expected_type=str)) for child in main_window.findChildren(QDockWidget, 'PythonConsole'): if child.objectName() == 'PythonConsole': child.show() for widget in child.children(): if 'PythonConsoleWidget' in str(widget.__class__): # print "Console widget found" shell = widget.shell shell.runCommand( 'from inasafe.test_suite import test_package') shell.runCommand('test_package(\'%s\')' % package) break def show_extent_selector(self): """Show the extent selector widget for defining analysis extents.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.extent_selector_dialog import ExtentSelectorDialog widget = ExtentSelectorDialog( self.iface, self.iface.mainWindow(), extent=self.dock_widget.extent.user_extent, crs=self.dock_widget.extent.crs) widget.clear_extent.connect( self.dock_widget.extent.clear_user_analysis_extent) widget.extent_defined.connect( self.dock_widget.define_user_analysis_extent) # This ensures that run button state is updated on dialog close widget.extent_selector_closed.connect( self.dock_widget.validate_impact_function) # Needs to be non modal to support hide -> interact with map -> show widget.show() # non modal def show_minimum_needs(self): """Show the minimum needs dialog.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.minimum_needs.needs_calculator_dialog import ( NeedsCalculatorDialog) dialog = NeedsCalculatorDialog(self.iface.mainWindow()) dialog.exec_() def show_minimum_needs_configuration(self): """Show the minimum needs dialog.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.minimum_needs.needs_manager_dialog import ( NeedsManagerDialog) dialog = NeedsManagerDialog(parent=self.iface.mainWindow(), dock=self.dock_widget) dialog.exec_() # modal def show_options(self): """Show the options dialog.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.options_dialog import OptionsDialog dialog = OptionsDialog(iface=self.iface, parent=self.iface.mainWindow()) dialog.show_option_dialog() if dialog.exec_(): # modal self.dock_widget.read_settings() from safe.gui.widgets.message import getting_started_message send_static_message(self.dock_widget, getting_started_message()) # Issue #4734, make sure to update the combobox after update the # InaSAFE option self.dock_widget.get_layers() def show_welcome_message(self): """Show the welcome message.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.options_dialog import OptionsDialog # Do not show by default show_message = False previous_version = StrictVersion(setting('previous_version')) current_version = StrictVersion(inasafe_version) # Set previous_version to the current inasafe_version set_setting('previous_version', inasafe_version) if setting('always_show_welcome_message', expected_type=bool): # Show if it the setting said so show_message = True elif previous_version < current_version: # Always show if the user installed new version show_message = True # Allow to disable welcome message when running automated tests if os.environ.get('INASAFE_DISABLE_WELCOME_MESSAGE', False): show_message = False if show_message: dialog = OptionsDialog(iface=self.iface, parent=self.iface.mainWindow()) dialog.show_welcome_dialog() if dialog.exec_(): # modal self.dock_widget.read_settings() def show_keywords_wizard(self): """Show the keywords creation wizard.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.wizard.wizard_dialog import WizardDialog if self.iface.activeLayer() is None: return # Don't break an existing wizard session if accidentally clicked if self.wizard and self.wizard.isVisible(): return # Prevent spawning multiple copies since the IFCW is non modal if not self.wizard: self.wizard = WizardDialog(self.iface.mainWindow(), self.iface, self.dock_widget) self.wizard.set_keywords_creation_mode() self.wizard.exec_() # modal def show_function_centric_wizard(self): """Show the function centric wizard.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.wizard.wizard_dialog import WizardDialog # Don't break an existing wizard session if accidentally clicked if self.wizard and self.wizard.isVisible(): return # Prevent spawning multiple copies since it is non modal if not self.wizard: self.wizard = WizardDialog(self.iface.mainWindow(), self.iface, self.dock_widget) self.wizard.set_function_centric_mode() # non-modal in order to hide for selecting user extent self.wizard.show() def show_shakemap_importer(self): """Show the converter dialog.""" # import here only so that it is AFTER i18n set up from safe.gui.tools.shake_grid.shakemap_converter_dialog import ( ShakemapConverterDialog) dialog = ShakemapConverterDialog(self.iface.mainWindow(), self.iface, self.dock_widget) dialog.exec_() # modal def show_multi_buffer(self): """Show the multi buffer tool.""" from safe.gui.tools.multi_buffer_dialog import (MultiBufferDialog) dialog = MultiBufferDialog(self.iface.mainWindow(), self.iface, self.dock_widget) dialog.exec_() # modal def show_osm_downloader(self): """Show the OSM buildings downloader dialog.""" from safe.gui.tools.osm_downloader_dialog import OsmDownloaderDialog dialog = OsmDownloaderDialog(self.iface.mainWindow(), self.iface) # otherwise dialog is never deleted dialog.setAttribute(Qt.WA_DeleteOnClose, True) dialog.show() # non modal def show_geonode_uploader(self): """Show the Geonode uploader dialog.""" from safe.gui.tools.geonode_uploader import GeonodeUploaderDialog dialog = GeonodeUploaderDialog(self.iface.mainWindow()) dialog.show() # non modal def add_osm_layer(self): """Add OSM tile layer to the map. This uses a gdal wrapper around the OSM tile service - see the WorldOSM.gdal file for how it is constructed. """ path = resources_path('osm', 'WorldOSM.gdal') layer = QgsRasterLayer(path, self.tr('OpenStreetMap')) project = QgsProject.instance() # Try to add it as the last layer in the list # False flag prevents layer being added to legend project.addMapLayer(layer, False) root = QgsProject.instance().layerTreeRoot() index = len(root.findLayers()) + 1 # LOGGER.info('Inserting layer %s at position %s' % ( # layer.source(), index)) root.insertLayer(index, layer) project.addMapLayer(layer) def show_definitions(self): """Show InaSAFE Definitions (a report showing all key metadata).""" from safe.utilities.help import show_help from safe.gui.tools.help import definitions_help show_help(definitions_help.definitions_help()) def show_field_mapping(self): """Show InaSAFE Field Mapping.""" from safe.gui.tools.field_mapping_dialog import FieldMappingDialog dialog = FieldMappingDialog( parent=self.iface.mainWindow(), iface=self.iface, ) if dialog.exec_(): # modal LOGGER.debug('Show field mapping accepted') self.dock_widget.layer_changed(self.iface.activeLayer()) else: LOGGER.debug('Show field mapping not accepted') def show_metadata_converter(self): """Show InaSAFE Metadata Converter.""" from safe.gui.tools.metadata_converter_dialog import ( MetadataConverterDialog) dialog = MetadataConverterDialog( parent=self.iface.mainWindow(), iface=self.iface, ) dialog.exec_() def show_multi_exposure(self): """Show InaSAFE Multi Exposure.""" from safe.gui.tools.multi_exposure_dialog import MultiExposureDialog dialog = MultiExposureDialog(self.iface.mainWindow(), self.iface) dialog.exec_() # modal def add_petabencana_layer(self): """Add petabencana layer to the map. This uses the PetaBencana API to fetch the latest floods in JK. See https://data.petabencana.id/floods """ from safe.gui.tools.peta_bencana_dialog import PetaBencanaDialog dialog = PetaBencanaDialog(self.iface.mainWindow(), self.iface) dialog.show() # non modal def show_batch_runner(self): """Show the batch runner dialog.""" from safe.gui.tools.batch.batch_dialog import BatchDialog dialog = BatchDialog(parent=self.iface.mainWindow(), iface=self.iface, dock=self.dock_widget) dialog.exec_() # modal def save_scenario(self): """Save current scenario to text file.""" from safe.gui.tools.save_scenario import SaveScenarioDialog dialog = SaveScenarioDialog(iface=self.iface, dock=self.dock_widget) dialog.save_scenario() def layer_changed(self, layer): """Enable or disable keywords editor icon when active layer changes. :param layer: The layer that is now active. :type layer: QgsMapLayer """ if not layer: enable_keyword_wizard = False elif not hasattr(layer, 'providerType'): enable_keyword_wizard = False elif layer.providerType() == 'wms': enable_keyword_wizard = False else: enable_keyword_wizard = True try: if layer: if is_raster_layer(layer): enable_field_mapping_tool = False else: keywords = KeywordIO().read_keywords(layer) keywords_version = keywords.get('keyword_version') if not keywords_version: supported = False else: supported = ( is_keyword_version_supported(keywords_version)) if not supported: enable_field_mapping_tool = False else: layer_purpose = keywords.get('layer_purpose') if not layer_purpose: enable_field_mapping_tool = False else: if layer_purpose == layer_purpose_exposure['key']: layer_subcategory = keywords.get('exposure') elif layer_purpose == layer_purpose_hazard['key']: layer_subcategory = keywords.get('hazard') else: layer_subcategory = None field_groups = get_field_groups( layer_purpose, layer_subcategory) if len(field_groups) == 0: # No field group, disable field mapping tool. enable_field_mapping_tool = False else: enable_field_mapping_tool = True else: enable_field_mapping_tool = False except (KeywordNotFoundError, NoKeywordsFoundError, MetadataReadError): # No keywords, disable field mapping tool. enable_field_mapping_tool = False self.action_keywords_wizard.setEnabled(enable_keyword_wizard) self.action_field_mapping.setEnabled(enable_field_mapping_tool) def shortcut_f7(self): """Executed when user press F7 - will show the shakemap importer.""" self.show_shakemap_importer()
class PythonConsoleWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console")) self.settings = QgsSettings() self.shell = ShellScintilla(self) self.setFocusProxy(self.shell) self.shellOut = ShellOutputScintilla(self) self.tabEditorWidget = EditorTabWidget(self) # ------------ UI ------------------------------- self.splitterEditor = QSplitter(self) self.splitterEditor.setOrientation(Qt.Horizontal) self.splitterEditor.setHandleWidth(6) self.splitterEditor.setChildrenCollapsible(True) self.shellOutWidget = QWidget(self) self.shellOutWidget.setLayout(QVBoxLayout()) self.shellOutWidget.layout().setContentsMargins(0, 0, 0, 0) self.shellOutWidget.layout().addWidget(self.shellOut) self.splitter = QSplitter(self.splitterEditor) self.splitter.setOrientation(Qt.Vertical) self.splitter.setHandleWidth(3) self.splitter.setChildrenCollapsible(False) self.splitter.addWidget(self.shellOutWidget) self.splitter.addWidget(self.shell) # self.splitterEditor.addWidget(self.tabEditorWidget) self.splitterObj = QSplitter(self.splitterEditor) self.splitterObj.setHandleWidth(3) self.splitterObj.setOrientation(Qt.Horizontal) # self.splitterObj.setSizes([0, 0]) # self.splitterObj.setStretchFactor(0, 1) self.widgetEditor = QWidget(self.splitterObj) self.widgetFind = QWidget(self) self.listClassMethod = QTreeWidget(self.splitterObj) self.listClassMethod.setColumnCount(2) objInspLabel = QCoreApplication.translate("PythonConsole", "Object Inspector") self.listClassMethod.setHeaderLabels([objInspLabel, '']) self.listClassMethod.setColumnHidden(1, True) self.listClassMethod.setAlternatingRowColors(True) # self.splitterEditor.addWidget(self.widgetEditor) # self.splitterObj.addWidget(self.listClassMethod) # self.splitterObj.addWidget(self.widgetEditor) # Hide side editor on start up self.splitterObj.hide() self.listClassMethod.hide() # Hide search widget on start up self.widgetFind.hide() icon_size = iface.iconSize(dockedToolbar=True) if iface else QSize(16, 16) sizes = self.splitter.sizes() self.splitter.setSizes(sizes) # ----------------Restore Settings------------------------------------ self.restoreSettingsConsole() # ------------------Toolbar Editor------------------------------------- # Action for Open File openFileBt = QCoreApplication.translate("PythonConsole", "Open Script…") self.openFileButton = QAction(self) self.openFileButton.setCheckable(False) self.openFileButton.setEnabled(True) self.openFileButton.setIcon(QgsApplication.getThemeIcon("console/iconOpenConsole.svg")) self.openFileButton.setMenuRole(QAction.PreferencesRole) self.openFileButton.setIconVisibleInMenu(True) self.openFileButton.setToolTip(openFileBt) self.openFileButton.setText(openFileBt) openExtEditorBt = QCoreApplication.translate("PythonConsole", "Open in External Editor") self.openInEditorButton = QAction(self) self.openInEditorButton.setCheckable(False) self.openInEditorButton.setEnabled(True) self.openInEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconShowEditorConsole.svg")) self.openInEditorButton.setMenuRole(QAction.PreferencesRole) self.openInEditorButton.setIconVisibleInMenu(True) self.openInEditorButton.setToolTip(openExtEditorBt) self.openInEditorButton.setText(openExtEditorBt) # Action for Save File saveFileBt = QCoreApplication.translate("PythonConsole", "Save") self.saveFileButton = QAction(self) self.saveFileButton.setCheckable(False) self.saveFileButton.setEnabled(False) self.saveFileButton.setIcon(QgsApplication.getThemeIcon("console/iconSaveConsole.svg")) self.saveFileButton.setMenuRole(QAction.PreferencesRole) self.saveFileButton.setIconVisibleInMenu(True) self.saveFileButton.setToolTip(saveFileBt) self.saveFileButton.setText(saveFileBt) # Action for Save File As saveAsFileBt = QCoreApplication.translate("PythonConsole", "Save As…") self.saveAsFileButton = QAction(self) self.saveAsFileButton.setCheckable(False) self.saveAsFileButton.setEnabled(True) self.saveAsFileButton.setIcon(QgsApplication.getThemeIcon("console/iconSaveAsConsole.svg")) self.saveAsFileButton.setMenuRole(QAction.PreferencesRole) self.saveAsFileButton.setIconVisibleInMenu(True) self.saveAsFileButton.setToolTip(saveAsFileBt) self.saveAsFileButton.setText(saveAsFileBt) # Action Cut cutEditorBt = QCoreApplication.translate("PythonConsole", "Cut") self.cutEditorButton = QAction(self) self.cutEditorButton.setCheckable(False) self.cutEditorButton.setEnabled(True) self.cutEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditCut.svg")) self.cutEditorButton.setMenuRole(QAction.PreferencesRole) self.cutEditorButton.setIconVisibleInMenu(True) self.cutEditorButton.setToolTip(cutEditorBt) self.cutEditorButton.setText(cutEditorBt) # Action Copy copyEditorBt = QCoreApplication.translate("PythonConsole", "Copy") self.copyEditorButton = QAction(self) self.copyEditorButton.setCheckable(False) self.copyEditorButton.setEnabled(True) self.copyEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditCopy.svg")) self.copyEditorButton.setMenuRole(QAction.PreferencesRole) self.copyEditorButton.setIconVisibleInMenu(True) self.copyEditorButton.setToolTip(copyEditorBt) self.copyEditorButton.setText(copyEditorBt) # Action Paste pasteEditorBt = QCoreApplication.translate("PythonConsole", "Paste") self.pasteEditorButton = QAction(self) self.pasteEditorButton.setCheckable(False) self.pasteEditorButton.setEnabled(True) self.pasteEditorButton.setIcon(QgsApplication.getThemeIcon("mActionEditPaste.svg")) self.pasteEditorButton.setMenuRole(QAction.PreferencesRole) self.pasteEditorButton.setIconVisibleInMenu(True) self.pasteEditorButton.setToolTip(pasteEditorBt) self.pasteEditorButton.setText(pasteEditorBt) # Action Run Script (subprocess) runScriptEditorBt = QCoreApplication.translate("PythonConsole", "Run Script") self.runScriptEditorButton = QAction(self) self.runScriptEditorButton.setCheckable(False) self.runScriptEditorButton.setEnabled(True) self.runScriptEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconRunScriptConsole.svg")) self.runScriptEditorButton.setMenuRole(QAction.PreferencesRole) self.runScriptEditorButton.setIconVisibleInMenu(True) self.runScriptEditorButton.setToolTip(runScriptEditorBt) self.runScriptEditorButton.setText(runScriptEditorBt) # Action Run Script (subprocess) commentEditorBt = QCoreApplication.translate("PythonConsole", "Comment") self.commentEditorButton = QAction(self) self.commentEditorButton.setCheckable(False) self.commentEditorButton.setEnabled(True) self.commentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg")) self.commentEditorButton.setMenuRole(QAction.PreferencesRole) self.commentEditorButton.setIconVisibleInMenu(True) self.commentEditorButton.setToolTip(commentEditorBt) self.commentEditorButton.setText(commentEditorBt) # Action Run Script (subprocess) uncommentEditorBt = QCoreApplication.translate("PythonConsole", "Uncomment") self.uncommentEditorButton = QAction(self) self.uncommentEditorButton.setCheckable(False) self.uncommentEditorButton.setEnabled(True) self.uncommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconUncommentEditorConsole.svg")) self.uncommentEditorButton.setMenuRole(QAction.PreferencesRole) self.uncommentEditorButton.setIconVisibleInMenu(True) self.uncommentEditorButton.setToolTip(uncommentEditorBt) self.uncommentEditorButton.setText(uncommentEditorBt) # Action for Object browser objList = QCoreApplication.translate("PythonConsole", "Object Inspector…") self.objectListButton = QAction(self) self.objectListButton.setCheckable(True) self.objectListButton.setEnabled(self.settings.value("pythonConsole/enableObjectInsp", False, type=bool)) self.objectListButton.setIcon(QgsApplication.getThemeIcon("console/iconClassBrowserConsole.svg")) self.objectListButton.setMenuRole(QAction.PreferencesRole) self.objectListButton.setIconVisibleInMenu(True) self.objectListButton.setToolTip(objList) self.objectListButton.setText(objList) # Action for Find text findText = QCoreApplication.translate("PythonConsole", "Find Text") self.findTextButton = QAction(self) self.findTextButton.setCheckable(True) self.findTextButton.setEnabled(True) self.findTextButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchEditorConsole.svg")) self.findTextButton.setMenuRole(QAction.PreferencesRole) self.findTextButton.setIconVisibleInMenu(True) self.findTextButton.setToolTip(findText) self.findTextButton.setText(findText) # ----------------Toolbar Console------------------------------------- # Action Show Editor showEditor = QCoreApplication.translate("PythonConsole", "Show Editor") self.showEditorButton = QAction(self) self.showEditorButton.setEnabled(True) self.showEditorButton.setCheckable(True) self.showEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconShowEditorConsole.svg")) self.showEditorButton.setMenuRole(QAction.PreferencesRole) self.showEditorButton.setIconVisibleInMenu(True) self.showEditorButton.setToolTip(showEditor) self.showEditorButton.setText(showEditor) # Action for Clear button clearBt = QCoreApplication.translate("PythonConsole", "Clear Console") self.clearButton = QAction(self) self.clearButton.setCheckable(False) self.clearButton.setEnabled(True) self.clearButton.setIcon(QgsApplication.getThemeIcon("console/iconClearConsole.svg")) self.clearButton.setMenuRole(QAction.PreferencesRole) self.clearButton.setIconVisibleInMenu(True) self.clearButton.setToolTip(clearBt) self.clearButton.setText(clearBt) # Action for settings optionsBt = QCoreApplication.translate("PythonConsole", "Options…") self.optionsButton = QAction(self) self.optionsButton.setCheckable(False) self.optionsButton.setEnabled(True) self.optionsButton.setIcon(QgsApplication.getThemeIcon("console/iconSettingsConsole.svg")) self.optionsButton.setMenuRole(QAction.PreferencesRole) self.optionsButton.setIconVisibleInMenu(True) self.optionsButton.setToolTip(optionsBt) self.optionsButton.setText(optionsBt) # Action for Run script runBt = QCoreApplication.translate("PythonConsole", "Run Command") self.runButton = QAction(self) self.runButton.setCheckable(False) self.runButton.setEnabled(True) self.runButton.setIcon(QgsApplication.getThemeIcon("console/mIconRunConsole.svg")) self.runButton.setMenuRole(QAction.PreferencesRole) self.runButton.setIconVisibleInMenu(True) self.runButton.setToolTip(runBt) self.runButton.setText(runBt) # Help action helpBt = QCoreApplication.translate("PythonConsole", "Help…") self.helpButton = QAction(self) self.helpButton.setCheckable(False) self.helpButton.setEnabled(True) self.helpButton.setIcon(QgsApplication.getThemeIcon("console/iconHelpConsole.svg")) self.helpButton.setMenuRole(QAction.PreferencesRole) self.helpButton.setIconVisibleInMenu(True) self.helpButton.setToolTip(helpBt) self.helpButton.setText(helpBt) self.toolBar = QToolBar() self.toolBar.setEnabled(True) self.toolBar.setFocusPolicy(Qt.NoFocus) self.toolBar.setContextMenuPolicy(Qt.DefaultContextMenu) self.toolBar.setLayoutDirection(Qt.LeftToRight) self.toolBar.setIconSize(icon_size) self.toolBar.setMovable(False) self.toolBar.setFloatable(False) self.toolBar.addAction(self.clearButton) self.toolBar.addAction(self.runButton) self.toolBar.addSeparator() self.toolBar.addAction(self.showEditorButton) self.toolBar.addSeparator() self.toolBar.addAction(self.optionsButton) self.toolBar.addAction(self.helpButton) self.toolBarEditor = QToolBar() self.toolBarEditor.setEnabled(False) self.toolBarEditor.setFocusPolicy(Qt.NoFocus) self.toolBarEditor.setContextMenuPolicy(Qt.DefaultContextMenu) self.toolBarEditor.setLayoutDirection(Qt.LeftToRight) self.toolBarEditor.setIconSize(icon_size) self.toolBarEditor.setMovable(False) self.toolBarEditor.setFloatable(False) self.toolBarEditor.addAction(self.openFileButton) self.toolBarEditor.addAction(self.openInEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.saveFileButton) self.toolBarEditor.addAction(self.saveAsFileButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.runScriptEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.findTextButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.cutEditorButton) self.toolBarEditor.addAction(self.copyEditorButton) self.toolBarEditor.addAction(self.pasteEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.commentEditorButton) self.toolBarEditor.addAction(self.uncommentEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.objectListButton) self.widgetButton = QWidget() sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widgetButton.sizePolicy().hasHeightForWidth()) self.widgetButton.setSizePolicy(sizePolicy) self.widgetButtonEditor = QWidget(self.widgetEditor) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widgetButtonEditor.sizePolicy().hasHeightForWidth()) self.widgetButtonEditor.setSizePolicy(sizePolicy) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.shellOut.sizePolicy().hasHeightForWidth()) self.shellOut.setSizePolicy(sizePolicy) self.shellOut.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.shell.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # ------------ Layout ------------------------------- self.mainLayout = QGridLayout(self) self.mainLayout.setMargin(0) self.mainLayout.setSpacing(0) self.mainLayout.addWidget(self.widgetButton, 0, 0, 1, 1) self.mainLayout.addWidget(self.splitterEditor, 0, 1, 1, 1) self.shellOutWidget.layout().insertWidget(0, self.toolBar) self.layoutEditor = QGridLayout(self.widgetEditor) self.layoutEditor.setMargin(0) self.layoutEditor.setSpacing(0) self.layoutEditor.addWidget(self.toolBarEditor, 0, 1, 1, 1) self.layoutEditor.addWidget(self.widgetButtonEditor, 1, 0, 2, 1) self.layoutEditor.addWidget(self.tabEditorWidget, 1, 1, 1, 1) self.layoutEditor.addWidget(self.widgetFind, 2, 1, 1, 1) # Layout for the find widget self.layoutFind = QGridLayout(self.widgetFind) self.layoutFind.setContentsMargins(0, 0, 0, 0) self.lineEditFind = QgsFilterLineEdit() placeHolderTxt = QCoreApplication.translate("PythonConsole", "Enter text to find…") self.lineEditFind.setPlaceholderText(placeHolderTxt) self.toolBarFindText = QToolBar() self.toolBarFindText.setIconSize(icon_size) self.findNextButton = QAction(self) self.findNextButton.setEnabled(False) toolTipfindNext = QCoreApplication.translate("PythonConsole", "Find Next") self.findNextButton.setToolTip(toolTipfindNext) self.findNextButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchNextEditorConsole.svg")) self.findPrevButton = QAction(self) self.findPrevButton.setEnabled(False) toolTipfindPrev = QCoreApplication.translate("PythonConsole", "Find Previous") self.findPrevButton.setToolTip(toolTipfindPrev) self.findPrevButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchPrevEditorConsole.svg")) self.caseSensitive = QCheckBox() caseSensTr = QCoreApplication.translate("PythonConsole", "Case Sensitive") self.caseSensitive.setText(caseSensTr) self.wholeWord = QCheckBox() wholeWordTr = QCoreApplication.translate("PythonConsole", "Whole Word") self.wholeWord.setText(wholeWordTr) self.wrapAround = QCheckBox() self.wrapAround.setChecked(True) wrapAroundTr = QCoreApplication.translate("PythonConsole", "Wrap Around") self.wrapAround.setText(wrapAroundTr) self.toolBarFindText.addWidget(self.lineEditFind) self.toolBarFindText.addAction(self.findPrevButton) self.toolBarFindText.addAction(self.findNextButton) self.toolBarFindText.addWidget(self.caseSensitive) self.toolBarFindText.addWidget(self.wholeWord) self.toolBarFindText.addWidget(self.wrapAround) self.layoutFind.addWidget(self.toolBarFindText, 0, 1, 1, 1) # ------------ Add first Tab in Editor ------------------------------- # self.tabEditorWidget.newTabEditor(tabName='first', filename=None) # ------------ Signal ------------------------------- self.findTextButton.triggered.connect(self._toggleFind) self.objectListButton.toggled.connect(self.toggleObjectListWidget) self.commentEditorButton.triggered.connect(self.commentCode) self.uncommentEditorButton.triggered.connect(self.uncommentCode) self.runScriptEditorButton.triggered.connect(self.runScriptEditor) self.cutEditorButton.triggered.connect(self.cutEditor) self.copyEditorButton.triggered.connect(self.copyEditor) self.pasteEditorButton.triggered.connect(self.pasteEditor) self.showEditorButton.toggled.connect(self.toggleEditor) self.clearButton.triggered.connect(self.shellOut.clearConsole) self.optionsButton.triggered.connect(self.openSettings) self.runButton.triggered.connect(self.shell.entered) self.openFileButton.triggered.connect(self.openScriptFile) self.openInEditorButton.triggered.connect(self.openScriptFileExtEditor) self.saveFileButton.triggered.connect(self.saveScriptFile) self.saveAsFileButton.triggered.connect(self.saveAsScriptFile) self.helpButton.triggered.connect(self.openHelp) self.listClassMethod.itemClicked.connect(self.onClickGoToLine) self.lineEditFind.returnPressed.connect(self._findNext) self.findNextButton.triggered.connect(self._findNext) self.findPrevButton.triggered.connect(self._findPrev) self.lineEditFind.textChanged.connect(self._textFindChanged) self.findScut = QShortcut(QKeySequence.Find, self.widgetEditor) self.findScut.setContext(Qt.WidgetWithChildrenShortcut) self.findScut.activated.connect(self._openFind) self.findNextScut = QShortcut(QKeySequence.FindNext, self.widgetEditor) self.findNextScut.setContext(Qt.WidgetWithChildrenShortcut) self.findNextScut.activated.connect(self._findNext) self.findPreviousScut = QShortcut(QKeySequence.FindPrevious, self.widgetEditor) self.findPreviousScut.setContext(Qt.WidgetWithChildrenShortcut) self.findPreviousScut.activated.connect(self._findPrev) # Escape on editor hides the find bar self.findScut = QShortcut(Qt.Key_Escape, self.widgetEditor) self.findScut.setContext(Qt.WidgetWithChildrenShortcut) self.findScut.activated.connect(self._closeFind) def _toggleFind(self): self.tabEditorWidget.currentWidget().newEditor.toggleFindWidget() def _openFind(self): self.tabEditorWidget.currentWidget().newEditor.openFindWidget() def _closeFind(self): self.tabEditorWidget.currentWidget().newEditor.closeFindWidget() def _findNext(self): self.tabEditorWidget.currentWidget().newEditor.findText(True) def _findPrev(self): self.tabEditorWidget.currentWidget().newEditor.findText(False) def _textFindChanged(self): if self.lineEditFind.text(): self.findNextButton.setEnabled(True) self.findPrevButton.setEnabled(True) self.tabEditorWidget.currentWidget().newEditor.findText(True, showMessage=False, findFirst=True) else: self.lineEditFind.setStyleSheet('') self.findNextButton.setEnabled(False) self.findPrevButton.setEnabled(False) def onClickGoToLine(self, item, column): tabEditor = self.tabEditorWidget.currentWidget().newEditor if item.text(1) == 'syntaxError': check = tabEditor.syntaxCheck(fromContextMenu=False) if check and not tabEditor.isReadOnly(): self.tabEditorWidget.currentWidget().save() return linenr = int(item.text(1)) itemName = str(item.text(0)) charPos = itemName.find(' ') if charPos != -1: objName = itemName[0:charPos] else: objName = itemName tabEditor.goToLine(objName, linenr) def toggleEditor(self, checked): self.splitterObj.show() if checked else self.splitterObj.hide() if not self.tabEditorWidget: self.tabEditorWidget.enableToolBarEditor(checked) self.tabEditorWidget.restoreTabsOrAddNew() def toggleObjectListWidget(self, checked): self.listClassMethod.show() if checked else self.listClassMethod.hide() def pasteEditor(self): self.tabEditorWidget.currentWidget().newEditor.paste() def cutEditor(self): self.tabEditorWidget.currentWidget().newEditor.cut() def copyEditor(self): self.tabEditorWidget.currentWidget().newEditor.copy() def runScriptEditor(self): self.tabEditorWidget.currentWidget().newEditor.runScriptCode() def commentCode(self): self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(True) def uncommentCode(self): self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(False) def openScriptFileExtEditor(self): tabWidget = self.tabEditorWidget.currentWidget() path = tabWidget.path import subprocess try: subprocess.Popen([os.environ['EDITOR'], path]) except KeyError: QDesktopServices.openUrl(QUrl.fromLocalFile(path)) def openScriptFile(self): lastDirPath = self.settings.value("pythonConsole/lastDirPath", QDir.homePath()) openFileTr = QCoreApplication.translate("PythonConsole", "Open File") fileList, selected_filter = QFileDialog.getOpenFileNames( self, openFileTr, lastDirPath, "Script file (*.py)") if fileList: for pyFile in fileList: for i in range(self.tabEditorWidget.count()): tabWidget = self.tabEditorWidget.widget(i) if tabWidget.path == pyFile: self.tabEditorWidget.setCurrentWidget(tabWidget) break else: tabName = QFileInfo(pyFile).fileName() self.tabEditorWidget.newTabEditor(tabName, pyFile) lastDirPath = QFileInfo(pyFile).path() self.settings.setValue("pythonConsole/lastDirPath", pyFile) self.updateTabListScript(pyFile, action='append') def saveScriptFile(self): tabWidget = self.tabEditorWidget.currentWidget() try: tabWidget.save() except (IOError, OSError) as error: msgText = QCoreApplication.translate('PythonConsole', 'The file <b>{0}</b> could not be saved. Error: {1}').format(tabWidget.path, error.strerror) self.callWidgetMessageBarEditor(msgText, 2, False) def saveAsScriptFile(self, index=None): tabWidget = self.tabEditorWidget.currentWidget() if not index: index = self.tabEditorWidget.currentIndex() if not tabWidget.path: fileName = self.tabEditorWidget.tabText(index) + '.py' folder = self.settings.value("pythonConsole/lastDirPath", QDir.homePath()) pathFileName = os.path.join(folder, fileName) fileNone = True else: pathFileName = tabWidget.path fileNone = False saveAsFileTr = QCoreApplication.translate("PythonConsole", "Save File As") filename, filter = QFileDialog.getSaveFileName(self, saveAsFileTr, pathFileName, "Script file (*.py)") if filename: try: tabWidget.save(filename) except (IOError, OSError) as error: msgText = QCoreApplication.translate('PythonConsole', 'The file <b>{0}</b> could not be saved. Error: {1}').format(tabWidget.path, error.strerror) self.callWidgetMessageBarEditor(msgText, 2, False) if fileNone: tabWidget.path = None else: tabWidget.path = pathFileName return if not fileNone: self.updateTabListScript(pathFileName, action='remove') def openHelp(self): QgsHelp.openHelp("plugins/python_console.html") def openSettings(self): if optionsDialog(self).exec_(): self.shell.refreshSettingsShell() self.shellOut.refreshSettingsOutput() self.tabEditorWidget.refreshSettingsEditor() def callWidgetMessageBar(self, text): self.shellOut.widgetMessageBar(iface, text) def callWidgetMessageBarEditor(self, text, level, timed): self.tabEditorWidget.widgetMessageBar(iface, text, level, timed) def updateTabListScript(self, script, action=None): if action == 'remove': self.tabListScript.remove(script) elif action == 'append': if not self.tabListScript: self.tabListScript = [] if script not in self.tabListScript: self.tabListScript.append(script) else: self.tabListScript = [] self.settings.setValue("pythonConsole/tabScripts", self.tabListScript) def saveSettingsConsole(self): self.settings.setValue("pythonConsole/splitterConsole", self.splitter.saveState()) self.settings.setValue("pythonConsole/splitterObj", self.splitterObj.saveState()) self.settings.setValue("pythonConsole/splitterEditor", self.splitterEditor.saveState()) self.shell.writeHistoryFile(True) def restoreSettingsConsole(self): storedTabScripts = self.settings.value("pythonConsole/tabScripts", []) self.tabListScript = storedTabScripts self.splitter.restoreState(self.settings.value("pythonConsole/splitterConsole", QByteArray())) self.splitterEditor.restoreState(self.settings.value("pythonConsole/splitterEditor", QByteArray())) self.splitterObj.restoreState(self.settings.value("pythonConsole/splitterObj", QByteArray()))
class PythonConsoleWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowTitle( QCoreApplication.translate("PythonConsole", "Python Console")) self.settings = QgsSettings() self.shell = ShellScintilla(self) self.setFocusProxy(self.shell) self.shellOut = ShellOutputScintilla(self) self.tabEditorWidget = EditorTabWidget(self) # ------------ UI ------------------------------- self.splitterEditor = QSplitter(self) self.splitterEditor.setOrientation(Qt.Horizontal) self.splitterEditor.setHandleWidth(6) self.splitterEditor.setChildrenCollapsible(True) self.shellOutWidget = QWidget(self) self.shellOutWidget.setLayout(QVBoxLayout()) self.shellOutWidget.layout().setContentsMargins(0, 0, 0, 0) self.shellOutWidget.layout().addWidget(self.shellOut) self.splitter = QSplitter(self.splitterEditor) self.splitter.setOrientation(Qt.Vertical) self.splitter.setHandleWidth(3) self.splitter.setChildrenCollapsible(False) self.splitter.addWidget(self.shellOutWidget) self.splitter.addWidget(self.shell) # self.splitterEditor.addWidget(self.tabEditorWidget) self.splitterObj = QSplitter(self.splitterEditor) self.splitterObj.setHandleWidth(3) self.splitterObj.setOrientation(Qt.Horizontal) # self.splitterObj.setSizes([0, 0]) # self.splitterObj.setStretchFactor(0, 1) self.widgetEditor = QWidget(self.splitterObj) self.widgetFind = QWidget(self) self.listClassMethod = QTreeWidget(self.splitterObj) self.listClassMethod.setColumnCount(2) objInspLabel = QCoreApplication.translate("PythonConsole", "Object Inspector") self.listClassMethod.setHeaderLabels([objInspLabel, '']) self.listClassMethod.setColumnHidden(1, True) self.listClassMethod.setAlternatingRowColors(True) # self.splitterEditor.addWidget(self.widgetEditor) # self.splitterObj.addWidget(self.listClassMethod) # self.splitterObj.addWidget(self.widgetEditor) # Hide side editor on start up self.splitterObj.hide() self.listClassMethod.hide() # Hide search widget on start up self.widgetFind.hide() icon_size = iface.iconSize( dockedToolbar=True) if iface else QSize(16, 16) sizes = self.splitter.sizes() self.splitter.setSizes(sizes) # ----------------Restore Settings------------------------------------ self.restoreSettingsConsole() # ------------------Toolbar Editor------------------------------------- # Action for Open File openFileBt = QCoreApplication.translate("PythonConsole", "Open Script...") self.openFileButton = QAction(self) self.openFileButton.setCheckable(False) self.openFileButton.setEnabled(True) self.openFileButton.setIcon( QgsApplication.getThemeIcon("console/iconOpenConsole.png")) self.openFileButton.setMenuRole(QAction.PreferencesRole) self.openFileButton.setIconVisibleInMenu(True) self.openFileButton.setToolTip(openFileBt) self.openFileButton.setText(openFileBt) openExtEditorBt = QCoreApplication.translate( "PythonConsole", "Open in External Editor") self.openInEditorButton = QAction(self) self.openInEditorButton.setCheckable(False) self.openInEditorButton.setEnabled(True) self.openInEditorButton.setIcon( QgsApplication.getThemeIcon("console/iconShowEditorConsole.png")) self.openInEditorButton.setMenuRole(QAction.PreferencesRole) self.openInEditorButton.setIconVisibleInMenu(True) self.openInEditorButton.setToolTip(openExtEditorBt) self.openInEditorButton.setText(openExtEditorBt) # Action for Save File saveFileBt = QCoreApplication.translate("PythonConsole", "Save") self.saveFileButton = QAction(self) self.saveFileButton.setCheckable(False) self.saveFileButton.setEnabled(False) self.saveFileButton.setIcon( QgsApplication.getThemeIcon("console/iconSaveConsole.png")) self.saveFileButton.setMenuRole(QAction.PreferencesRole) self.saveFileButton.setIconVisibleInMenu(True) self.saveFileButton.setToolTip(saveFileBt) self.saveFileButton.setText(saveFileBt) # Action for Save File As saveAsFileBt = QCoreApplication.translate("PythonConsole", "Save As...") self.saveAsFileButton = QAction(self) self.saveAsFileButton.setCheckable(False) self.saveAsFileButton.setEnabled(True) self.saveAsFileButton.setIcon( QgsApplication.getThemeIcon("console/iconSaveAsConsole.png")) self.saveAsFileButton.setMenuRole(QAction.PreferencesRole) self.saveAsFileButton.setIconVisibleInMenu(True) self.saveAsFileButton.setToolTip(saveAsFileBt) self.saveAsFileButton.setText(saveAsFileBt) # Action Cut cutEditorBt = QCoreApplication.translate("PythonConsole", "Cut") self.cutEditorButton = QAction(self) self.cutEditorButton.setCheckable(False) self.cutEditorButton.setEnabled(True) self.cutEditorButton.setIcon( QgsApplication.getThemeIcon("mActionEditCut.svg")) self.cutEditorButton.setMenuRole(QAction.PreferencesRole) self.cutEditorButton.setIconVisibleInMenu(True) self.cutEditorButton.setToolTip(cutEditorBt) self.cutEditorButton.setText(cutEditorBt) # Action Copy copyEditorBt = QCoreApplication.translate("PythonConsole", "Copy") self.copyEditorButton = QAction(self) self.copyEditorButton.setCheckable(False) self.copyEditorButton.setEnabled(True) self.copyEditorButton.setIcon( QgsApplication.getThemeIcon("mActionEditCopy.svg")) self.copyEditorButton.setMenuRole(QAction.PreferencesRole) self.copyEditorButton.setIconVisibleInMenu(True) self.copyEditorButton.setToolTip(copyEditorBt) self.copyEditorButton.setText(copyEditorBt) # Action Paste pasteEditorBt = QCoreApplication.translate("PythonConsole", "Paste") self.pasteEditorButton = QAction(self) self.pasteEditorButton.setCheckable(False) self.pasteEditorButton.setEnabled(True) self.pasteEditorButton.setIcon( QgsApplication.getThemeIcon("mActionEditPaste.svg")) self.pasteEditorButton.setMenuRole(QAction.PreferencesRole) self.pasteEditorButton.setIconVisibleInMenu(True) self.pasteEditorButton.setToolTip(pasteEditorBt) self.pasteEditorButton.setText(pasteEditorBt) # Action Run Script (subprocess) runScriptEditorBt = QCoreApplication.translate("PythonConsole", "Run script") self.runScriptEditorButton = QAction(self) self.runScriptEditorButton.setCheckable(False) self.runScriptEditorButton.setEnabled(True) self.runScriptEditorButton.setIcon( QgsApplication.getThemeIcon("console/iconRunScriptConsole.png")) self.runScriptEditorButton.setMenuRole(QAction.PreferencesRole) self.runScriptEditorButton.setIconVisibleInMenu(True) self.runScriptEditorButton.setToolTip(runScriptEditorBt) self.runScriptEditorButton.setText(runScriptEditorBt) # Action Run Script (subprocess) commentEditorBt = QCoreApplication.translate("PythonConsole", "Comment") self.commentEditorButton = QAction(self) self.commentEditorButton.setCheckable(False) self.commentEditorButton.setEnabled(True) self.commentEditorButton.setIcon( QgsApplication.getThemeIcon( "console/iconCommentEditorConsole.png")) self.commentEditorButton.setMenuRole(QAction.PreferencesRole) self.commentEditorButton.setIconVisibleInMenu(True) self.commentEditorButton.setToolTip(commentEditorBt) self.commentEditorButton.setText(commentEditorBt) # Action Run Script (subprocess) uncommentEditorBt = QCoreApplication.translate("PythonConsole", "Uncomment") self.uncommentEditorButton = QAction(self) self.uncommentEditorButton.setCheckable(False) self.uncommentEditorButton.setEnabled(True) self.uncommentEditorButton.setIcon( QgsApplication.getThemeIcon( "console/iconUncommentEditorConsole.png")) self.uncommentEditorButton.setMenuRole(QAction.PreferencesRole) self.uncommentEditorButton.setIconVisibleInMenu(True) self.uncommentEditorButton.setToolTip(uncommentEditorBt) self.uncommentEditorButton.setText(uncommentEditorBt) # Action for Object browser objList = QCoreApplication.translate("PythonConsole", "Object Inspector...") self.objectListButton = QAction(self) self.objectListButton.setCheckable(True) self.objectListButton.setEnabled( self.settings.value("pythonConsole/enableObjectInsp", False, type=bool)) self.objectListButton.setIcon( QgsApplication.getThemeIcon("console/iconClassBrowserConsole.png")) self.objectListButton.setMenuRole(QAction.PreferencesRole) self.objectListButton.setIconVisibleInMenu(True) self.objectListButton.setToolTip(objList) self.objectListButton.setText(objList) # Action for Find text findText = QCoreApplication.translate("PythonConsole", "Find Text") self.findTextButton = QAction(self) self.findTextButton.setCheckable(True) self.findTextButton.setEnabled(True) self.findTextButton.setIcon( QgsApplication.getThemeIcon("console/iconSearchEditorConsole.png")) self.findTextButton.setMenuRole(QAction.PreferencesRole) self.findTextButton.setIconVisibleInMenu(True) self.findTextButton.setToolTip(findText) self.findTextButton.setText(findText) # ----------------Toolbar Console------------------------------------- # Action Show Editor showEditor = QCoreApplication.translate("PythonConsole", "Show Editor") self.showEditorButton = QAction(self) self.showEditorButton.setEnabled(True) self.showEditorButton.setCheckable(True) self.showEditorButton.setIcon( QgsApplication.getThemeIcon("console/iconShowEditorConsole.png")) self.showEditorButton.setMenuRole(QAction.PreferencesRole) self.showEditorButton.setIconVisibleInMenu(True) self.showEditorButton.setToolTip(showEditor) self.showEditorButton.setText(showEditor) # Action for Clear button clearBt = QCoreApplication.translate("PythonConsole", "Clear Console") self.clearButton = QAction(self) self.clearButton.setCheckable(False) self.clearButton.setEnabled(True) self.clearButton.setIcon( QgsApplication.getThemeIcon("console/iconClearConsole.png")) self.clearButton.setMenuRole(QAction.PreferencesRole) self.clearButton.setIconVisibleInMenu(True) self.clearButton.setToolTip(clearBt) self.clearButton.setText(clearBt) # Action for settings optionsBt = QCoreApplication.translate("PythonConsole", "Options...") self.optionsButton = QAction(self) self.optionsButton.setCheckable(False) self.optionsButton.setEnabled(True) self.optionsButton.setIcon( QgsApplication.getThemeIcon("console/iconSettingsConsole.png")) self.optionsButton.setMenuRole(QAction.PreferencesRole) self.optionsButton.setIconVisibleInMenu(True) self.optionsButton.setToolTip(optionsBt) self.optionsButton.setText(optionsBt) # Action for Run script runBt = QCoreApplication.translate("PythonConsole", "Run Command") self.runButton = QAction(self) self.runButton.setCheckable(False) self.runButton.setEnabled(True) self.runButton.setIcon( QgsApplication.getThemeIcon("console/iconRunConsole.png")) self.runButton.setMenuRole(QAction.PreferencesRole) self.runButton.setIconVisibleInMenu(True) self.runButton.setToolTip(runBt) self.runButton.setText(runBt) # Help action helpBt = QCoreApplication.translate("PythonConsole", "Help...") self.helpButton = QAction(self) self.helpButton.setCheckable(False) self.helpButton.setEnabled(True) self.helpButton.setIcon( QgsApplication.getThemeIcon("console/iconHelpConsole.png")) self.helpButton.setMenuRole(QAction.PreferencesRole) self.helpButton.setIconVisibleInMenu(True) self.helpButton.setToolTip(helpBt) self.helpButton.setText(helpBt) self.toolBar = QToolBar() self.toolBar.setEnabled(True) self.toolBar.setFocusPolicy(Qt.NoFocus) self.toolBar.setContextMenuPolicy(Qt.DefaultContextMenu) self.toolBar.setLayoutDirection(Qt.LeftToRight) self.toolBar.setIconSize(icon_size) self.toolBar.setMovable(False) self.toolBar.setFloatable(False) self.toolBar.addAction(self.clearButton) self.toolBar.addAction(self.runButton) self.toolBar.addSeparator() self.toolBar.addAction(self.showEditorButton) self.toolBar.addSeparator() self.toolBar.addAction(self.optionsButton) self.toolBar.addAction(self.helpButton) self.toolBarEditor = QToolBar() self.toolBarEditor.setEnabled(False) self.toolBarEditor.setFocusPolicy(Qt.NoFocus) self.toolBarEditor.setContextMenuPolicy(Qt.DefaultContextMenu) self.toolBarEditor.setLayoutDirection(Qt.LeftToRight) self.toolBarEditor.setIconSize(icon_size) self.toolBarEditor.setMovable(False) self.toolBarEditor.setFloatable(False) self.toolBarEditor.addAction(self.openFileButton) self.toolBarEditor.addAction(self.openInEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.saveFileButton) self.toolBarEditor.addAction(self.saveAsFileButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.runScriptEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.findTextButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.cutEditorButton) self.toolBarEditor.addAction(self.copyEditorButton) self.toolBarEditor.addAction(self.pasteEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.commentEditorButton) self.toolBarEditor.addAction(self.uncommentEditorButton) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.objectListButton) self.widgetButton = QWidget() sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.widgetButton.sizePolicy().hasHeightForWidth()) self.widgetButton.setSizePolicy(sizePolicy) self.widgetButtonEditor = QWidget(self.widgetEditor) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.widgetButtonEditor.sizePolicy().hasHeightForWidth()) self.widgetButtonEditor.setSizePolicy(sizePolicy) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.shellOut.sizePolicy().hasHeightForWidth()) self.shellOut.setSizePolicy(sizePolicy) self.shellOut.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.shell.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # ------------ Layout ------------------------------- self.mainLayout = QGridLayout(self) self.mainLayout.setMargin(0) self.mainLayout.setSpacing(0) self.mainLayout.addWidget(self.widgetButton, 0, 0, 1, 1) self.mainLayout.addWidget(self.splitterEditor, 0, 1, 1, 1) self.shellOutWidget.layout().insertWidget(0, self.toolBar) self.layoutEditor = QGridLayout(self.widgetEditor) self.layoutEditor.setMargin(0) self.layoutEditor.setSpacing(0) self.layoutEditor.addWidget(self.toolBarEditor, 0, 1, 1, 1) self.layoutEditor.addWidget(self.widgetButtonEditor, 1, 0, 2, 1) self.layoutEditor.addWidget(self.tabEditorWidget, 1, 1, 1, 1) self.layoutEditor.addWidget(self.widgetFind, 2, 1, 1, 1) # Layout for the find widget self.layoutFind = QGridLayout(self.widgetFind) self.layoutFind.setContentsMargins(0, 0, 0, 0) self.lineEditFind = QgsFilterLineEdit() placeHolderTxt = QCoreApplication.translate("PythonConsole", "Enter text to find...") self.lineEditFind.setPlaceholderText(placeHolderTxt) self.findNextButton = QToolButton() self.findNextButton.setEnabled(False) toolTipfindNext = QCoreApplication.translate("PythonConsole", "Find Next") self.findNextButton.setToolTip(toolTipfindNext) self.findNextButton.setIcon( QgsApplication.getThemeIcon( "console/iconSearchNextEditorConsole.png")) self.findNextButton.setIconSize(QSize(24, 24)) self.findNextButton.setAutoRaise(True) self.findPrevButton = QToolButton() self.findPrevButton.setEnabled(False) toolTipfindPrev = QCoreApplication.translate("PythonConsole", "Find Previous") self.findPrevButton.setToolTip(toolTipfindPrev) self.findPrevButton.setIcon( QgsApplication.getThemeIcon( "console/iconSearchPrevEditorConsole.png")) self.findPrevButton.setIconSize(QSize(24, 24)) self.findPrevButton.setAutoRaise(True) self.caseSensitive = QCheckBox() caseSensTr = QCoreApplication.translate("PythonConsole", "Case Sensitive") self.caseSensitive.setText(caseSensTr) self.wholeWord = QCheckBox() wholeWordTr = QCoreApplication.translate("PythonConsole", "Whole Word") self.wholeWord.setText(wholeWordTr) self.wrapAround = QCheckBox() self.wrapAround.setChecked(True) wrapAroundTr = QCoreApplication.translate("PythonConsole", "Wrap Around") self.wrapAround.setText(wrapAroundTr) self.layoutFind.addWidget(self.lineEditFind, 0, 1, 1, 1) self.layoutFind.addWidget(self.findPrevButton, 0, 2, 1, 1) self.layoutFind.addWidget(self.findNextButton, 0, 3, 1, 1) self.layoutFind.addWidget(self.caseSensitive, 0, 4, 1, 1) self.layoutFind.addWidget(self.wholeWord, 0, 5, 1, 1) self.layoutFind.addWidget(self.wrapAround, 0, 6, 1, 1) # ------------ Add first Tab in Editor ------------------------------- # self.tabEditorWidget.newTabEditor(tabName='first', filename=None) # ------------ Signal ------------------------------- self.findTextButton.triggered.connect(self._toggleFind) self.objectListButton.toggled.connect(self.toggleObjectListWidget) self.commentEditorButton.triggered.connect(self.commentCode) self.uncommentEditorButton.triggered.connect(self.uncommentCode) self.runScriptEditorButton.triggered.connect(self.runScriptEditor) self.cutEditorButton.triggered.connect(self.cutEditor) self.copyEditorButton.triggered.connect(self.copyEditor) self.pasteEditorButton.triggered.connect(self.pasteEditor) self.showEditorButton.toggled.connect(self.toggleEditor) self.clearButton.triggered.connect(self.shellOut.clearConsole) self.optionsButton.triggered.connect(self.openSettings) self.runButton.triggered.connect(self.shell.entered) self.openFileButton.triggered.connect(self.openScriptFile) self.openInEditorButton.triggered.connect(self.openScriptFileExtEditor) self.saveFileButton.triggered.connect(self.saveScriptFile) self.saveAsFileButton.triggered.connect(self.saveAsScriptFile) self.helpButton.triggered.connect(self.openHelp) self.listClassMethod.itemClicked.connect(self.onClickGoToLine) self.lineEditFind.returnPressed.connect(self._findNext) self.findNextButton.clicked.connect(self._findNext) self.findPrevButton.clicked.connect(self._findPrev) self.lineEditFind.textChanged.connect(self._textFindChanged) self.findScut = QShortcut(QKeySequence.Find, self.widgetEditor) self.findScut.setContext(Qt.WidgetWithChildrenShortcut) self.findScut.activated.connect(self._openFind) self.findNextScut = QShortcut(QKeySequence.FindNext, self.widgetEditor) self.findNextScut.setContext(Qt.WidgetWithChildrenShortcut) self.findNextScut.activated.connect(self._findNext) self.findPreviousScut = QShortcut(QKeySequence.FindPrevious, self.widgetEditor) self.findPreviousScut.setContext(Qt.WidgetWithChildrenShortcut) self.findPreviousScut.activated.connect(self._findPrev) # Escape on editor hides the find bar self.findScut = QShortcut(Qt.Key_Escape, self.widgetEditor) self.findScut.setContext(Qt.WidgetWithChildrenShortcut) self.findScut.activated.connect(self._closeFind) def _toggleFind(self): self.tabEditorWidget.currentWidget().newEditor.toggleFindWidget() def _openFind(self): self.tabEditorWidget.currentWidget().newEditor.openFindWidget() def _closeFind(self): self.tabEditorWidget.currentWidget().newEditor.closeFindWidget() def _findNext(self): self.tabEditorWidget.currentWidget().newEditor.findText(True) def _findPrev(self): self.tabEditorWidget.currentWidget().newEditor.findText(False) def _textFindChanged(self): if self.lineEditFind.text(): self.findNextButton.setEnabled(True) self.findPrevButton.setEnabled(True) self.tabEditorWidget.currentWidget().newEditor.findText( True, showMessage=False, findFirst=True) else: self.lineEditFind.setStyleSheet('') self.findNextButton.setEnabled(False) self.findPrevButton.setEnabled(False) def onClickGoToLine(self, item, column): tabEditor = self.tabEditorWidget.currentWidget().newEditor if item.text(1) == 'syntaxError': check = tabEditor.syntaxCheck(fromContextMenu=False) if check and not tabEditor.isReadOnly(): self.tabEditorWidget.currentWidget().save() return linenr = int(item.text(1)) itemName = str(item.text(0)) charPos = itemName.find(' ') if charPos != -1: objName = itemName[0:charPos] else: objName = itemName tabEditor.goToLine(objName, linenr) def toggleEditor(self, checked): self.splitterObj.show() if checked else self.splitterObj.hide() if not self.tabEditorWidget: self.tabEditorWidget.enableToolBarEditor(checked) self.tabEditorWidget.restoreTabsOrAddNew() def toggleObjectListWidget(self, checked): self.listClassMethod.show() if checked else self.listClassMethod.hide() def pasteEditor(self): self.tabEditorWidget.currentWidget().newEditor.paste() def cutEditor(self): self.tabEditorWidget.currentWidget().newEditor.cut() def copyEditor(self): self.tabEditorWidget.currentWidget().newEditor.copy() def runScriptEditor(self): self.tabEditorWidget.currentWidget().newEditor.runScriptCode() def commentCode(self): self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(True) def uncommentCode(self): self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(False) def openScriptFileExtEditor(self): tabWidget = self.tabEditorWidget.currentWidget() path = tabWidget.path import subprocess try: subprocess.Popen([os.environ['EDITOR'], path]) except KeyError: QDesktopServices.openUrl(QUrl.fromLocalFile(path)) def openScriptFile(self): lastDirPath = self.settings.value("pythonConsole/lastDirPath", QDir.homePath()) openFileTr = QCoreApplication.translate("PythonConsole", "Open File") fileList, selected_filter = QFileDialog.getOpenFileNames( self, openFileTr, lastDirPath, "Script file (*.py)") if fileList: for pyFile in fileList: for i in range(self.tabEditorWidget.count()): tabWidget = self.tabEditorWidget.widget(i) if tabWidget.path == pyFile: self.tabEditorWidget.setCurrentWidget(tabWidget) break else: tabName = QFileInfo(pyFile).fileName() self.tabEditorWidget.newTabEditor(tabName, pyFile) lastDirPath = QFileInfo(pyFile).path() self.settings.setValue("pythonConsole/lastDirPath", pyFile) self.updateTabListScript(pyFile, action='append') def saveScriptFile(self): tabWidget = self.tabEditorWidget.currentWidget() try: tabWidget.save() except (IOError, OSError) as error: msgText = QCoreApplication.translate( 'PythonConsole', 'The file <b>{0}</b> could not be saved. Error: {1}').format( tabWidget.path, error.strerror) self.callWidgetMessageBarEditor(msgText, 2, False) def saveAsScriptFile(self, index=None): tabWidget = self.tabEditorWidget.currentWidget() if not index: index = self.tabEditorWidget.currentIndex() if not tabWidget.path: fileName = self.tabEditorWidget.tabText(index) + '.py' folder = self.settings.value("pythonConsole/lastDirPath", QDir.home()) pathFileName = os.path.join(folder, fileName) fileNone = True else: pathFileName = tabWidget.path fileNone = False saveAsFileTr = QCoreApplication.translate("PythonConsole", "Save File As") filename, filter = QFileDialog.getSaveFileName(self, saveAsFileTr, pathFileName, "Script file (*.py)") if filename: try: tabWidget.save(filename) except (IOError, OSError) as error: msgText = QCoreApplication.translate( 'PythonConsole', 'The file <b>{0}</b> could not be saved. Error: {1}' ).format(tabWidget.path, error.strerror) self.callWidgetMessageBarEditor(msgText, 2, False) if fileNone: tabWidget.path = None else: tabWidget.path = pathFileName return if not fileNone: self.updateTabListScript(pathFileName, action='remove') def openHelp(self): QgsHelp.openHelp("plugins/python_console.html") def openSettings(self): if optionsDialog(self).exec_(): self.shell.refreshSettingsShell() self.shellOut.refreshSettingsOutput() self.tabEditorWidget.refreshSettingsEditor() def callWidgetMessageBar(self, text): self.shellOut.widgetMessageBar(iface, text) def callWidgetMessageBarEditor(self, text, level, timed): self.tabEditorWidget.widgetMessageBar(iface, text, level, timed) def updateTabListScript(self, script, action=None): if action == 'remove': self.tabListScript.remove(script) elif action == 'append': if not self.tabListScript: self.tabListScript = [] if script not in self.tabListScript: self.tabListScript.append(script) else: self.tabListScript = [] self.settings.setValue("pythonConsole/tabScripts", self.tabListScript) def saveSettingsConsole(self): self.settings.setValue("pythonConsole/splitterConsole", self.splitter.saveState()) self.settings.setValue("pythonConsole/splitterObj", self.splitterObj.saveState()) self.settings.setValue("pythonConsole/splitterEditor", self.splitterEditor.saveState()) self.shell.writeHistoryFile(True) def restoreSettingsConsole(self): storedTabScripts = self.settings.value("pythonConsole/tabScripts", []) self.tabListScript = storedTabScripts self.splitter.restoreState( self.settings.value("pythonConsole/splitterConsole", QByteArray())) self.splitterEditor.restoreState( self.settings.value("pythonConsole/splitterEditor", QByteArray())) self.splitterObj.restoreState( self.settings.value("pythonConsole/splitterObj", QByteArray()))
class AttributesTable(QWidget): def __init__(self, iface): QWidget.__init__(self) self.setWindowTitle(self.tr('Search results')) self.resize(480,320) self.setMinimumSize(320,240) self.center() # Results export button self.btn_saveTab = QAction(QIcon(':/plugins/qgeric/resources/icon_save.png'), self.tr('Save this tab\'s results'), self) self.btn_saveTab.triggered.connect(lambda : self.saveAttributes(True)) self.btn_saveAllTabs = QAction(QIcon(':/plugins/qgeric/resources/icon_saveAll.png'), self.tr('Save all results'), self) self.btn_saveAllTabs.triggered.connect(lambda : self.saveAttributes(False)) self.btn_export = QAction(QIcon(':/plugins/qgeric/resources/icon_export.png'), self.tr('Export the selection as a memory layer'), self) self.btn_export.triggered.connect(self.exportLayer) self.btn_zoom = QAction(QIcon(':/plugins/qgeric/resources/icon_Zoom.png'), self.tr('Zoom to selected attributes'), self) self.btn_zoom.triggered.connect(self.zoomToFeature) self.btn_selectGeom = QAction(QIcon(':/plugins/qgeric/resources/icon_HlG.png'), self.tr('Highlight feature\'s geometry'), self) self.btn_selectGeom.triggered.connect(self.selectGeomChanged) self.btn_rename = QAction(QIcon(':/plugins/qgeric/resources/icon_Settings.png'), self.tr('Settings'), self) self.btn_rename.triggered.connect(self.renameWindow) self.tabWidget = QTabWidget() # Tab container self.tabWidget.setTabsClosable(True) self.tabWidget.currentChanged.connect(self.highlight_features) self.tabWidget.tabCloseRequested.connect(self.closeTab) self.loadingWindow = QProgressDialog() self.loadingWindow.setWindowTitle(self.tr('Loading...')) self.loadingWindow.setRange(0,100) self.loadingWindow.setAutoClose(False) self.loadingWindow.setCancelButton(None) self.canvas = iface.mapCanvas() self.canvas.extentsChanged.connect(self.highlight_features) self.highlight = [] self.highlight_rows = [] toolbar = QToolBar() toolbar.addAction(self.btn_saveTab) toolbar.addAction(self.btn_saveAllTabs) toolbar.addAction(self.btn_export) toolbar.addSeparator() toolbar.addAction(self.btn_zoom) toolbar.addSeparator() toolbar.addAction(self.btn_selectGeom) toolbar.addAction(self.btn_rename) vbox = QVBoxLayout() vbox.setContentsMargins(0,0,0,0) vbox.addWidget(toolbar) vbox.addWidget(self.tabWidget) self.setLayout(vbox) self.mb = iface.messageBar() self.selectGeom = False # False for point, True for geometry def renameWindow(self): title, ok = QInputDialog.getText(self, self.tr('Rename window'), self.tr('Enter a new title:')) if ok: self.setWindowTitle(title) def closeTab(self, index): self.tabWidget.widget(index).deleteLater() self.tabWidget.removeTab(index) def selectGeomChanged(self): if self.selectGeom: self.selectGeom = False self.btn_selectGeom.setText(self.tr('Highlight feature\'s geometry')) self.btn_selectGeom.setIcon(QIcon(':/plugins/qgeric/resources/icon_HlG.png')) else: self.selectGeom = True self.btn_selectGeom.setText(self.tr('Highlight feature\'s centroid')) self.btn_selectGeom.setIcon(QIcon(':/plugins/qgeric/resources/icon_HlC.png')) self.highlight_features() def exportLayer(self): if self.tabWidget.count() != 0: index = self.tabWidget.currentIndex() table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] items = table.selectedItems() if len(items) > 0: type = '' if items[0].feature.geometry().type() == QgsWkbTypes.PointGeometry: type = 'Point' elif items[0].feature.geometry().type() == QgsWkbTypes.LineGeometry: type = 'LineString' else: type = 'Polygon' features = [] for item in items: if item.feature not in features: features.append(item.feature) name = '' ok = True while not name.strip() and ok == True: name, ok = QInputDialog.getText(self, self.tr('Layer name'), self.tr('Give a name to the layer:')) if ok: layer = QgsVectorLayer(type+"?crs="+table.crs.authid(),name,"memory") layer.startEditing() layer.dataProvider().addAttributes(features[0].fields().toList()) layer.dataProvider().addFeatures(features) layer.commitChanges() QgsProject.instance().addMapLayer(layer) else: self.mb.pushWarning(self.tr('Warning'), self.tr('There is no selected feature !')) def highlight_features(self): for item in self.highlight: self.canvas.scene().removeItem(item) del self.highlight[:] del self.highlight_rows[:] index = self.tabWidget.currentIndex() tab = self.tabWidget.widget(index) if self.tabWidget.count() != 0: table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] nb = 0 area = 0 length = 0 items = table.selectedItems() for item in items: if item.row() not in self.highlight_rows: if self.selectGeom: highlight = QgsHighlight(self.canvas, item.feature.geometry(), self.tabWidget.widget(index).layer) else: highlight = QgsHighlight(self.canvas, item.feature.geometry().centroid(), self.tabWidget.widget(index).layer) highlight.setColor(QColor(255,0,0)) self.highlight.append(highlight) self.highlight_rows.append(item.row()) g = QgsGeometry(item.feature.geometry()) g.transform(QgsCoordinateTransform(tab.layer.crs(), QgsCoordinateReferenceSystem(2154), QgsProject.instance())) # geometry reprojection to get meters nb += 1 area += g.area() length += g.length() if tab.layer.geometryType()==QgsWkbTypes.PolygonGeometry: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+' '+self.tr('Area')+': '+"%.2f"%area+' m'+u'²') elif tab.layer.geometryType()==QgsWkbTypes.LineGeometry: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+' '+self.tr('Length')+': '+"%.2f"%length+' m') else: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)) def tr(self, message): return QCoreApplication.translate('Qgeric', message) def zoomToFeature(self): index = self.tabWidget.currentIndex() table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] items = table.selectedItems() feat_id = [] for item in items: feat_id.append(item.feature.id()) if len(feat_id) >= 1: if len(feat_id) == 1: self.canvas.setExtent(items[0].feature.geometry().buffer(5, 0).boundingBox()) # in case of a single point, it will still zoom to it else: self.canvas.zoomToFeatureIds(self.tabWidget.widget(self.tabWidget.currentIndex()).layer, feat_id) self.canvas.refresh() # Add a new tab def addLayer(self, layer, headers, types, features): tab = QWidget() tab.layer = layer p1_vertical = QVBoxLayout(tab) p1_vertical.setContentsMargins(0,0,0,0) table = QTableWidget() table.itemSelectionChanged.connect(self.highlight_features) table.title = layer.name() table.crs = layer.crs() table.setColumnCount(len(headers)) if len(features) > 0: table.setRowCount(len(features)) nbrow = len(features) self.loadingWindow.show() self.loadingWindow.setLabelText(table.title) self.loadingWindow.activateWindow() self.loadingWindow.showNormal() # Table population m = 0 for feature in features: n = 0 for cell in feature.attributes(): item = QTableWidgetItem() item.setData(Qt.DisplayRole, cell) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item.feature = feature table.setItem(m, n, item) n += 1 m += 1 self.loadingWindow.setValue(int((float(m)/nbrow)*100)) QApplication.processEvents() else: table.setRowCount(0) table.setHorizontalHeaderLabels(headers) table.horizontalHeader().setSectionsMovable(True) table.types = types table.filter_op = [] table.filters = [] for i in range(0, len(headers)): table.filters.append('') table.filter_op.append(0) header = table.horizontalHeader() header.setContextMenuPolicy(Qt.CustomContextMenu) header.customContextMenuRequested.connect(partial(self.filterMenu, table)) table.setSortingEnabled(True) p1_vertical.addWidget(table) # Status bar to display informations (ie: area) tab.sb = QStatusBar() p1_vertical.addWidget(tab.sb) title = table.title # We reduce the title's length to 20 characters if len(title)>20: title = title[:20]+'...' # We add the number of elements to the tab's title. title += ' ('+str(len(features))+')' self.tabWidget.addTab(tab, title) # Add the tab to the conatiner self.tabWidget.setTabToolTip(self.tabWidget.indexOf(tab), table.title) # Display a tooltip with the layer's full name def filterMenu(self, table, pos): index = table.columnAt(pos.x()) menu = QMenu() filter_operation = QComboBox() if table.types[index] in [10]: filter_operation.addItems([self.tr('Contains'),self.tr('Equals')]) else: filter_operation.addItems(['=','>','<']) filter_operation.setCurrentIndex(table.filter_op[index]) action_filter_operation = QWidgetAction(self) action_filter_operation.setDefaultWidget(filter_operation) if table.types[index] in [14]: if not isinstance(table.filters[index], QDate): filter_value = QDateEdit() else: filter_value = QDateEdit(table.filters[index]) elif table.types[index] in [15]: if not isinstance(table.filters[index], QTime): filter_value = QTimeEdit() else: filter_value = QTimeEdit(table.filters[index]) elif table.types[index] in [16]: if not isinstance(table.filters[index], QDateTime): filter_value = QDateTimeEdit() else: filter_value = QDateTimeEdit(table.filters[index]) else: filter_value = QLineEdit(table.filters[index]) action_filter_value = QWidgetAction(self) action_filter_value.setDefaultWidget(filter_value) menu.addAction(action_filter_operation) menu.addAction(action_filter_value) action_filter_apply = QAction(self.tr('Apply'), self) action_filter_apply.triggered.connect(partial(self.applyFilter, table, index, filter_value, filter_operation)) action_filter_cancel = QAction(self.tr('Cancel'), self) action_filter_cancel.triggered.connect(partial(self.applyFilter, table, index, None, filter_operation)) menu.addAction(action_filter_apply) menu.addAction(action_filter_cancel) menu.exec_(QtGui.QCursor.pos()) def applyFilter(self, table, index, filter_value, filter_operation): if filter_value == None: table.filters[index] = None else: if isinstance(filter_value, QDateEdit): table.filters[index] = filter_value.date() elif isinstance(filter_value, QTimeEdit): table.filters[index] = filter_value.time() elif isinstance(filter_value, QDateTimeEdit): table.filters[index] = filter_value.dateTime() else: table.filters[index] = filter_value.text() table.filter_op[index] = filter_operation.currentIndex() nb_elts = 0 for i in range(0, table.rowCount()): table.setRowHidden(i, False) nb_elts += 1 hidden_rows = [] for nb_col in range(0, table.columnCount()): filtered = False header = table.horizontalHeaderItem(nb_col).text() valid = False if table.filters[nb_col] is not None: if type(table.filters[nb_col]) in [QDate, QTime, QDateTime]: valid = True else: if table.filters[nb_col].strip(): valid = True if valid: filtered = True items = None if table.types[nb_col] in [10]:# If it's a string filter_type = None if table.filter_op[nb_col] == 0: # Contain filter_type = Qt.MatchContains if table.filter_op[nb_col] == 1: # Equal filter_type = Qt.MatchFixedString items = table.findItems(table.filters[nb_col], filter_type) elif table.types[nb_col] in [14, 15, 16]: # If it's a date/time items = [] for nb_row in range(0, table.rowCount()): item = table.item(nb_row, nb_col) if table.filter_op[nb_col] == 0: # = if item.data(QTableWidgetItem.Type) == table.filters[nb_col]: items.append(item) if table.filter_op[nb_col] == 1: # > if item.data(QTableWidgetItem.Type) > table.filters[nb_col]: items.append(item) if table.filter_op[nb_col] == 2: # < if item.data(QTableWidgetItem.Type) < table.filters[nb_col]: items.append(item) else: # If it's a number items = [] for nb_row in range(0, table.rowCount()): item = table.item(nb_row, nb_col) if item.text().strip(): if table.filter_op[nb_col] == 0: # = if float(item.text()) == float(table.filters[nb_col]): items.append(item) if table.filter_op[nb_col] == 1: # > if float(item.text()) > float(table.filters[nb_col]): items.append(item) if table.filter_op[nb_col] == 2: # < if float(item.text()) < float(table.filters[nb_col]): items.append(item) rows = [] for item in items: if item.column() == nb_col: rows.append(item.row()) for i in range(0, table.rowCount()): if i not in rows: if i not in hidden_rows: nb_elts -= 1 table.setRowHidden(i, True) hidden_rows.append(i) if filtered: if header[len(header)-1] != '*': table.setHorizontalHeaderItem(nb_col, QTableWidgetItem(header+'*')) else: if header[len(header)-1] == '*': header = header[:-1] table.setHorizontalHeaderItem(nb_col, QTableWidgetItem(header)) title = self.tabWidget.tabText(self.tabWidget.currentIndex()) for i in reversed(range(len(title))): if title[i] == ' ': break title = title[:-1] title += '('+str(nb_elts)+')' self.tabWidget.setTabText(self.tabWidget.currentIndex(), title) # Save tables in OpenDocument format # Use odswriter library def saveAttributes(self, active): file = QFileDialog.getSaveFileName(self, self.tr('Save in...'),'', self.tr('OpenDocument Spreadsheet (*.ods)')) if file[0]: try: with ods.writer(open(file[0],"wb")) as odsfile: tabs = None if active: tabs = self.tabWidget.currentWidget().findChildren(QTableWidget) else: tabs = self.tabWidget.findChildren(QTableWidget) for table in reversed(tabs): sheet = odsfile.new_sheet(table.title[:20]+'...') # For each tab in the container, a new sheet is created sheet.writerow([table.title]) # As the tab's title's lenght is limited, the full name of the layer is written in the first row nb_row = table.rowCount() nb_col = table.columnCount() # Fetching and writing of the table's header header = [] for i in range(0,nb_col): header.append(table.horizontalHeaderItem(i).text()) sheet.writerow(header) # Fetching and writing of the table's items for i in range(0,nb_row): row = [] for j in range(0,nb_col): row.append(table.item(i,j).text()) if not table.isRowHidden(i): sheet.writerow(row) return True except IOError: QMessageBox.critical(self, self.tr('Error'), self.tr('The file can\'t be written.')+'\n'+self.tr('Maybe you don\'t have the rights or are trying to overwrite an opened file.')) return False def center(self): screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def clear(self): self.tabWidget.clear() for table in self.tabWidget.findChildren(QTableWidget): table.setParent(None) def closeEvent(self, e): result = QMessageBox.question(self, self.tr("Saving ?"), self.tr("Would you like to save results before exit ?"), buttons = QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if result == QMessageBox.Yes: if self.saveAttributes(False): self.clear() e.accept() else: e.ignore() elif result == QMessageBox.No: self.clear() e.accept() else: e.ignore()
class QWeather: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(self.plugin_dir, 'i18n', 'QWeather_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&QWeather') # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar(u'QWeather') self.toolbar.setObjectName(u'QWeather') self.key1 = '9Q2gyTzBLVGVSTWdoJnM9Y29uc' self.toolButton = QToolButton() self.toolButton.setMenu(QMenu()) self.toolButton.setPopupMode(QToolButton.MenuButtonPopup) self.toolbar.addWidget(self.toolButton) # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('QWeather', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ # Create the dialog (after translation) and keep reference icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def _generate_signature(self, key, data): key_bytes = bytes(key, 'utf-8') data_bytes = bytes(data, 'utf-8') signature = hmac.new(key_bytes, data_bytes, hashlib.sha1).digest() return b64encode(signature).decode() def get_yahoo_weather( self, location, temp_type, app_id, consumer_key, consumer_secret, url='https://weather-ydn-yql.media.yahoo.com/forecastrss'): # Basic info method = 'GET' concat = '&' query = {'location': location, 'format': 'json', 'u': temp_type} oauth = { 'oauth_consumer_key': consumer_key, 'oauth_nonce': uuid.uuid4().hex, 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': str(int(time.time())), 'oauth_version': '1.0' } # Prepare signature string (merge all params and SORT them) merged_params = query.copy() merged_params.update(oauth) sorted_params = [ k + '=' + urllib.parse.quote(merged_params[k], safe='') for k in sorted(merged_params.keys()) ] signature_base_str = method + concat + urllib.parse.quote( url, safe='') + concat + urllib.parse.quote( concat.join(sorted_params), safe='') # Generate signature composite_key = urllib.parse.quote(consumer_secret, safe='') + concat oauth_signature = self._generate_signature(composite_key, signature_base_str) # Prepare Authorization header oauth['oauth_signature'] = oauth_signature auth_header = ( 'OAuth ' + ', '.join(['{}="{}"'.format(k, v) for k, v in oauth.items()])) # Send request url = url + '?' + urllib.parse.urlencode(query) request = urllib.request.Request(url) request.add_header('Authorization', auth_header) request.add_header('X-Yahoo-App-Id', app_id) response = urllib.request.urlopen(request).read() return json.loads(response) def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/QWeather/weather.png' self.mainButton = QAction(QIcon(icon_path), "Weather Info", self.iface.mainWindow()) self.mainButton.triggered.connect(self.run) icon_path = ':/plugins/QWeather/icons/reload.png' self.reloadButton = self.add_action( icon_path, text=self.tr(u'Reload QWeather layer'), callback=self.reloadWeather, parent=self.iface.mainWindow()) self.style_temperature_btn = QAction(QIcon(''), "Temperature", self.iface.mainWindow()) self.style_temperature_btn.setText("Temperature") self.style_temperature_btn.triggered.connect(self.style_temperature) self.style_direction_btn = QAction(QIcon(''), "Direction", self.iface.mainWindow()) self.style_direction_btn.setText("Direction") self.style_direction_btn.triggered.connect(self.style_direction) self.style_humidity_btn = QAction(QIcon(''), "Humidity", self.iface.mainWindow()) self.style_humidity_btn.setText("Humidity") self.style_humidity_btn.triggered.connect(self.style_humidity) menu = self.toolButton.menu() menu.addAction(self.mainButton) menu.addAction(self.style_temperature_btn) menu.addSeparator() menu.addAction(self.style_direction_btn) menu.addAction(self.style_humidity_btn) self.toolButton.setDefaultAction(self.mainButton) self.dlg = QWeatherDialog() #self.dlg.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint) self.key2 = 'VtZXJzZWNyZXQmc3Y9MCZ4PTZl' self.dlg.ok.clicked.connect(self.ok) self.dlg.closebutton.clicked.connect(self.close) self.dlg.toolButtonImport.clicked.connect(self.toolButtonImport) self.dlg.checkBox.clicked.connect(self.customBox) self.csvFile = None self.current = 'World Capitals' self.layerTemp = "QWeather" self.reload = False self.dlg.checkBox.setChecked(True) self.reloadButton.setEnabled(False) self.app_id = 'IyELbl3e' self.consumer_key = 'dj0yJmk' + self.key1 + '3' + self.key2 self.consumer_secret = 'e9962f3287764230d163045f737f9ad81428cdcc' def style_humidity(self): try: self.QWeather.styleManager().setCurrentStyle('humidity') except: pass def style_temperature(self): try: self.QWeather.styleManager().setCurrentStyle('temperature') except: pass def style_direction(self): try: self.QWeather.styleManager().setCurrentStyle('direction') except: pass def reloadWeather(self): self.reload = True if self.csvFile is None: self.csvFile = os.path.join(self.plugin_dir, 'World Capitals.csv') self.dlg.imp.setText(self.csvFile) self.ok() def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'&QWeather'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar def customBox(self): if self.dlg.checkBox.isChecked(): self.dlg.comboBox.setEnabled(False) self.dlg.toolButtonImport.setEnabled(True) self.dlg.imp.setEnabled(True) else: if 'Countries.csv' in self.dlg.imp.text(): self.csvFile = os.path.join(self.plugin_dir, 'World Capitals.csv') self.dlg.imp.setText(self.csvFile) self.dlg.comboBox.setEnabled(True) self.dlg.toolButtonImport.setEnabled(False) self.dlg.imp.setEnabled(False) def run(self): self.dlg.ok.setEnabled(True) self.dlg.closebutton.setEnabled(True) self.dlg.toolButtonImport.setEnabled(True) if self.csvFile is None: self.csvFile = os.path.join(self.plugin_dir, 'World Capitals.csv') self.dlg.imp.setText(self.csvFile) self.dlg.Celsius.setChecked(True) self.dlg.progressBar.setValue(0) self.customBox() self.dlg.comboBox.clear() db_list = [ 'World Capitals', 'Afghanistan', 'Aland', 'Albania', 'Algeria', 'American Samoa', 'Andorra', 'Angola', 'Antarctica', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Aruba', 'Australia', 'Austria', 'Azerbaijan', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bermuda', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Cayman Islands', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Brazzaville)', 'Congo (Kinshasa)', 'Cook Islands', 'Costa Rica', 'Croatia', 'Cuba', 'Curacao', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'East Timor', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Falkland Islands', 'Faroe Islands', 'Federated States of Micronesia', 'Fiji', 'Finland', 'France', 'French Polynesia', 'Gabon', 'Georgia', 'Germany', 'Ghana', 'Gibraltar', 'Greece', 'Greenland', 'Grenada', 'Guam', 'Guatemala', 'Guinea', 'Guinea Bissau', 'Guyana', 'Haiti', 'Honduras', 'Hong Kong S.A.R.', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Isle of Man', 'Israel', 'Italy', 'Ivory Coast', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kosovo', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Macau S.A.R', 'Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia', 'Nepal', 'Netherlands', 'New Caledonia', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'Northern Mariana Islands', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Puerto Rico', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'Somaliland', 'South Africa', 'South Georgia and the Islands', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Svalbard and Jan Mayen Islands', 'Swaziland', 'Sweden', 'Switzerland', 'Syria', 'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', 'The Bahamas', 'The Gambia', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Turks and Caicos Islands', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States Virgin Islands', 'USA', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Vatican (Holy Sea)', 'Venezuela', 'Vietnam', 'Western Sahara', 'Yemen', 'Zambia', 'Zimbabwe' ] self.dlg.comboBox.addItems(db_list) self.dlg.comboBox.setCurrentText(self.current) if self.dlg.isVisible(): self.dlg.close() self.dlg.show() else: self.dlg.show() def close(self): self.dlg.close() def toolButtonImport(self): self.csvFile = QFileDialog.getOpenFileName( None, "Choose the file", os.path.join(os.path.join(os.path.expanduser('~')), 'Desktop'), "(*.csv)") if self.csvFile[0] == "": self.csvFile = os.path.join(self.plugin_dir, 'World Capitals.csv') self.dlg.imp.setText(self.csvFile) return self.csvFile = self.csvFile[0] self.dlg.imp.setText(self.csvFile) def selectOutp(self): msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('Warning') msgBox.setText('Please define a csv file with locations.') msgBox.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint) msgBox.exec_() return True def call_import_temps_task(self): self.taskWeather = QgsTask.fromFunction(u'QWeather', self.import_temps_task, on_finished=self.completed, wait_time=4) QgsApplication.taskManager().addTask(self.taskWeather) def import_temps_task(self, task, wait_time): for i, location in enumerate(self.all_cities): try: time.sleep(.1) perc = (i / len(self.all_cities)) * 100 self.taskWeather.setProgress(perc) self.dlg.progressBar.setValue(perc) data = self.get_yahoo_weather( location.replace("'", ""), self.unit, self.app_id, self.consumer_key, self.consumer_secret, url='https://weather-ydn-yql.media.yahoo.com/forecastrss') if data['location'] == {}: continue fields = {} fieldnames = [ 'country', 'city', 'region', 'temperature', 'temperature_unit', 'date', 'direction', 'direction_unit', 'speed', 'speed_unit', 'humidity', 'humidity_unit', 'pressure', 'pressure_unit', 'visibility', 'visibility_unit', 'sunrise', 'sunset', 'icon', 'lat', 'lon' ] for field_init in fieldnames: fields[field_init] = [] try: fields['city'] = data['location']['city'] fields['temperature'] = str(data['current_observation'] ['condition']['temperature']) if not fields['temperature'].isnumeric(): continue fields['country'] = data['location']['country'].replace( 'ô', 'o').replace('´', '') fields['region'] = data['location']['region'].replace( '´', '') fields['direction'] = str( data['current_observation']['wind']['direction']) fields['direction_unit'] = self.unitDirection fields['speed'] = str( data['current_observation']['wind']['speed']) fields['speed_unit'] = self.unitSpeed fields['humidity'] = str( data['current_observation']['atmosphere']['humidity']) fields['humidity_unit'] = self.unitHumidity fields['pressure'] = str( data['current_observation']['atmosphere']['pressure']) fields['pressure_unit'] = self.unitPressure fields['visibility'] = str(data['current_observation'] ['atmosphere']['visibility']) fields['visibility_unit'] = self.unitVisibility fields['sunrise'] = str( data['current_observation']['astronomy']['sunrise']) fields['sunset'] = str( data['current_observation']['astronomy']['sunset']) Lon = data['location']['long'] Lat = data['location']['lat'] fields['lon'] = str(Lon) fields['lat'] = str(Lat) fields['date'] = time.ctime( int(str(data['current_observation']['pubDate']))) try: fields['icon'] = data['current_observation'][ 'condition']['text'] except: fields['icon'] = ' ' except: pass fields['temperature_unit'] = self.unit geo_info = { "type": "Feature", "properties": fields, "geometry": { "coordinates": [Lon, Lat], "type": "Point" } } self.geoinfo.append(geo_info) if self.taskWeather.isCanceled(): self.stopped(self.taskWeather) self.taskWeather.destroyed() return None except: pass return True def stopped(self, task): QgsMessageLog.logMessage( 'Task "{name}" was canceled'.format(name=task.description()), 'QWeather', Qgis.Info) def completed(self, exception, result=None): geojson = { "type": "FeatureCollection", "name": self.layerTemp, "crs": { "type": "name", "properties": { "name": "crs:OGC:1.3:CRS84" } }, "features": self.geoinfo } with open(self.outQWeatherGeoJson, 'w') as geofile: json.dump(geojson, geofile) if len(QgsProject.instance().mapLayersByName(self.layerTemp)) == 0: self.addQWeatherLayer() else: for x in self.iface.mapCanvas().layers(): if x.name() == self.layerTemp: self.QWeather = x QgsProject.instance().removeMapLayer(self.QWeather.id()) self.addQWeatherLayer() try: self.QWeather.selectAll() self.iface.mapCanvas().zoomToSelected() self.QWeather.removeSelection() except: pass ########################################### self.dlg.ok.setEnabled(True) self.dlg.closebutton.setEnabled(True) if not self.dlg.checkBox.isChecked(): self.dlg.toolButtonImport.setEnabled(False) else: self.dlg.toolButtonImport.setEnabled(True) self.reloadButton.setEnabled(True) self.dlg.progressBar.setValue(100) self.dlg.close() def addQWeatherLayer(self): # load qml to current style self.QWeather = self.iface.addVectorLayer(self.outQWeatherGeoJson, self.layerTemp, "ogr") style_manager = self.QWeather.styleManager() # read valid style from layer style = QgsMapLayerStyle() style.readFromLayer(self.QWeather) self.QWeather.loadNamedStyle( os.path.join(self.plugin_dir, "icons", self.style2 + ".qml")) style_manager.renameStyle("default", "direction") style_name = "humidity" # add style with new name style_manager.addStyle(style_name, style) # set new style as current style_manager.setCurrentStyle(style_name) self.QWeather.loadNamedStyle( os.path.join(self.plugin_dir, "icons", "humidity.qml")) style_manager.renameStyle(style_name, style_name) style = QgsMapLayerStyle() style.readFromLayer(self.QWeather) style_name = self.style # add style with new name style_manager.addStyle(style_name, style) # set new style as current style_manager.setCurrentStyle(style_name) self.QWeather.loadNamedStyle( os.path.join(self.plugin_dir, "icons", self.style + ".qml")) style_manager.renameStyle(self.style, "temperature") def ok(self): if not self.reload: if self.dlg.imp.text() == '': if self.selectOutp(): return elif not os.path.isabs(self.dlg.imp.text()): if self.selectOutp(): return else: self.reload = False self.csvFile = self.dlg.imp.text() status = False if not self.dlg.checkBox.isChecked(): self.current = self.dlg.comboBox.currentText() status = self.current.upper() == 'WORLD CAPITALS' if status: self.csvFile = os.path.join(self.plugin_dir, 'World Capitals.csv') if not self.dlg.checkBox.isChecked() and not status: self.csvFile = os.path.join(self.plugin_dir, 'Countries.csv') with open(self.csvFile, 'r') as f_all: self.all_cities = [] for line in f_all: mm = line.rstrip(', ').upper() if self.current.upper() in mm: self.all_cities.append(mm[:-1]) else: try: with open(self.csvFile, 'r') as f: self.all_cities = [line.rstrip('\n').upper() for line in f] except: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('QWeather') msgBox.setText('Please define a csv file path.') msgBox.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint) msgBox.exec_() return self.outQWeatherGeoJson = os.path.join(self.plugin_dir, 'QWeather.geojson') basename = os.path.basename(self.outQWeatherGeoJson) self.layerTemp = basename[:-8] try: f = open(self.outQWeatherGeoJson, "w") f.close() except: self.selectOutp() self.dlg.ok.setEnabled(False) self.dlg.closebutton.setEnabled(False) self.dlg.toolButtonImport.setEnabled(False) if not self.iface.actionSelectRectangle().isChecked(): self.iface.actionSelectRectangle().trigger() if not self.iface.actionMapTips().isChecked(): self.iface.actionMapTips().trigger() if len(self.all_cities) > 1000: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('Warning') msgBox.setText('Maximum locations must be below from 1000.') msgBox.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint) msgBox.exec_() ########################################### self.dlg.progressBar.setValue(100) self.dlg.progressBar.setValue(0) self.dlg.ok.setEnabled(True) self.dlg.closebutton.setEnabled(True) self.dlg.toolButtonImport.setEnabled(True) return # do stuff if self.dlg.Celsius.isChecked(): self.unit = 'C' self.unitDirection = "degrees" self.unitSpeed = "km/h" self.unitHumidity = "%" self.unitVisibility = "km" self.unitPressure = "hPa" self.style = 'weather_c' self.style2 = 'direction_c' else: self.unit = 'F' self.unitDirection = "degrees" self.unitSpeed = "mph" self.unitHumidity = "%" self.unitVisibility = "mi" self.unitPressure = "psi" self.style = 'weather_f' self.style2 = 'direction_f' self.geoinfo = [] self.call_import_temps_task()
class MainPlugin(object): def __init__(self, iface): self.name = "groupLayers" self.iface = iface self.project = QgsProject.instance() self.treeBeforeSave = None def initGui(self): self.action = QAction( QIcon(os.path.dirname(os.path.realpath(__file__)) + "/icon.png"), u"Group Layers by similar type (keep visibility)", self.iface.mainWindow() ) self.action.setObjectName("groupAction") self.action.setWhatsThis("Group/ungroup layers by type") self.action.setStatusTip("Group/ungroup layers by type") self.action.setCheckable(True) self.action.triggered.connect(self.run) self.resetAction = QAction("Group and make all layers visible") self.resetAction.triggered.connect(self.run_reset_visibility) # the icon pressed status could be used, but it is already # changed when run method is called, so this is ambiguous # therefore a dedicated boolean status is used self.grouped = False self.groupAdditionalTypes = False self.defSelection = groupHierarchies.keys().__iter__().__next__() self.hierarchyDefinition = groupHierarchies[self.defSelection] # add toolbar button and menu item layersDock = self.iface.mainWindow().findChild(QDockWidget, "Layers") self.layersToolBar = layersDock.widget().layout().itemAt(0).widget() assert isinstance(self.layersToolBar, QToolBar) self.layersToolBar.addAction(self.action) self.menuButton = [btn for btn in self.layersToolBar.children() if isinstance(btn, QToolButton) if self.action in btn.actions()][0] self.buttonMenu = QMenu() self.menuButton.setMenu(self.buttonMenu) self.menuButton.setPopupMode(QToolButton.MenuButtonPopup) self.buttonMenu.addAction(self.action) self.buttonMenu.addAction(self.resetAction) # self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&Group Layers", self.action) self.defSelector = QAction( u"Select hierarchy definitions", self.iface.mainWindow() ) self.defSelector.setObjectName("defSelector") self.defSelector.triggered.connect(self.selectDefs) self.iface.addPluginToMenu("&Group Layers", self.defSelector) # connect hook to reset the plugin state # when a new or existing project is opened self.project.cleared.connect(self.reset_state) self.project.writeProject.connect(self.write) self.project.projectSaved.connect(self.saved) def unload(self): # remove the plugin menu item and icon self.iface.removePluginMenu("&Group Layers", self.action) self.iface.removePluginMenu("&Group Layers", self.defSelector) self.layersToolBar.removeAction(self.action) self.project.cleared.disconnect(self.reset_state) self.project.writeProject.disconnect(self.write) self.project.projectSaved.disconnect(self.saved) try: self.project.layerWasAdded.disconnect(self.add_layer_sync) except Exception: print('could not disconnect add_layer_sync in unload') try: self.project.layerRemoved.disconnect(self.remove_layer_sync) except Exception: print('could not disconnect remove_layer_sync in unload') # self.iface.removeToolBarIcon(self.action) def selectDefs(self): dialog = DefSelectDialog(self.defSelection, self.groupAdditionalTypes) if dialog.exec_(): self.defSelection = dialog.comboBox.currentText() self.hierarchyDefinition = groupHierarchies[self.defSelection] self.groupAdditionalTypes = dialog.checkBox.isChecked() def run(self, checked=False, reset=False): try: if self.grouped: try: self.project.layerWasAdded.disconnect(self.add_layer_sync) except Exception: print('could not disconnect add_layer_sync') try: self.project.layerRemoved.disconnect(self.remove_layer_sync) except Exception: print('could not disconnect remove_layer_sync') self.groupToTree(reset_initial_visibility=reset) self.resetAction.setText("Group and make all layers visible") else: self.treeToGroup(all_visible=reset) self.resetAction.setText("Ungroup and restore initial (ungrouped) visibility") self.project.layerWasAdded.connect(self.add_layer_sync) self.project.layerRemoved.connect(self.remove_layer_sync) except Exception as e: raise(e) finally: # synchronize plugin state with button state in case of exceptions self.grouped = checked def run_reset_visibility(self): self.action.toggle() self.run(checked=self.action.isChecked(), reset=True) def reset_state(self): self.action.setChecked(False) self.grouped = False def write(self): if self.grouped: answer = QMessageBox.question(self.iface.mainWindow(), "Save ungrouped state", "The layers are currently grouped by the " "groupLayers plugin\n\n" "Would you like to save the initial (ungrouped) state?\n" "(save current (grouped) layer tree if answer = NO)", QMessageBox.Yes|QMessageBox.No) if answer == QMessageBox.Yes: self.treeBeforeSave = self.iface.layerTreeCanvasBridge().rootGroup().clone() self.groupToTree(reset_initial_visibility=True) self.iface.messageBar().pushMessage( 'CAUTION: The layer tree has been saved in its original format, ' 'check options if you want to change this behavior.', Qgis.Info ) def saved(self): if self.treeBeforeSave is not None: tempOldTree = self.oldTree self.oldTree = self.treeBeforeSave self.groupToTree(reset_initial_visibility=True) self.oldTree = tempOldTree self.treeBeforeSave = None def add_layer_sync(self, addedLayer): self.oldTree.addLayer(addedLayer) def remove_layer_sync(self, removedLayerId): removedLayer = self.oldTree.findLayer(removedLayerId) self.recursiveRemoveFromGroup(self.oldTree, removedLayer) def recursiveRemoveFromGroup(self, group, layer): group.removeChildNode(layer) for subGroup in group.findGroups(): self.recursiveRemoveFromGroup(subGroup, layer) def initTreeRec(self, hierarchyDefinition, tree): for (k, v) in hierarchyDefinition.items(): if "groupCriteria" in v: tree[k] = {} self.initTreeRec(v["values"], tree[k]) else: tree[k] = [] def treeToGroup(self, all_visible=True): self.layerDict = {} self.treeRoot = self.project.layerTreeRoot() self.initTreeRec(self.hierarchyDefinition['values'], self.layerDict) layerTree = self.iface.layerTreeCanvasBridge().rootGroup() self.oldTree = layerTree.clone() self.parseTreeRec(layerTree) # into self.layerDict self.layerDict = self.cleanTree(self.layerDict) oldLen = len(layerTree.children()) self.layerDictToTree(self.layerDict, layerTree, all_visible) # caution: commented instruction below removes all layers !! # iface.layerTreeCanvasBridge().rootGroup().clear() layerTree.removeChildren(0, oldLen) def groupToTree(self, reset_initial_visibility=True): self.treeRoot = self.project.layerTreeRoot() layerTree = self.iface.layerTreeCanvasBridge().rootGroup() oldLen = len(layerTree.children()) self.insertInto(self.oldTree, layerTree, reset_initial_visibility) layerTree.removeChildren(0, oldLen) def layerDictToTree(self, layerDict, destinationGroup, all_visible): if isinstance(layerDict, dict): for (layerType, layers) in layerDict.items(): grp = destinationGroup.addGroup(layerType) self.layerDictToTree(layers, grp, all_visible) elif isinstance(layerDict, list): for l in layerDict: isVisible = self.treeRoot.findLayer(l).isVisible() node = destinationGroup.addLayer(l) if not all_visible: node.setItemVisibilityChecked(isVisible) else: raise Exception("Tree dictionary has been initialized incorrectly.") def insertInto(self, origin, destination, reset_initial_visibility): for el in origin.children(): if QgsLayerTree.isLayer(el): node = destination.addLayer(el.layer()) node.setItemVisibilityChecked( self.treeRoot.findLayer(el.layer()).isVisible() ) elif QgsLayerTree.isGroup(el): node = destination.addGroup(el.name()) self.insertInto(el, node, reset_initial_visibility) if reset_initial_visibility: # overwrite visibility with previously saved visibility node.setItemVisibilityChecked(el.itemVisibilityChecked()) def parseTreeRec(self, treeLeaf): for el in treeLeaf.children(): if QgsLayerTree.isLayer(el): l = el.layer() self.sortInto(l, self.layerDict, self.hierarchyDefinition) elif QgsLayerTree.isGroup(el): self.parseTreeRec(el) def sortInto(self, layer, destination, definitions): if "groupCriteria" in definitions: groupValue = layer.__getattribute__(definitions["groupCriteria"])() itemFound = False for (label, criteria) in definitions["values"].items(): if groupValue == criteria["value"]: itemFound = True self.sortInto(layer, destination[label], criteria) if not itemFound: if self.groupAdditionalTypes: groupName = "others" else: groupName = str(groupValue) try: destination[groupName].append(layer) except KeyError: destination[groupName] = [layer] else: destination.append(layer) def cleanTree(self, sourceTree): # remove all branches without end leaves if isinstance(sourceTree, dict): groupContents = {} for (layerType, layers) in sourceTree.items(): groupLayers = self.cleanTree(layers) if groupLayers: groupContents[layerType] = groupLayers return groupContents elif isinstance(sourceTree, list): return sourceTree else: raise Exception("Tree dictionary has been initialized incorrectly.")
def iniAccions(self): act = QAction() act.setText('Quant a') act.triggered.connect(self._about) self.afegirAccio('about', act)
class LockZoomToTiles: def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.epsg3857 = QgsCoordinateReferenceSystem('EPSG:3857') self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.islocking = False def initGui(self): '''Initialize Lock Zoom to Tiles GUI.''' icon = QIcon() icon.addFile(os.path.dirname(__file__) + "/images/zoomUnlocked.png", state=QIcon.Off) icon.addFile(os.path.dirname(__file__) + "/images/zoomLocked.png", state=QIcon.On) self.action = QAction(icon, "Lock zoom scale", self.iface.mainWindow()) self.action.setObjectName('lockZoom') self.action.triggered.connect(self.lockIt) self.action.setCheckable(True) self.iface.addPluginToMenu("Lock zoom to tile scale", self.action) self.iface.addToolBarIcon(self.action) icon = QIcon(os.path.dirname(__file__) + '/images/help.png') self.helpAction = QAction(icon, "Help", self.iface.mainWindow()) self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lock zoom to tile scale', self.helpAction) self.checkCrs() self.canvas.destinationCrsChanged.connect(self.checkCrs) self.canvas.layersChanged.connect(self.checkCrs) def unload(self): '''Unload from the QGIS interface''' self.iface.removePluginMenu('Lock zoom to tile scale', self.action) self.iface.removeToolBarIcon(self.action) self.iface.removePluginMenu("Lock zoom to tile scale", self.helpAction) self.canvas.destinationCrsChanged.disconnect(self.checkCrs) if self.islocking == True: try: self.canvas.scaleChanged.disconnect(self.lockIt) except Exception: pass def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def lockIt(self): '''Set the focus of the copy coordinate tool''' if self.action.isChecked(): self.zoomTo() if self.islocking == False: self.islocking = True self.canvas.scaleChanged.connect(self.zoomTo) self.action.setText("Unlock zoom scale") self.action.setIconText("Unlock zoom scale") else: if self.islocking == True: self.canvas.scaleChanged.disconnect(self.zoomTo) self.islocking = False self.action.setText("Lock zoom scale") self.action.setIconText("Lock zoom scale") def zoomTo(self): crs = self.canvas.mapSettings().destinationCrs() mupp = self.canvas.mapUnitsPerPixel() if crs == self.epsg3857: r = 0 for i in range(0, len(r3857)): r = i if r3857[i] > mupp: if i > 0 and (r3857[i] - mupp > mupp - r3857[i - 1]): r = i - 1 break if not math.isclose(r3857[r], mupp, rel_tol=1e-5): self.canvas.zoomByFactor(r3857[r] / self.canvas.mapUnitsPerPixel()) else: r = 0 for i in range(0, len(r4326)): r = i if r4326[i] > mupp: if i > 0 and (r4326[i] - mupp > mupp - r4326[i - 1]): r = i - 1 break if not math.isclose(r4326[r], mupp, rel_tol=1e-5): self.canvas.zoomByFactor(r4326[r] / self.canvas.mapUnitsPerPixel()) def checkCrs(self): crs = self.canvas.mapSettings().destinationCrs() numlayers = self.canvas.layerCount() if (crs == self.epsg3857 or crs == self.epsg4326) and numlayers > 0: self.action.setEnabled(True) else: self.action.setEnabled(False) self.action.setChecked(False) self.lockIt()
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.snapping = True icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.defaultextent = None self.current_form = None self.last_form = None self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge( QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.snappingutils = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snappingutils) threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) smallmode = roam.config.settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.gpsMarker = GPSMarker(self.canvas) self.gpsMarker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(88, 64, 173, 50)) self.currentfeatureband.setOutlineColour(QColor(88, 64, 173)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.refresh_map.connect(self.refresh_map) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.gpslabelposition = QLabel("") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addWidget(self.gpslabelposition) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.update_status_label) self.canvas.scaleChanged.connect(self.update_status_label) self.connectButtons() scalebar_enabled = roam.config.settings.get('scale_bar', False) self.scalebar_enabled = False if scalebar_enabled: roam.utils.warning( "Unsupported feature: Scale bar support not ported to QGIS 3 API yet." ) RoamEvents.raisemessage( "Unsupported feature", "Scale bar support not ported to QGIS 3 API yet", level=RoamEvents.CRITICAL) self.scalebar_enabled = False # self.scalebar = ScaleBarItem(self.canvas) # self.canvas.scene().addItem(self.scalebar) def clear_plugins(self) -> None: """ Clear all the plugin added toolbars from the map interface. """ toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if toolbar.property("plugin_toolbar"): toolbar.unload() self.removeToolBar(toolbar) toolbar.deleteLater() def add_plugins(self, pluginnames) -> None: """ Add the given plugins to to the mapping interface. Adds the toolbars the plugin exposes as new toolbars for the user. :param pluginnames: The names of the plugins to load. Must already be loaded by the plugin loader """ for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'toolbars'): roam.utils.warning( "No toolbars() function found in {}".format(name)) continue toolbars = plugin_mod.toolbars() self.load_plugin_toolbars(toolbars) def load_plugin_toolbars(self, toolbars): """ Load the plugin toolbars into the mapping interface. :param toolbars: The list of toolbars class objects to load. :return: """ for ToolBarClass in toolbars: toolbar = ToolBarClass(plugins.api, self) self.addToolBar(Qt.BottomToolBarArea, toolbar) toolbar.setProperty("plugin_toolbar", True) def snapping_changed(self, snapping): """ Called when the snapping settings have changed. Updates the label in the status bar. :param snapping: """ self.snapping = snapping if snapping: self.snappingbutton.setText("Snapping: On") else: self.snappingbutton.setText("Snapping: Off") def toggle_snapping(self): """ Toggle snapping on or off. """ self.snapping = not self.snapping try: self.canvas.mapTool().toggle_snapping() except AttributeError: pass RoamEvents.snappingChanged.emit(self.snapping) def selectscale(self): """ Show the select scale widget. :return: """ self.scalelist.show() def update_scale_from_item(self, index): """ Update the canvas scale from the selected scale item. :param index: The index of the selected item. """ scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def update_gps_fixed_label(self, fixed, gpsinfo): if not fixed: self.gpslabel.setText("GPS: Acquiring fix") self.gpslabelposition.setText("") quality_mappings = { 0: "invalid", 1: "GPS", 2: "DGPS", 3: "PPS", 4: "Real Time Kinematic", 5: "Float RTK", 6: "Estimated", 7: "Manual input mode", 8: "Simulation mode" } def update_gps_label(self, position, gpsinfo): """ Update the GPS label in the status bar with the GPS status. :param position: The current GPS position. :param gpsinfo: The current extra GPS information. """ if not self.gps.connected: return fixtype = self.quality_mappings.get(gpsinfo.quality, "") self.gpslabel.setText( "DOP P:<b>{0:.2f}</b> H:<b>{1:.2f}</b> V:<b>{2:.2f}</b> " "Fix: <b>{3}</b> " "Sats: <b>{4}</b> ".format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop, fixtype, gpsinfo.satellitesUsed)) places = roam.config.settings.get("gpsplaces", 8) self.gpslabelposition.setText("X: <b>{x:.{places}f}</b> " "Y: <b>{y:.{places}f}</b> " "Z: <b>{z}m</b> ".format( x=position.x(), y=position.y(), z=gpsinfo.elevation, places=places)) def gps_disconnected(self): self.gpslabel.setText("GPS: Not Active") self.gpslabelposition.setText("") self.gpsMarker.hide() def zoom_to_feature(self, feature): """ Zoom to the given feature in the map. :param feature: :return: """ box = feature.geometry().boundingBox() xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum( ), box.yMaximum() xmin -= 5 xmax += 5 ymin -= 5 ymax += 5 box = QgsRectangle(xmin, ymin, xmax, ymax) self.canvas.setExtent(box) self.canvas.refresh() def update_status_label(self, *args) -> None: """ Update the status bar labels when the information has changed. """ extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format( extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def refresh_map(self) -> None: """ Refresh the map """ self.canvas.refresh() def updatescale(self) -> None: """ Update the scale of the map with the current scale from the scale widget :return: """ self.canvas.zoomScale(1.0 / self.scalewidget.scale()) @property def crs(self) -> QgsCoordinateReferenceSystem: """ Get the CRS used that is being used in the canvas :return: The QgsCoordinateReferenceSystem that is used by the canvas """ return self.canvas.mapSettings().destinationCrs() def feature_form_loaded(self, form, feature, *args): """ Called when the feature form is loaded. :param form: The Form object. Holds a reference to the forms layer. :param feature: The current capture feature """ self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): """ Highlight the selection on the canvas. This updates all selected objects based on the result set. :param results: A dict-of-list of layer-features. """ self.clear_selection() for layer, features in results.items(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0)) band.setIconSize(25) band.setWidth(5) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) band.setZValue(self.currentfeatureband.zValue() - 1) for feature in features: band.addGeometry(feature.geometry(), layer) self.canvas.update() def highlight_active_selection(self, layer, feature, features): """ Update the current active selected feature. :param layer: The layer of the active feature. :param feature: The active feature. :param features: The other features in the set to show as non active selection. :return: """ self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) self.canvas.update() def clear_selection(self): """ Clear the selection from the canvas. Resets all selection rubber bands. :return: """ # Clear the main selection rubber band self.canvas.scene().update() self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.values(): band.reset() self.canvas.update() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): """ Push a feature on the edit stack so the feature can have the geometry edited. :note: This is a big hack and I don't like it! :param form: The form for the current feature :param feature: The active feature. """ def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() self.canvas.currentLayer().startEditing() self.canvas.mapTool().setEditMode(True, feature.geometry(), feature) break self.editfeaturestack.append((form, feature)) self.save_current_form() self.load_form(form) trigger_default_action() def save_current_form(self): self.last_form = self.current_form def restore_last_form(self): self.load_form(self.last_form) def clear_temp_objects(self): """ Clear all temp objects from the canvas. :return: """ def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() if hasattr(tool, "clearBand"): tool.clearBand() self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): """ Called when the settings have been updated in the Roam config. :param settings: A dict of the settings. """ self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging smallmode = settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) def set_gps(self, gps, logging): """ Set the GPS for the map widget. Connects GPS signals """ self.gps = gps self.gpslogging = logging self.gps.gpsfixed.connect(self.update_gps_fixed_label) self.gps.gpsposition.connect(self.update_gps_label) self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) self.gpsMarker.setgps(self.gps) self.actionGPS.setgps(gps) def gps_update_canvas(self, position, gpsinfo): """ Updates the map canvas based on the GPS position. By default if the GPS is outside the canvas extent the canvas will move to center on the GPS. Can be turned off in settings. :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastgpsposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.gpsMarker.show() self.gpsMarker.setCenter(position, gpsinfo) def gps_first_fix(self, postion, gpsinfo): """ Called the first time the GPS gets a fix. If set this will zoom to the GPS after the first fix :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): """ Zoom to ta given position on the map.. """ rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def select_data_entry(self): """ Open the form selection widget to allow the user to pick the active capture form. """ def showformerror(form): pass def actions(): for form in self.project.forms: if not self.form_valid_for_capture(form): continue action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form", wrap=5) formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): """ Called when the project is loaded. Main entry point for a loade project. :param project: The Roam project that has been loaded. """ self.snappingutils.setConfig(QgsProject.instance().snappingConfig()) self.project = project self.actionPan.trigger() firstform = self.first_capture_form() if firstform: self.load_form(firstform) self.dataentryselection.setVisible(True) else: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = roam.api.utils.layers() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.refresh() projectscales, _ = QgsProject.instance().readBoolEntry( "Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry( "Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = [ "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100" ] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) if self.scalebar_enabled: self.scalebar.update() red = QgsProject.instance().readNumEntry("Gui", "/CanvasColorRedPart", 255)[0] green = QgsProject.instance().readNumEntry("Gui", "/CanvasColorGreenPart", 255)[0] blue = QgsProject.instance().readNumEntry("Gui", "/CanvasColorBluePart", 255)[0] myColor = QColor(red, green, blue) self.canvas.setCanvasColor(myColor) self.actionPan.toggle() self.clear_plugins() self.add_plugins(project.enabled_plugins) def setMapTool(self, tool, *args): """ Set the active map tool in the canvas. :param tool: The QgsMapTool to set. """ if tool == self.canvas.mapTool(): return if hasattr(tool, "setSnapping"): tool.setSnapping(self.snapping) self.canvas.setMapTool(tool) def connectButtons(self): """ Connect the default buttons in the interface. Zoom, pan, etc """ def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = QgsMapToolPan(self.canvas) self.infoTool = InfoTool(self.canvas) self.infoTool.setAction(self.actionInfo) self.zoomInTool.setAction(self.actionZoom_In) self.zoomOutTool.setAction(self.actionZoom_Out) self.panTool.setAction(self.actionPan) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/select')) self.actionRaster.triggered.connect(self.toggle_raster_layers) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ if self.defaultextent: self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def form_valid_for_capture(self, form): """ Check if the given form is valid for capture. :param form: The form to check. :return: True if valid form for capture """ return form.has_geometry and self.project.layer_can_capture( form.QGISLayer) def first_capture_form(self): """ Return the first valid form for capture. """ for form in self.project.forms: if self.form_valid_for_capture(form): return form def load_form(self, form): """ Load the given form so it's the active one for capture :param form: The form to load """ self.clear_capture_tools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) self.current_form = form def create_capture_buttons(self, form): """ Create the capture buttons in the toolbar for the given form. :param form: The active form. """ tool = form.getMaptool()(self.canvas, form.settings) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(self.show_invalid_geometry_message) def show_invalid_geometry_message(self, message) -> None: """ Shows the message to the user if the there is a invalid geometry capture. :param message: The message to show the user. """ RoamEvents.raisemessage("Invalid geometry capture", message, level=RoamEvents.CRITICAL) if self.canvas.currentLayer() is not None: self.canvas.currentLayer().rollBack() RoamEvents.editgeometry_invalid.emit() def add_new_feature(self, form, geometry: QgsGeometry): """ Add a new new feature to the given layer :param form: The form to use for the new feature. :param geometry: The new geometry to create the feature for. """ # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if geometry.isMultipart(): geometry.convertToMultiType() # Transform the new geometry back into the map layers geometry if it's needed transform = self.canvas.mapSettings().layerTransform(layer) if transform.isValid(): geometry.transform(transform, QgsCoordinateTransform.ReverseTransform) try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(geometry=geometry) RoamEvents.load_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) self.canvas.mapTool().setEditMode(False, None, None) self.restore_last_form() def clear_capture_tools(self): """ Clear the capture tools from the toolbar. :return: True if the capture button was active at the time of clearing. """ captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggle_raster_layers(self) -> None: """ Toggle all raster layers on or off. """ # Freeze the canvas to save on UI refresh dlg = PickActionDialog(msg="Raster visibility") actions = [ (":/icons/raster_0", "Off", partial(self._set_basemaps_opacity, 0), "photo_off"), (":/icons/raster_25", "25%", partial(self._set_basemaps_opacity, .25), "photo_25"), (":/icons/raster_50", "50%", partial(self._set_basemaps_opacity, .50), "photo_50"), (":/icons/raster_75", "75%", partial(self._set_basemaps_opacity, .75), "photo_75"), (":/icons/raster_100", "100%", partial(self._set_basemaps_opacity, 1), "photo_100"), ] # ":/icons/raster_100"), "100%", self, triggered=partial(self._set_raster_layer_value, 1), # objectName="photo_100") dialog_actions = [] for action in actions: icon = QIcon(action[0]) qaction = QAction(icon, action[1], self, triggered=action[2], objectName=action[3]) dialog_actions.append(qaction) dlg.addactions(dialog_actions) dlg.exec_() def _set_basemaps_opacity(self, value=0) -> None: """ Set the opacity for all basemap raster layers. :param value: The opacity value betwen 0 and 1 """ tree = QgsProject.instance().layerTreeRoot() for node in tree.findLayers(): layer = node.layer() if node.layer().type() == QgsMapLayer.RasterLayer: if value > 0: node.setItemVisibilityChecked(Qt.Checked) renderer = layer.renderer() renderer.setOpacity(value) if value == 0: node.setItemVisibilityChecked(Qt.Unchecked) self.canvas.refresh() def cleanup(self): """ Clean up when the project has changed. :return: """ # TODO Review cleanup # self.bridge.clear() self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clear_capture_tools() for action in self.layerbuttons: self.editgroup.removeAction(action)
class LessonsCreator(object): def __init__(self, iface): self.iface = iface # add tests to tester plugin try: from qgistester.tests import addTestModule from lessonscreator.test import testerplugin addTestModule(testerplugin, "LessonsCreator") except Exception as e: pass self.capturing = False def unload(self): self.iface.removePluginMenu(u"Lessons", self.action) del self.action del self.newStepAction QgsApplication.instance().focusChanged.disconnect( self.processFocusChanged) try: from qgistester.tests import removeTestModule from lessonscreator.test import testerplugin removeTestModule(testerplugin, "LessonsCreator") except Exception as e: pass def initGui(self): lessonIcon = QIcon(os.path.dirname(__file__) + '/edit.png') self.action = QAction(lessonIcon, "Capture lesson steps", self.iface.mainWindow()) self.action.triggered.connect(self.toggleCapture) self.iface.addPluginToMenu(u"Lessons", self.action) QgsApplication.instance().focusChanged.connect( self.processFocusChanged) self.newStepAction = QAction("New step", self.iface.mainWindow()) self.newStepAction.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_W)) self.newStepAction.setShortcutContext(Qt.ApplicationShortcut) self.newStepAction.triggered.connect(self.startNewStep) self.iface.mainWindow().addAction(self.newStepAction) connections = [] iScreenshot = 0 iStep = 0 outputHtmlFile = None outputPyFile = None def startNewStep(self): if self.outputHtmlFile: self.outputHtmlFile.close() self.iStep += 1 path = os.path.join(self.folder, "step_%i.html" % self.iStep) self.outputHtmlFile = open(path, "w") self.outputPyFile.write( '''lesson.addStep("Step_%i", "step_%i.html", steptype=Step.MANUALSTEP)\n''' % (self.iStep, self.iStep)) def toggleCapture(self): if self.capturing: self.action.setText("Capture lesson steps") self.capturing = False self.outputHtmlFile.close() self.outputPyFile.close() self.outputHtmlFile = None self.outputPyFile = None else: self.folder = QFileDialog.getExistingDirectory( self.iface.mainWindow(), "Select folder to store lesson") if not self.folder: return path = os.path.join(self.folder, "__init__.py") self.outputPyFile = open(path, "w") template = ( "from lessons.lesson import Lesson, Step\n" "from lessons.utils import *\n" "lesson = Lesson('Lesson', 'Basic lessons', 'lesson.html')\n\n" ) self.outputPyFile.write(template) self.iScreenshot = 0 self.iStep = 0 self.updateConnections() self.action.setText("Stop capturing lesson steps") self.capturing = True self.startNewStep() def processWidgetClick(self, obj): if self.capturing: try: text = "Click on '%s'" % obj.text() except Exception as e: # fix_print_with_import print(e) text = "Click on " + str(obj) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.updateConnections() lastComboText = None def processComboNewSelection(self, combo): if self.capturing: text = "Select '%s' in '%s'" % (combo.currentText(), combo.objectName()) if text == self.lastComboText: return self.lastComboText = text self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(combo.parent(), combo.frameGeometry()) self.updateConnections() def processCheckBoxChange(self, check): if self.capturing: if check.isChecked(): text = "Check the '%s' checkbox" % (check.text()) else: text = "Uncheck the '%s' checkbox" % (check.text()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(check.parent(), check.frameGeometry()) def processMenuClick(self, action): if self.capturing and action.text() != "Stop capturing lesson steps": text = "Click on menu '%s'" % action.text() self.outputHtmlFile.write("<p>%s</p>\n" % text) def getParentWindow(self, obj): window = None try: parent = obj.parent() while parent is not None: if isinstance(parent, QDialog): window = parent break parent = parent.parent() except: window = None return window or QgsApplication.instance().desktop() def processFocusChanged(self, old, new): if self.capturing: self.updateConnections() if isinstance(old, QLineEdit) and old.text().strip(): text = "Enter '%s' in textbox '%s'" % (old.text(), old.objectName()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(old.parent(), old.frameGeometry()) else: oldParent = self.getParentWindow(old) newParent = self.getParentWindow(new) # fix_print_with_import print(oldParent, newParent) if oldParent != newParent: self.createScreenshot(newParent) elif isinstance( new, (QLineEdit, QTextEdit, QComboBox, QSpinBox, QRadioButton)): text = "Select the '%s' widget" % (old.objectName() or str(old)) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(new.parent(), new.frameGeometry()) timer = None def _createScreenshot(self, obj, rect): pixmap = QPixmap.grabWindow(obj.winId()).copy() if rect is not None: painter = QPainter() painter.begin(pixmap) painter.setPen(QPen(QBrush(Qt.red), 3, Qt.DashLine)) painter.drawRect(rect) painter.end() pixmap.save(os.path.join(self.folder, '%i.jpg' % self.iScreenshot), 'jpg') self.outputHtmlFile.write("<img src='%i.jpg'/>\n" % self.iScreenshot) self.iScreenshot += 1 self.timer = None def createScreenshot(self, obj, rect=None): if self.capturing and self.timer is None: self.timer = QTimer() self.timer.setInterval(1000) self.timer.setSingleShot(True) self.timer.timeout.connect( lambda: self._createScreenshot(obj, rect)) self.timer.start() def updateConnections(self): widgets = QgsApplication.instance().allWidgets() for w in widgets: if w not in self.connections: if isinstance(w, (QPushButton, QToolButton)): f = partial(self.processWidgetClick, w) w.clicked.connect(f) self.connections.append(w) elif isinstance(w, QComboBox): f = partial(self.processComboNewSelection, w) w.currentIndexChanged.connect(f) self.connections.append(w) elif isinstance(w, QCheckBox): f = partial(self.processCheckBoxChange, w) w.stateChanged.connect(f) self.connections.append(w) menuActions = [] actions = self.iface.mainWindow().menuBar().actions() for action in actions: menuActions.extend(self.getActions(action, None)) for action, menu in menuActions: if menu not in self.connections: menu.triggered.connect(self.processMenuClick) self.connections.append(menu) def getActions(self, action, menu): menuActions = [] submenu = action.menu() if submenu is None: menuActions.append((action, menu)) return menuActions else: actions = submenu.actions() for subaction in actions: if subaction.menu() is not None: menuActions.extend( self.getActions(subaction, subaction.menu())) elif not subaction.isSeparator(): menuActions.append((subaction, submenu)) return menuActions
class aeag_mask(QObject): def __init__(self, iface): QObject.__init__(self) # Save reference to the QGIS interface global aeag_mask_instance aeag_mask_instance = self self.iface = iface try: # install translator locale = QSettings().value("locale/userLocale")[0:2] localePath = QFileInfo(os.path.realpath( __file__)).path() + "/i18n/{}.qm".format(locale) if QFileInfo(localePath).exists(): self.translator = QTranslator() self.translator.load(localePath) QCoreApplication.installTranslator(self.translator) except Exception: # no translation pass try: self.toolBar = None self.act_aeag_mask = None self.act_aeag_toolbar_help = None self.canvas = self.iface.mapCanvas() self.old_active_layer = None # test qgis version for the presence of the simplifier self.has_simplifier = is_in_qgis_core("QgsMapToPixelSimplifier") # test qgis version for the presence of pointOnSurface self.has_point_on_surface = "pointOnSurface" in dir(QgsGeometry) self.MASK_NAME = "Mask" except Exception as e: for m in e.args: QgsMessageLog.logMessage(m, "Extensions") try: self.reset_mask_layer(False) except Exception as e: for m in e.args: QgsMessageLog.logMessage(m, "Extensions") def reset_mask_layer(self, toSave=True): self.layer = None # mask parameters self.parameters = MaskParameters() if toSave: self.save_to_project(self.layer, self.parameters) for name, layer in QgsProject.instance().mapLayers().items(): if mask_filter.has_mask_filter(layer): # remove mask filter from layer, if any mask_filter.remove_mask_filter(layer) self.simplified_geometries = {} def initGui(self): self.mask_geometry_function = MaskGeometryFunction(self) QgsExpression.registerFunction(self.mask_geometry_function) self.in_mask_function = InMaskFunction(self) QgsExpression.registerFunction(self.in_mask_function) # self.disable_remove_mask_signal = False self.disable_add_layer_signal = False self.project = QgsProject.instance() self.project.layerWillBeRemoved.connect(self.on_remove_mask) self.act_aeag_mask = QAction( QIcon(":plugins/mask/aeag_mask.png"), self.tr("Create a mask"), self.iface.mainWindow(), ) self.toolBar = self.iface.pluginToolBar() self.toolBar.addAction(self.act_aeag_mask) self.iface.addPluginToMenu("&Mask", self.act_aeag_mask) # turn it to true to enable test if False: self.act_test = QAction(QIcon(":plugins/mask/aeag_mask.png"), "Test", self.iface.mainWindow()) self.toolBar.addAction(self.act_test) self.iface.addPluginToMenu("&Mask", self.act_test) self.act_test.triggered.connect(self.do_test) # Add documentation links to the menu self.act_aeag_about = QAction(self.tr("About"), self.iface.mainWindow()) self.act_aeag_about.triggered.connect(self.on_about) self.act_aeag_doc = QAction(self.tr("Documentation"), self.iface.mainWindow()) self.act_aeag_doc.triggered.connect(self.on_doc) self.iface.addPluginToMenu("&Mask", self.act_aeag_about) self.iface.addPluginToMenu("&Mask", self.act_aeag_doc) # Add actions to the toolbar self.act_aeag_mask.triggered.connect(self.run) # look for an existing mask layer mask_id, ok = QgsProject.instance().readEntry("Mask", "layer_id") self.layer = self.project.mapLayer(mask_id) # register layout signals lm = QgsProject.instance().layoutManager() lm.layoutAdded.connect(self.on_layout_added) lm.layoutAboutToBeRemoved.connect(self.on_layout_removed) # register already existing layouts for layout in lm.printLayouts(): self.on_layout_added(layout.name()) # register to the change of active layer for enabling/disabling # of the action self.old_active_layer = None self.iface.mapCanvas().currentLayerChanged.connect( self.on_current_layer_changed) self.on_current_layer_changed(None) # register to project reading # connect to QgisApp::projectRead to make sure MemoryLayerSaver has # been called before (it connects to QgsProject::readProject) self.iface.mainWindow().projectRead.connect(self.on_project_open) def load_from_project(self): parameters = MaskParameters() try: # return layer, parameters ok = parameters.load_from_project() if not ok: # no parameters in the project # look for a vector layer called 'Mask' for _id, l in list(QgsProject.instance().mapLayers().items()): if l.type() == QgsMapLayer.VectorLayer and l.name( ) == "Mask": return self.load_from_layer(l) layer_id, ok = QgsProject.instance().readEntry("Mask", "layer_id") layer = QgsProject.instance().mapLayer(layer_id) return layer, parameters except Exception as e: for m in e.args: QgsMessageLog.logMessage( "Mask error when loading - {}".format(m), "Extensions") return None, parameters def save_to_project(self, layer, parameters): try: QgsProject.instance().writeEntry("Mask", "layer_id", layer.id() if layer else "") parameters.save_to_project() except Exception as e: for m in e.args: QgsMessageLog.logMessage( "Mask error when saving - {}".format(m), "Extensions") def on_project_open(self): self.layer, self.parameters = self.load_from_project() if self.layer is not None: self.layer = self.apply_mask_parameters( self.layer, self.parameters, dest_crs=None, poly=None, name=self.layer.name(), keep_layer=True, ) self.act_aeag_mask.setEnabled(True) def on_current_layer_changed(self, layer): if self.layer is None: _, poly = self.get_selected_polygons() self.act_aeag_mask.setEnabled(poly != []) else: self.act_aeag_mask.setEnabled(True) if layer and layer.type() != QgsMapLayer.VectorLayer: self.old_active_layer = None return try: if self.old_active_layer is not None: self.old_active_layer.selectionChanged.disconnect( self.on_current_layer_selection_changed) except Exception as e: for m in e.args: QgsMessageLog.logMessage( "on_current_layer_changed - {}".format(m), "Extensions") if layer is not None: layer.selectionChanged.connect( self.on_current_layer_selection_changed) if layer != self.old_active_layer: self.old_active_layer = layer def on_current_layer_selection_changed(self): if self.layer is None: _, poly = self.get_selected_polygons() self.act_aeag_mask.setEnabled(poly != []) def unload(self): self.toolBar.removeAction(self.act_aeag_mask) self.iface.removePluginMenu("&Mask", self.act_aeag_mask) # remove doc links self.iface.removePluginMenu("&Mask", self.act_aeag_about) self.iface.removePluginMenu("&Mask", self.act_aeag_doc) QgsExpression.unregisterFunction("$mask_geometry") QgsExpression.unregisterFunction("in_mask") self.project.layerWillBeRemoved.disconnect(self.on_remove_mask) lm = QgsProject.instance().layoutManager() lm.layoutAdded.disconnect(self.on_layout_added) lm.layoutAboutToBeRemoved.disconnect(self.on_layout_removed) # remove layout signals for layout in lm.printLayouts(): self.on_layout_removed(layout.name()) self.iface.mapCanvas().currentLayerChanged.disconnect( self.on_current_layer_changed) self.iface.mainWindow().projectRead.disconnect(self.on_project_open) def on_about(self): dlg = HtmlDialog(None, "about.html") dlg.exec_() def on_doc(self): QDesktopServices.openUrl(QUrl("https://github.com/aeag/mask/wiki")) # force loading of parameters from a layer # for backward compatibility with older versions def load_from_layer(self, layer): # return layer, parameters parameters = MaskParameters() ok = parameters.load_from_layer(layer) if not ok: return layer, parameters QgsProject.instance().writeEntry("Mask", "layer_id", layer.id()) layer = self.apply_mask_parameters( layer, parameters, dest_crs=None, poly=None, name=layer.name(), keep_layer=False, ) return layer, parameters def connect_layout_events(self, layout): try: layout.atlas().renderBegun.connect(self.on_atlas_begin_render) layout.atlas().renderEnded.connect(self.on_atlas_end_render) for item in layout.items(): if isinstance(item, QgsLayoutItemMap): item.preparedForAtlas.connect( partial(self.on_prepared_for_atlas, layout)) except Exception: pass def disconnect_layout_events(self, layout): try: layout.atlas().renderBegun.disconnect(self.on_atlas_begin_render) layout.atlas().renderEnded.disconnect(self.on_atlas_end_render) for item in layout.items(): if isinstance(item, QgsLayoutItemMap): item.preparedForAtlas.disconnect() except Exception: pass def refreshEvents(self, layout): self.disconnect_layout_events(layout) self.connect_layout_events(layout) def on_layout_added(self, layoutName): layout = QgsProject.instance().layoutManager().layoutByName(layoutName) # It is impossible to detect the addition of a 'map' item to the layout (first time case). # The user is forced to close the composer then, open it again so that the mask works with the atlas. # layout.refreshed.connect(self.on_layout_refreshed) self.iface.layoutDesignerClosed.connect( partial(self.refreshEvents, layout)) self.connect_layout_events(layout) def on_layout_removed(self, layoutName): layout = QgsProject.instance().layoutManager().layoutByName(layoutName) self.disconnect_layout_events(layout) def compute_mask_geometries(self, parameters, poly): geom = None for g in poly: if geom is None: geom = QgsGeometry(g) else: # do an union here geom = geom.combine(g) if parameters.do_buffer: geom = geom.buffer(parameters.buffer_units, parameters.buffer_segments) # reset the simplified geometries dict self.simplified_geometries = {} return geom def on_prepared_for_atlas(self, layout): # called for each atlas feature if not self.layer: return geom = QgsExpressionContextUtils.atlasScope( layout.atlas()).variable("atlas_geometry") if not geom: return masked_atlas_geometry = [geom] # no need to zoom, it has already been scaled by atlas self.layer = self.apply_mask_parameters( self.layer, self.parameters, dest_crs=self.layer.crs(), poly=masked_atlas_geometry, name=self.layer.name(), cleanup_and_zoom=False, ) # update maps QCoreApplication.processEvents() def on_atlas_begin_render(self): if not self.layer: return # save the mask geometry self.geometries_backup = [ QgsGeometry(g) for g in self.parameters.orig_geometry ] def on_atlas_end_render(self): if not self.layer: return # restore the mask geometry self.parameters.orig_geometry = self.geometries_backup # no need to zoom, it has already been scaled by atlas self.layer = self.apply_mask_parameters( self.layer, self.parameters, dest_crs=None, poly=None, name=self.layer.name(), cleanup_and_zoom=False, ) self.simplified_geometries = {} # process events to go out of the current rendering, if any QCoreApplication.processEvents() # update maps # for layout in QgsProject.instance().layoutManager().printLayouts(): # layout.refreshItems() def apply_mask_parameters( self, layer, parameters, dest_crs=None, poly=None, name=None, cleanup_and_zoom=True, keep_layer=True, ): # Apply given mask parameters to the given layer. Returns the new layer # The given layer is removed and then recreated in the layer tree # if poly is not None, it is used as the mask geometry # else, the geometry is taken from parameters.geometry if name is None: mask_name = self.MASK_NAME else: mask_name = name if poly is None: dest_crs, poly = self.get_selected_polygons() if (poly == [] and (parameters.orig_geometry is not None) and (layer is not None)): dest_crs = layer.crs() poly = parameters.orig_geometry if layer is None and poly is None: QMessageBox.critical(None, self.tr("Mask plugin error"), self.tr("No polygon selection !")) return if layer is None: # create a new layer layer = QgsVectorLayer("MultiPolygon?crs=%s" % dest_crs.authid(), mask_name, "memory") style_tools.set_default_layer_symbology(layer) # add a mask filter to all layer for name, l in self.project.mapLayers().items(): if not isinstance(l, QgsVectorLayer): continue mask_filter.add_mask_filter(l) parameters.layer = layer # compute the geometry parameters.orig_geometry = [QgsGeometry(g) for g in poly] parameters.geometry = self.compute_mask_geometries(parameters, poly) # disable rendering self.canvas.setRenderFlag(False) try: if not keep_layer: # save layer's style layer_style = self.get_layer_style(layer) # remove the old layer self.disable_remove_mask_signal = True self.project.removeMapLayer(layer.id()) self.disable_remove_mask_signal = False # (re)create the layer is_mem = not parameters.do_save_as nlayer = None try: nlayer = self.create_layer(parameters, mask_name, is_mem, dest_crs, layer_style) except Exception as e: for m in e.args: QgsMessageLog.logMessage( "apply_mask_parameters - {}".format(m), "Extensions") except RuntimeError as ex: for m in ex.args: if m == 1: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Driver not found !"), ) elif m == 2: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Cannot create data source !"), ) elif m == 3: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Cannot create layer !"), ) elif m == 4: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Attribute type unsupported !"), ) elif m == 5: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Attribute creation failed !"), ) elif m == 6: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Projection error !"), ) elif m == 7: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Feature write failed !"), ) elif m == 8: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Invalid layer !"), ) return if nlayer is None: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("Problem saving the mask layer"), ) return # add the new layer layer = nlayer QgsProject.instance().writeEntry("Mask", "layer_id", nlayer.id()) self.add_layer(layer) parameters.layer = layer else: # replace the mask geometry pr = layer.dataProvider() fid = 0 for f in pr.getFeatures(): fid = f.id() pr.truncate() fet1 = QgsFeature(fid) fet1.setFields(layer.fields()) fet1.setGeometry(parameters.geometry) pr.addFeatures([fet1]) if cleanup_and_zoom: layer.updateExtents() # RH 04 05 2015 > clean up selection of all layers for l in self.canvas.layers(): if l.type() != QgsMapLayer.VectorLayer: # Ignore this layer as it's not a vector continue if l.featureCount() == 0: # There are no features - skip continue l.removeSelection() # RH 04 05 2015 > zooms to mask layer canvas = self.iface.mapCanvas() extent = layer.extent() extent.scale(1.1) # scales extent by 10% unzoomed canvas.setExtent(extent) self.update_menus() # refresh self.canvas.clearCache() finally: self.canvas.setRenderFlag(True) # will call refresh return layer # run method that performs all the real work def run(self): dest_crs, poly = self.get_selected_polygons() is_new = False layer, parameters = self.load_from_project() if not layer: if not poly: QMessageBox.critical( None, self.tr("Mask plugin error"), self.tr("No polygon selection !"), ) return layer = QgsVectorLayer("MultiPolygon?crs=%s" % dest_crs.authid(), self.MASK_NAME, "memory") style_tools.set_default_layer_symbology(layer) is_new = True parameters.layer = layer dlg = MainDialog(parameters, is_new) # for "Apply" and "Ok" self.layer = layer def on_applied_(): keep_layer = not is_new and self.parameters.have_same_layer_options( parameters) new_layer = self.apply_mask_parameters(self.layer, parameters, keep_layer=keep_layer) self.save_to_project(new_layer, parameters) self.layer = new_layer self.parameters = parameters # connect apply dlg.applied.connect(on_applied_) r = dlg.exec_() if r == 1: # Ok on_applied_() self.update_menus() def update_menus(self): # update menus based on whether the layer mask exists or not if self.layer is not None: self.act_aeag_mask.setText(self.tr("Update the current mask")) else: self.act_aeag_mask.setText(self.tr("Create a mask")) # update icon state self.on_current_layer_changed(self.iface.activeLayer()) def on_remove_mask(self, layer_id): if self.disable_remove_mask_signal: return if self.layer is not None and layer_id == self.layer.id(): self.reset_mask_layer() self.update_menus() def get_selected_polygons(self): "return array of (polygon_feature,crs) from current selection" geos = [] layer = self.iface.activeLayer() if not isinstance(layer, QgsVectorLayer): return None, [] for feature in layer.selectedFeatures(): if (feature.geometry() and feature.geometry().type() == QgsWkbTypes.PolygonGeometry): geos.append(QgsGeometry(feature.geometry())) return layer.crs(), geos def add_layer(self, layer): # add a layer to the registry, if not already there layers = self.project.mapLayers() for name, alayer in layers.items(): if alayer == layer: return self.project.addMapLayer(layer, False) # make sure the mask layer is on top of other layers lt = QgsProject.instance().layerTreeRoot() # insert a new on top self.disable_add_layer_signal = True lt.insertChildNode(0, QgsLayerTreeLayer(layer)) self.disable_add_layer_signal = False def get_layer_style(self, layer): if layer is None: return None return ( layer.opacity(), layer.featureBlendMode(), layer.blendMode(), layer.renderer().clone(), ) def set_layer_style(self, nlayer, style): nlayer.setOpacity(style[0]) nlayer.setFeatureBlendMode(style[1]) nlayer.setBlendMode(style[2]) nlayer.setRenderer(style[3]) def set_default_layer_style(self, layer): settings = QSettings() parameters = MaskParameters() defaults = settings.value("mask/defaults", None) if defaults is not None: parameters.unserialize(defaults) else: default_style = os.path.join(os.path.dirname(__file__), "default_mask_style.qml") layer.loadNamedStyle(default_style) def create_layer(self, parameters, name, is_memory, dest_crs, layer_style=None): save_as = parameters.file_path file_format = parameters.file_format # save paramaters serialized = base64.b64encode( parameters.serialize(with_style=False, with_geometry=False)) # save geometry layer = QgsVectorLayer("MultiPolygon?crs=%s" % dest_crs.authid(), name, "memory") pr = layer.dataProvider() layer.startEditing() layer.addAttribute(QgsField("params", QVariant.String)) fet1 = QgsFeature(0) fet1.setFields(layer.fields()) fet1.setAttribute("params", str(serialized)[2:-1]) fet1.setGeometry(parameters.geometry) pr.addFeatures([fet1]) layer.commitChanges() # copy layer style if layer_style is not None: self.set_layer_style(layer, layer_style) if is_memory: return layer if os.path.isfile(save_as): # delete first if already exists if save_as.endswith(".shp"): QgsVectorFileWriter.deleteShapeFile(save_as) else: os.unlink(save_as) # create the disk layer QgsMessageLog.logMessage( "Mask saving '{}' as {}".format(save_as, file_format), "Extensions") error = QgsVectorFileWriter.writeAsVectorFormat( layer, save_as, "System", dest_crs, file_format) if error[0] == 0: QgsMessageLog.logMessage("Error = 0", "Extensions") nlayer = QgsVectorLayer(save_as, name, "ogr") if not nlayer.dataProvider().isValid(): QgsMessageLog.logMessage("Invalid dataProvider", "Extensions") return None if not nlayer.isSpatial(): QgsMessageLog.logMessage("No GeometryType", "Extensions") return None # force CRS nlayer.setCrs(dest_crs) # copy layer style layer_style = self.get_layer_style(layer) self.set_layer_style(nlayer, layer_style) return nlayer else: raise RuntimeError(error) return None def mask_geometry(self): if not self.parameters.geometry: geom = QgsGeometry() return geom, QgsRectangle() geom = QgsGeometry(self.parameters.geometry) # COPY !! if self.parameters.do_simplify: if hasattr(self.canvas, "mapSettings"): tol = (self.parameters.simplify_tolerance * self.canvas.mapSettings().mapUnitsPerPixel()) else: tol = (self.parameters.simplify_tolerance * self.canvas.mapRenderer().mapUnitsPerPixel()) if tol in list(self.simplified_geometries.keys()): geom, bbox = self.simplified_geometries[tol] else: if self.has_simplifier: simplifier = QgsMapToPixelSimplifier( QgsMapToPixelSimplifier.SimplifyGeometry, tol) geom = simplifier.simplify(geom) if not geom.isGeosValid(): # make valid geom = geom.buffer(0.0, 1) bbox = geom.boundingBox() self.simplified_geometries[tol] = ( QgsGeometry(geom), QgsRectangle(bbox), ) else: bbox = geom.boundingBox() return geom, bbox def in_mask(self, feature, srid=None): if feature is None: # expression overview return False if self.layer is None: return False try: # layer is not None but destroyed ? self.layer.id() except: self.reset_mask_layer() return False # mask layer empty due to unloaded memlayersaver plugin > no filtering if self.layer.featureCount() == 0: return True mask_geom, bbox = self.mask_geometry() geom = QgsGeometry(feature.geometry()) if not geom.isGeosValid(): geom = geom.buffer(0.0, 1) if geom is None: return False if srid is not None and self.layer.crs().postgisSrid() != srid: src_crs = QgsCoordinateReferenceSystem(srid) dest_crs = self.layer.crs() xform = QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance()) try: geom.transform(xform) except Exception as e: for m in e.args: QgsMessageLog.logMessage("in_mask - {}".format(m), "Extensions") # transformation error. Check layer projection. pass if geom.type() == QgsWkbTypes.PolygonGeometry: if (self.parameters.polygon_mask_method == 2 and not self.has_point_on_surface): self.parameters.polygon_mask_method = 1 if self.parameters.polygon_mask_method == 0: # this method can only work when no geometry simplification is involved return mask_geom.overlaps(geom) or mask_geom.contains(geom) elif self.parameters.polygon_mask_method == 1: # the fastest method, but with possible inaccuracies pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains( geom.centroid()) elif self.parameters.polygon_mask_method == 2: # will always work pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains( geom.pointOnSurface()) else: return False elif geom.type() == QgsWkbTypes.LineGeometry: if self.parameters.line_mask_method == 0: return mask_geom.intersects(geom) elif self.parameters.line_mask_method == 1: return mask_geom.contains(geom) else: return False elif geom.type() == QgsWkbTypes.PointGeometry: return mask_geom.intersects(geom) else: return False def do_test(self): # This test is hard to run without a full QGIS app running # with renderer, canvas # a layer with labeling filter enabled must be the current layer import time parameters = [ # simplify mask layer, simplify label layer, mask_method (False, False, 0), (True, True, 0), # cannot be used, for reference only (False, False, 1), (False, False, 2), (True, False, 1), (True, False, 2), (False, True, 1), (False, True, 2), (True, True, 1), (True, True, 2), ] # (False, False, 0) 0.3265 # (True, True, 0) 0.1790 # (False, False, 1) 0.2520 # (False, False, 2) 0.3000 # (True, False, 1) 0.1950 # (True, False, 2) 0.2345 # (False, True, 1) 0.2195 # (False, True, 2) 0.2315 # (True, True, 1) 0.1550 <-- # (True, True, 2) 0.1850 <-- # layer with labels to filter layer = self.iface.activeLayer() class RenderCallback: # this class deals with asyncrhonous render signals def __init__(self, parent, params, layer): self.it = 0 self.nRefresh = 20 self.start = time.clock() self.parent = parent self.params = params self.param_it = 0 self.layer = layer self.setup(0) self.parent.canvas.renderComplete.connect(self.update_render) self.parent.canvas.refresh() def setup(self, idx): simplify_mask, simplify_label, mask_method = self.params[idx] self.parent.parameters.do_simplify = simplify_mask self.parent.parameters.simplify_tolerance = 1.0 m = self.layer.simplifyMethod() m.setSimplifyHints( QgsVectorSimplifyMethod.SimplifyHints( 1 if simplify_label else 0)) self.layer.setSimplifyMethod(m) self.parent.mask_method = mask_method def update_render(self, painter): self.it = self.it + 1 if self.it < self.nRefresh: self.parent.canvas.refresh() else: end = time.clock() print( self.params[self.param_it], "%.4f" % ((end - self.start) / self.nRefresh), ) self.param_it += 1 if self.param_it < len(self.params): self.setup(self.param_it) self.it = 0 self.start = end self.parent.canvas.refresh() else: print("end") self.parent.canvas.renderComplete.disconnect( self.update_render) self.cb = RenderCallback(self, parameters, layer)