1

I have build a custom toolbar and a tool for a qgis plugin (QGIS 2.18.13) updating a working one in QGIS 1.8. I have several layers loaded, two of them being vector layers for nodes (points) and edges (lines). The tool creates a custom cursor and allows to draw nodes or edges on a map, depending if the user has selected nodes or edges layer.

When changing to a different layer (no matter what the new layer type is) the tool should be automatically deactivated. This worked in QGIS 1.8, but not now. The tool keeps active when I change from one layer to another. For example, if I am adding nodes and I change to edges layer, the tool keeps activate and does not add edges but nodes. I have tried to force action toggle, but anyway it does not deactivate.

Maybe I am not understanding the triggered signals.

In QGIS 1.8 the tool deactivate() method was called just after layer change, but I do not know who triggers this. Is it the Canvas?

Note: There is no problem with the behaviour of the tool once activated. It does what it is supossed to do.

Here is the relevant code I have:

class myToolbar:

  def __init__(self, iface, toolBar):
    # References to QGIS interface
    self.iface = iface
    self.canvas = iface.mapCanvas()

    # Actions 
    self.addFeatureAction = QAction(QIcon( \
      ":/myPlugin/images/icon.png"), \
      u"Add nodes or edges", \
      self.iface.mainWindow())
    self.addFeatureAction.setWhatsThis(u"MyPlugin: Add nodes or edges")
    self.addFeatureAction.setStatusTip(u"MyPlugin: Add nodes or edges")

    # Set checkable
    self.addFeatureAction.setCheckable(True)

    # Disable tools 
    self.addFeatureAction.setEnabled(False)

    # Connect signals
    self.addFeatureAction.triggered.connect(self.newFeatureRun)

    # Add action to toolBar
    toolBar.addAction(self.anadirFeatureAction)

    # Create tools
    self.newFeatureTool = addFeaturesTool(self.iface)
    self.newFeatureTool.setAction(self.addFeatureAction)

    # Store previous tool
    self.previousTool = self.canvas.mapTool()

    # Activate tools on layer change
    QObject.connect(self.iface,  SIGNAL("currentLayerChanged(QgsMapLayer*)"), self.onCurrentLayerChanged)
    QObject.connect(self.iface.mapCanvas(), SIGNAL("layersChanged()"), self.onCurrentLayerChanged)

  def onCurrentLayerChanged(self):    
    try:
      layer = self.canvas.currentLayer()

      if layer == None or layer.type() != QgsMapLayer.VectorLayer \
        or not self.iface.legendInterface().isLayerVisible(layer):
          self.addFeatureAction.setEnabled(False)
          return

      # Activate according geometry
      if layer.geometryType() == QGis.Point:
        if layer.customProperty("customEditable"):
          self.addFeatureAction.setEnabled(True)
        else:
          self.addFeatureAction.setEnabled(False)
      elif layer.geometryType() == QGis.Line:
        if layer.customProperty("customEditable"):
          self.addFeatureAction.setEnabled(True)
        else:
          self.addFeatureAction.setEnabled(False)
      else:
        self.addFeatureAction.setEnabled(False)
    except Exception as e:
      QgsMessageLog.logMessage( "ERROR onCurrentLayerChanged: {0}".format(e), "issueLayers", 2)

  def newFeatureRun(self):    
    if self.addFeatureAction.isChecked():
      # Save last tool and select new one
      self.previousTool = self.canvas.mapTool()
      self.canvas.setMapTool(self.newFeatureTool)
    else:
      tmpTool = self.canvas.mapTool()
      self.canvas.setMapTool(self.previousTool)

class addFeaturesTool(QgsMapTool):

  def __init__(self, iface):

    QgsMapTool.__init__(self, iface.mapCanvas())

    self.iface = iface
    self.canvas = iface.mapCanvas()

    self.layer = None

    self.previousNode = QgsPoint(0,0) 
    self.nextNode = QgsPoint(0,0) 

    # Rubberband
    self.rubber = QgsRubberBand(self.canvas,  False)

    # Lists to store initial and end nodes
    self.capturedNodes = []
    self.markers = []

    self.cursor = QCursor(QPixmap(["16 16 3 1",
                                  "      c None",
                                  ".     c #FF0000",
                                  "+     c #FFFFFF",
                                  "                ",
                                  "       +.+      ",
                                  "      ++.++     ",
                                  "     +.....+    ",
                                  "    +.     .+   ",
                                  "   +.   .   .+  ",
                                  "  +.    .    .+ ",
                                  " ++.    .    .++",
                                  " ... ...+... ...",
                                  " ++.    .    .++",
                                  "  +.    .    .+ ",
                                  "   +.   .   .+  ",
                                  "   ++.     .+   ",
                                  "    ++.....+    ",
                                  "      ++.++     ",
                                  "       +.+      "]))


  def canvasPressEvent(self,event):
    pass

  def canvasMoveEvent(self,event):
    ptCursor = self.toMapCoordinates(event.pos())

    self.rubber.movePoint(ptCursor)
    pass

  def canvasReleaseEvent(self,event):
    if event.button() == Qt.LeftButton:
      coordinates = self.toMapCoordinates(event.pos())
      if self.layer.geometryType() == QGis.Point:
        self.addNode( coordinates )
      elif self.layer.geometryType() == QGis.Line:
        self.addEdgeExtrem( coordenadas )
    else:
      self.resetCaptura()
    pass                    

  def activate(self):
    self.layer = self.canvas.currentLayer()

    caps = self.layer.dataProvider().capabilities()
    if not caps & QgsVectorDataProvider.AddFeatures:
      QMessageBox.critical(None, "MyPlugin", \
        u"Layer provider does not allow to add new elements")            
      return

    QgsMapTool.activate(self)
    self.canvas.setCursor(self.cursor)

  def deactivate(self):
    self.resetCapture()
    QgsMapTool.deactivate(self)

  def resetCapture(self):
    self.capturedNodes = []

    self.rubber.reset(False)

    for mItem in self.markers:
      self.canvas.scene().removeItem(mItem)
      del mItem

  def isZoomTool(self):
    return False

  def isTransient(self):
    return False

  def isEditTool(self):
    return True

  def addNode(self, coord):
    # Adds node

  def saveNode(self, point):
    # Saves node to layer

  def addEdgeExtrem(self, coord):
    # Add extrems to edge

  def addEdge(self, previousNode, currentNode):
    # adds new edge

UPDATE 1:

Maybe I should have written the question in a simpler way: After activate a custom action (button) on a custom maptoolbar, I want, when changing to a different layer the tool to be unset (so the previous tool will be active) an the button not to be shown checked. So, the user can deactivate the tool by toggling the button on the toolbar or by changing to a different layer. I am unable to get this behaviour working.

UPDATE 2:

I found I can do every time I change a layer on the onCurrentLayerChanged function the following:

if self.addFeatureAction.isChecked():
  self.addFeatureAction.toggle() 
  self.newFeatureRun()

but as eventually the toolbar will have several actions I do not find this useful (to check if each action is active to run the action that is attached to its trigger signal). I have also tried to trigger directly:

if self.addFeatureAction.isChecked():
  self.addFeatureAction.trigger()

but I do not understand why the trigger signal changes again the checked status, so it is not working as I expected.

7
  • Check this solution gis.stackexchange.com/a/259647/49538 and for disconnect simply use iface.legendInterface().currentLayerChanged.disconnect(your_call)
    – Fran Raga
    Commented Jan 23, 2018 at 8:57
  • I have no problems to detect when I am changing from a layer to a different one. QObject.connect(self.iface, SIGNAL("currentLayerChanged(QgsMapLayer*)"), self.onCurrentLayerChanged) or self.iface.currentLayerChanged.connect(self.onCurrentLayerChanged) if you prefer, do the work right. But non eof them are deactivating the custom tool I have created, and thatt is what I want as it was properly working on QGIS 1.8. I do not know if there is any change on how calls to the custom tool functions (activate(), deactivate(), isTransient(), or isEditTool() for example) are performed oin QGIS 2.18
    – anuf
    Commented Jan 23, 2018 at 9:34
  • try using self.iface.mapCanvas().unsetMapTool(self.mapTool)
    – Fran Raga
    Commented Jan 23, 2018 at 10:06
  • Yes, you are riight taht unsetMapTool() forces the tool to be deactivated (and no otehr activated), but my button (action) on the toolbar still keeps pressed. Maybe I should have written the question in a simpler way: After activate a custom action (button) on a custom maptoolbar, I want, when changing to a different layer the tool to be unset (so the previous tool will be active) an the button not to be shown checked. So, the user can deactivate the tool by toggling the button on the toolbar or by changing to a different layer. I am unable to get this behaviour working.
    – anuf
    Commented Jan 24, 2018 at 10:40
  • I found I can do every time I change a layer on the onCurrentLayerChanged function the following: check if self.addFeatureAction.isChecked() and if so do self.addFeatureAction.toggle() and then self.newFeatureRun(), but as eventually the toolbar will have several actions I do not find this useful (to check if each action is active to run the action that is attached to its trigger signal). I have also tried to trigger directly, but I don not understand why the trigger signal changes again the checked status.
    – anuf
    Commented Jan 24, 2018 at 12:20

2 Answers 2

1

Try listenning to the change of layer event using this:

self.iface.currentLayerChanged.connect(self.your_call)
3
  • Is it not the same as QObject.connect(self.iface, SIGNAL("currentLayerChanged(QgsMapLayer*)"), self.onCurrentLayerChanged)? Do they trigger different functions?
    – anuf
    Commented Jan 23, 2018 at 8:23
  • @anuf - This post shows a more recent syntax style which should be recommended.
    – Joseph
    Commented Jan 23, 2018 at 12:05
  • 1
    Thanks @Joseph. I new already about it. I have unchenged the signals as the old are still valid. I now they will be soon no longer valid.
    – anuf
    Commented Jan 24, 2018 at 10:31
0

This works for me (QGIS 3.34.7 console):

import os


class ActionFeatureAddPoint(QAction):
    def __init__(self, iface, parent=None):
        super(ActionFeatureAddPoint, self).__init__(parent)

        self.setObjectName('mActionFo')
        self.setCheckable(True)

        # gui stuff
        self.setText('AddPoint')
        self.setToolTip('add point feature')
        file = 'c:/1temp/mActionAddPoint.png'
        if os.path.isfile(file):
            self.setIcon(QIcon(file))

        # vars
        self.iface = iface

        # create map tool
        self.map_canvas = self.iface.mapCanvas()
        self.map_tool = QgsMapToolEmitPoint(self.map_canvas)
        self.map_tool.canvasClicked.connect(self.add_point)

        self.map_tool.setAction(self)

        self.last_tool = None

        self.layer = self.iface.activeLayer()
        print('__init__', self.layer.name())

        # check alway edit state of the layer
        self.no_check = True
        if self.layer is not None:
            self.layer.editingStarted.connect(self.edit_state_changed)
            self.layer.editingStopped.connect(self.edit_state_changed)
        self.no_check = False

        # check always when the layer is changed
        self.map_canvas.currentLayerChanged.connect(self.set_state)

        # set the current state, but give ui some time to create the widget
        self.set_state(self.layer)
        # QTimer.singleShot(100,lambda:self.set_state(self.layer))

        # do work when clicked
        self.triggered.connect(self.run)

    def edit_state_changed(self):
        if self.no_check:
            return
        self.set_state(self.layer)

    def layer_changed(self, layer):
        print('layer_changed', layer.name())
        self.set_state(layer)

    def set_state(self, layer):

        enabled_flag = False

        # disconnect signals
        try:
            if self.layer is not None:
                self.layer.editingStarted.disconnect(self.edit_state_changed)
                self.layer.editingStopped.disconnect(self.edit_state_changed)
        except Exception as e:
            print(e)

        # set refernce to new layer 
        self.layer = layer

        # connect signals again
        if self.layer is not None:
            self.layer.editingStarted.connect(self.edit_state_changed)
            self.layer.editingStopped.connect(self.edit_state_changed)
            print('set_state', self.layer.name())

        # check
        if layer is not None and isinstance(layer, QgsVectorLayer):
            print('set_state', 1)
            if layer.isSpatial() and layer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry:
                print('set_state', 2)
                if layer.isEditable():
                    print('set_state', 3)
                    enabled_flag = True

        self.setEnabled(enabled_flag)
        if not enabled_flag:
            # self.map_canvas.unsetMapTool(self.map_tool)
            self.map_tool.deactivate()
            if self.last_tool is not None:
                act = self.last_tool.action()
                if act is not None:
                    print('last tool', act.objectName())
                    act.trigger()
            self.last_tool = None

    def run(self):
        self.setChecked(True)
        if self.last_tool is None:
            self.last_tool = self.map_canvas.mapTool()
        self.map_canvas.setMapTool(self.map_tool)

    def add_point(self, point_xy, mouse_button):
        if self.isEnabled() and mouse_button == Qt.LeftButton:
            # convert the point_xy
            geom = QgsGeometry.fromPointXY(point_xy)

            # create the feature with default values
            feat = QgsVectorLayerUtils.createFeature(self.layer)
            feat.setGeometry(geom)

            # add the feature
            if self.show_form(self.layer, feat):
                # add the feature
                self.layer.addFeature(feat)
                self.layer.updateFeature(feat)

    def show_form(self, layer, feat):
        try:
            config = layer.editFormConfig()
            if config.suppress() == QgsEditFormConfig.SuppressOff:
                return self.iface.openFeatureForm(layer, feat)
            else:
                return True
        except Exception as e:
            self.info.err(e)


# find test toolbar
tb = iface.mainWindow().findChild(QToolBar, 'toolbar_fo')
if tb is None:
    tb = QToolBar()
    tb.setObjectName('toolbar_fo')
    tb.setWindowTitle('my toolbar')
    tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
    iface.addToolBar(tb)
    # create the action
    act = ActionFeatureAddPoint(iface, iface.mainWindow())
    tb.addAction(act)

# Now we can find the action by it's object name
# because we set mainWindow() as parent :
a = iface.mainWindow().findChild(QAction, 'mActionFo')
# a.trigger()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.