class LSbar(QToolBar): count = 0 def __init__(self, iface, mainClass, showDialog): QToolBar.__init__(self) self.iface = iface self.mainClass = mainClass LSbar.count+=1 #self.setToolTip('test') # LiveStatBar's properties self.name = "LiveStat %i" % LSbar.count self.autoName = 2 self.layer = None self.fieldName = '$area' self.filter = '' self.functionName = 'Sum' self.selectedOnly = 2 self.precision = 2 self.suffix = ' ' self.factor = '1' self.separator = 2 self.position = {} self.position['floating'] = 0 self.position['x'] = 0 self.position['y'] = 0 # LiveStatBar's dialog self.dialog = LSeditor(self.iface, self) QObject.connect(self.dialog, SIGNAL('accepted()'), self.dialogAccepted) # We connect the bar to some events that may trigger an update QObject.connect(self.iface, SIGNAL('currentLayerChanged(QgsMapLayer*)'), self.layerChanged) QObject.connect(self.iface.mapCanvas(), SIGNAL('selectionChanged(QgsMapLayer*)'), self.selectionChanged) # Setup the bar GUI self.setWindowTitle(self.name) # Create widgets self.nameWidget = QLabel(self.name); self.displayWidget = QLabel("Click to edit") # Layout widgets self.addWidget( self.nameWidget ) self.addWidget( self.displayWidget ) self.setMinimumSize( self.sizeHint() ) # We display the dialog at creation (if required) if showDialog: self.dialog.show(self) def showEvent(self, QShowEvent): QToolBar.showEvent(self, QShowEvent) self.compute() def mousePressEvent(self, event): # We want the dialog to display on a simple click self.dialog.show(self) def dialogAccepted(self): # If the clone box is checked, we create a new bar and apply the settings on the new bar makeClone = self.dialog.cloneUI.checkState() if makeClone: newStatsBar = LSbar(self.iface, self.mainClass, False) self.mainClass.addBar(newStatsBar) bar = newStatsBar else: bar = self # When the dialog is accepted, we update the bar's attribute to correspond to the dialog's input bar.name = self.dialog.nameUI.text() bar.autoName = self.dialog.autoNameUI.checkState() bar.layer = self.dialog.layerUI.currentLayer() bar.fieldName = self.dialog.fieldUI.currentText() bar.filter = self.dialog.filterUI.text() bar.functionName = self.dialog.functionUI.currentText() bar.selectedOnly = self.dialog.selectionUI.checkState() bar.precision = self.dialog.precisionUI.value() bar.suffix = self.dialog.suffixUI.text() bar.factor = self.dialog.factorUI.text() bar.separator = self.dialog.separatorUI.checkState() bar.setWindowTitle('LiveStat "'+bar.name+'"') # And we recompute the bar self.compute() if makeClone: # If it's a clone, we update it bar.compute() def dialogDelete(self): self.mainClass.removeBar(self) def layerChanged(self): # When the active layer changed, we trigger an update (but only if the bar displays stats of the -CURRENT- layer) if self.isVisible() and self.layer is None: self.compute() def selectionChanged(self): # When the current selection changes, we trigger an update (but only if the bar displays stats of the selection) if self.isVisible() and self.selectedOnly: self.compute() def compute(self): # This recompoutes the bar #QgsMessageLog.logMessage('COMPUTE','LiveStats') try: #Get the layer. if self.layer is not None: layer = self.layer else: # If the layer is None, it means we display the activeLayer activeLayer = self.iface.activeLayer() if activeLayer is None or activeLayer.type() != QgsMapLayer.VectorLayer: raise NoLayerError() layer = activeLayer #Prepare the computer if self.functionName == 'Count': computer = LScomputerCount() elif self.functionName == 'NonNull': computer = LScomputerNonNull() elif self.functionName == 'Sum': computer = LScomputerSum() elif self.functionName == 'Min': computer = LScomputerMin() elif self.functionName == 'Max': computer = LScomputerMax() elif self.functionName == 'Mean': computer = LScomputerMean() elif self.functionName == 'Concat': computer = LScomputerConcat() elif self.functionName == 'UniqueConcat': computer = LScomputerUniqueConcat() else: raise NoComputerError() def formatter(result): # And we finally display the result in the widget self.locale = QLocale() if not self.separator: self.locale.setNumberOptions(QLocale.OmitGroupSeparator) result *= float(self.factor) if self.precision < 0: pow10 = 10**(-self.precision) result = self.locale.toString(float(pow10*round(result/pow10)), 'f', 0) else: result = self.locale.toString(result, 'f', self.precision) result = result+self.suffix return result computer.formatter = formatter #Get the field index for the field that is being comuputed computeFieldName = self.fieldName #Not really clear what this does... #layer.select( layer.pendingAllAttributesList() ) if self.filter != '': expression = QgsExpression(self.filter) expression.prepare(layer.pendingFields()) if expression.hasParserError(): raise ParserError(expression.parserErrorString()) else: expression = None if self.fieldName not in ['$area', '$length'] and layer.fieldNameIndex( computeFieldName ) == -1: raise NoFieldError() def getFeatures(): if self.selectedOnly: return layer.selectedFeatures() else: # The layer is an iterator that returns a QgsFeature on next(), so just return the layer return layer # Loop the features. for feature in getFeatures(): if expression is not None: result = expression.evaluate(feature, layer.pendingFields()) if expression.hasEvalError(): raise EvalError(expression.evalErrorString()) continue if not result.toBool(): continue self.valueForFeature(feature, computer, computeFieldName) result = computer.result() #self.displayWidget.setText(result) self.setText(result) except NoLayerError as e: self.setText('---') return except ParserError as e: self.setText('PARSER ERROR : '+str(e)) return except NoComputerError as e: self.setText('NO COMPUTER !') return except NoFieldError as e: self.setText('NO FIELD !') return except NoGeometryError as e: self.setText('NO GEOMETRY !') return except EvalError as e: self.setText('EVAL ERROR : '+str(e)) return def setText(self, text): self.nameWidget.setText( self.name + ' : ' ) self.displayWidget.setText(text) #Resize the widget self.setMinimumSize( self.sizeHint() ) #TODO : there are some glitches when text length increases and the toolbar is docked on a side #but none of this works :/ #self.updateGeometry() #self.update() #self.repaint() #self.iface.mainWindow().updateGeometry() #self.iface.mainWindow().update() #self.iface.mainWindow().repaint() def valueForFeature(self, feature, computer, computeFieldName): # This returns the value for a feature and a computeFieldIndex if self.fieldName == '$area': if feature.geometry() is None: # This happens for instance when a CSV layer is selected. # They are considered as vector layer (!!) but their features have no geometry raise NoGeometryError() val = feature.geometry().area() elif self.fieldName == '$length': if feature.geometry() is None: # This happens for instance when a CSV layer is selected. # They are considered as vector layer (!!) but their features have no geometry raise NoGeometryError() val = feature.geometry().length() else: val = feature.attribute(computeFieldName) computer.addVal( val ) def save(self): # This returns this bar's attributes as a QString to be stored in the file returnStringList = [] # Statistics attributes returnStringList.append( self.name ) #0 returnStringList.append( str(self.autoName) ) #1 if self.layer is None: returnStringList.append( '' ) #2 else: returnStringList.append( self.layer.id() ) #2 returnStringList.append( self.fieldName ) #3 returnStringList.append( self.filter ) #4 returnStringList.append( self.functionName ) #5 returnStringList.append( str(self.selectedOnly) ) #6 returnStringList.append( str(self.precision) ) #7 returnStringList.append( self.suffix ) #8 returnStringList.append( self.factor ) #9 returnStringList.append( str(self.separator) ) #10 # Position attributes returnStringList.append( str(int(self.isFloating())) ) #11 returnStringList.append( str(self.pos().x()) ) #12 returnStringList.append( str(self.pos().y()) ) #13 return '*|*'.join(returnStringList) def load(self, string): # This sets this bar's attributes from a QString to be loaded loadStringList = string.split('*|*') try: # Statistics attributes self.name = loadStringList[0] #0 self.autoName = int(loadStringList[1]) #1 self.layer = None #2 for l in self.iface.legendInterface().layers(): if l.id() == loadStringList[2]: self.layer = l #2 self.fieldName = loadStringList[3] #3 self.filter = loadStringList[4] #4 self.functionName = loadStringList[5] #5 self.selectedOnly = int(loadStringList[6]) #6 self.precision = int(loadStringList[7]) #7 self.suffix = loadStringList[8] #8 self.factor = loadStringList[9] #9 self.separator = int(loadStringList[10]) #10 # Position attributes #self.storedGeometry = loadStringList[11].toAscii() #11 self.position['floating'] = bool(int(loadStringList[11])) self.position['x'] = int(loadStringList[12]) self.position['y'] = int(loadStringList[13]) except IndexError as e: # On plugin update, if attributes are added, this allows to load as much as possible... Remaining attributes will be defaults pass self.setWindowTitle('LiveStat "'+self.name+'"') self.compute()