In part 1 I talked about the new style dock and the in built functions it has in this post I would like to cover how you can extend that dock using plugin panels.
While building the dock it became evident that being able to add your own panels with be super neat, why lock the user to what styling controls we have built into core.
After one, or two, API iterations it is now possible to add your own panels to the style dock.
First we need to make a new widget using QgsLayerStylingPanel
class CustomPanel(QgsLayerStylingPanel): def __init__(self, layer, canvas, parent): super(CustomPanel, self).__init__(layer, canvas, parent) self.layer = layer def apply(self): pass
then we need to create a factory object (QgsLayerStylingPanelFactory) which will hold the metadata for the panel widget, things like icon, title, and the widget it self as required. The factory object looks like this:
class PanelFactory(QgsLayerStylingPanelFactory): def icon(self): return QIcon(r"F:\icons\SVG\book.svg") def title(self): return "" def supportsLayer( self, layer): return layer.type() == QgsMapLayer.VectorLayer def createPanel(self, layer, canvas, parent): return CustomPanel(layer, canvas, parent) # Also make a instance and tell the style dock about it # make sure you keep a instance of the the factory around factory = PanelFactory() iface.registerMapStylePanelFactory(factory)
You can also see we have a
supportsLayer() method. This must return True if the layer can be supported for the widget, if the layer isn’t support it’s not shown in the layer style dock. We also have the
createPanel method which just returns the widget, you could also use this method to return different widgets based on layer type if you needed. This
createPanel method will be called any time the item is selected in the left panel of the style dock. Using the above code we will see a new icon on the side panel
Remember, the create method is called any time this new item is selected. If your widget is expensive to create you can create a cache here.
Now lets add some logic. As an example I’m going to load the style xml and show it in the panel. Adding some logic to the
__init__ method of the CustomPanel class
class CustomPanel(QgsLayerStylingPanel): def __init__(self, layer, canvas, parent): super(CustomPanel, self).__init__(layer, canvas, parent) self.layer = layer self.setLayout(QVBoxLayout()) # Create the editor and set the xml from the layer. self.editor = QgsCodeEditorHTML() self.editor.setLexer(QsciLexerXML()) doc = QDomDocument( "style" ) rootNode = doc.createElement( "qgis" ) doc.appendChild( rootNode ) iface.activeLayer().writeStyle( rootNode, doc, "") xml = doc.toString() self.editor.setText(xml) self.layout().addWidget(self.editor)
Nifty. Now can we 1 UP it and make it live edit? Sure can! The style dock will call apply() whenever we raise the widgetChanged signal. We can just connect this to the text changed event and add some logic to the apply method.
class CustomPanel(QgsLayerStylingPanel): def __init__(self, layer, canvas, parent): super(CustomPanel, self).__init__(layer, canvas, parent) ..... self.editor.textChanged.connect(self.widgetChanged.emit) def apply(self): # Read the xml and set it back to the style. doc = QDomDocument( "style" ) doc.setContent(self.editor.text()) node = doc.documentElement() self.layer.readStyle(node, "") self.layer.triggerRepaint()
Pro Tip: Raise widgetChanged whenever you need to tell the style dock to trigger the apply method. The style state of the layer will also be saved to the undo stack so you don’t have to worry about that.
This is just an example, hand editing xml is well gross, but I’m sure you can see great uses for being able to expand the style dock.