Esempio n. 1
0
class Qgeric:

    def __init__(self, iface):
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            os.path.dirname(__file__),
            'i18n',
            'qgeric_{}.qm'.format(locale))
        
        self.translator = None
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)
                
        self.iface = iface
        self.sb = self.iface.mainWindow().statusBar()
        self.tool = None

        self.results = []

        self.actions = []
        self.menu = '&Qgeric'
        self.toolbar = self.iface.addToolBar('Qgeric')
        self.toolbar.setObjectName('Qgeric')
        
        self.loadingWindow = QProgressDialog(self.tr('Selecting...'),self.tr('Pass'),0,100)
        self.loadingWindow.setAutoClose(False)
        self.loadingWindow.close()
        
        self.themeColor = QColor(60,151,255, 128)
        
    def unload(self):
        for action in self.actions:
            self.iface.removePluginVectorMenu('&Qgeric', action)
            self.iface.removeToolBarIcon(action)
        del self.toolbar
        
    def tr(self, message):
        return QCoreApplication.translate('Qgeric', message)

    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        checkable=False,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        menu=None,
        parent=None):

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        action.setCheckable(checkable)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if menu is not None:
            action.setMenu(menu)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        icon_path = ':/plugins/qgeric/resources/icon_AT.png'
        self.add_action(
            icon_path,
            text=self.tr('Display selection\'s results'),
            callback=self.showAttributesTable,
            parent=self.iface.mainWindow()
        )
        self.toolbar.addSeparator()
        icon_path = ':/plugins/qgeric/resources/icon_SelPt.png'
        self.add_action(
            icon_path,
            text=self.tr('Point request tool'),
            checkable=True,
            callback=self.pointSelection,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qgeric/resources/icon_SelR.png'
        self.add_action(
            icon_path,
            text=self.tr('Rectangle request tool'),
            checkable=True,
            callback=self.rectangleSelection,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qgeric/resources/icon_SelC.png'
        self.add_action(
            icon_path,
            text=self.tr('Circle request tool'),
            checkable=True,
            callback=self.circleSelection,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qgeric/resources/icon_SelP.png'
        self.add_action(
            icon_path,
            text=self.tr('Polygon request tool'),
            checkable=True,
            callback=self.polygonSelection,
            parent=self.iface.mainWindow()
        )
        bufferMenu = QMenu()
        polygonBufferAction = QAction(QIcon(':/plugins/qgeric/resources/icon_SelTP.png'), self.tr('Polygon buffer request tool on the selected layer'), bufferMenu)
        polygonBufferAction.triggered.connect(self.polygonBufferSelection)
        bufferMenu.addAction(polygonBufferAction)
        icon_path = ':/plugins/qgeric/resources/icon_SelT.png'
        self.add_action(
            icon_path,
            text=self.tr('Buffer request tool on the selected layer'),
            checkable=True,
            menu=bufferMenu,
            callback=self.bufferSelection,
            parent=self.iface.mainWindow()
        )

    def showAttributesTable(self):        
        tab = AttributesTable(self.iface)

        layers = QgsProject().instance().mapLayers().values()

        for layer in layers:
            if layer.type() == QgsMapLayer.VectorLayer and QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible():
                fields_name = [field.name() for field in layer.fields()]
                fields_type = [field.type() for field in layer.fields()]
                cells = layer.selectedFeatures()
                if len(cells) != 0:
                    tab.addLayer(layer, fields_name, fields_type, cells)
                    
        tab.loadingWindow.close()
        tab.show()
        tab.activateWindow();
        tab.showNormal();
        
        self.results.append(tab)
    
    def closeAttributesTable(self, tab):
        self.results.remove(tab)
    
    def pointSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = selectPoint(self.iface, self.themeColor)
        self.tool.setAction(self.actions[1])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Left click to place a point.'))
        
    def rectangleSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = selectRect(self.iface, self.themeColor, 1)
        self.tool.setAction(self.actions[2])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Maintain the left click to draw a rectangle.'))
    
    def circleSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = selectCircle(self.iface, self.themeColor, 1, 40) # last parameter = number of vertices
        self.tool.setAction(self.actions[3])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Maintain the left click to draw a circle. Simple Left click to give a perimeter.'))
    
    def polygonSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = selectPolygon(self.iface, self.themeColor, 1)
        self.tool.setAction(self.actions[4])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Left click to place points. Right click to confirm.'))
        
    def bufferSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'buffer'
        self.tool = selectPoint(self.iface, self.themeColor)
        self.actions[5].setIcon(QIcon(':/plugins/qgeric/resources/icon_SelT.png'))
        self.actions[5].setText(self.tr('Buffer request tool on the selected layer'))
        self.actions[5].triggered.disconnect()
        self.actions[5].triggered.connect(self.bufferSelection)
        self.actions[5].menu().actions()[0].setIcon(QIcon(':/plugins/qgeric/resources/icon_SelTP.png'))
        self.actions[5].menu().actions()[0].setText(self.tr('Polygon buffer request tool on the selected layer'))
        self.actions[5].menu().actions()[0].triggered.disconnect()
        self.actions[5].menu().actions()[0].triggered.connect(self.polygonBufferSelection)
        self.tool.setAction(self.actions[5])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Select a vector layer in the Layer Tree, then left click on an attribute of this layer on the map.'))
        
    def polygonBufferSelection(self):
        if self.tool:
            self.tool.reset()
        self.request = 'buffer'
        self.tool = selectPolygon(self.iface, self.themeColor, 1)
        self.actions[5].setIcon(QIcon(':/plugins/qgeric/resources/icon_SelTP.png'))
        self.actions[5].setText(self.tr('Polygon buffer request tool on the selected layer'))
        self.actions[5].triggered.disconnect()
        self.actions[5].triggered.connect(self.polygonBufferSelection)
        self.actions[5].menu().actions()[0].setIcon(QIcon(':/plugins/qgeric/resources/icon_SelT.png'))
        self.actions[5].menu().actions()[0].setText(self.tr('Buffer request tool on the selected layer'))
        self.actions[5].menu().actions()[0].triggered.disconnect()
        self.actions[5].menu().actions()[0].triggered.connect(self.bufferSelection)
        self.tool.setAction(self.actions[5])
        self.tool.selectionDone.connect(self.returnedBounds)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.sb.showMessage(self.tr('Left click to place points. Right click to confirm.'))
    
    def geomTransform(self, geom, crs_orig, crs_dest):
        g = QgsGeometry(geom)
        crsTransform = QgsCoordinateTransform(crs_orig, crs_dest, QgsProject().instance())
        g.transform(crsTransform)
        return g
    
    def returnedBounds(self):
        rb = self.tool.rb

        warning = True
        ok = True
        active = False
        errBuffer_noAtt = False
        errBuffer_Vertices = False
        
        buffer_geom = None
        buffer_geom_crs = None
        
        # we check if there's at least one visible layer
        for layer in QgsProject().instance().mapLayers().values():
            if QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible():
                warning = False
                active = True
                break
                
        # buffer creation on the current layer
        if self.request == 'buffer':
            layer = self.iface.layerTreeView().currentLayer()
            if layer is not None and layer.type() == QgsMapLayer.VectorLayer and QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible():
                # rubberband reprojection
                g = self.geomTransform(rb.asGeometry(), self.iface.mapCanvas().mapSettings().destinationCrs(), layer.crs())
                features = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
                rbGeom = []
                for feature in features:
                    geom = feature.geometry()
                    if g.intersects(geom):
                        rbGeom.append(QgsGeometry(feature.geometry()))
                if len(rbGeom) > 0:
                    union_geoms = rbGeom[0]
                    for geometry in rbGeom:
                        if union_geoms.combine(geometry) is not None:
                            union_geoms = union_geoms.combine(geometry)
                    
                    rb.setToGeometry(union_geoms, layer)
                    
                    perim, ok = QInputDialog.getDouble(self.iface.mainWindow(), self.tr('Perimeter'), self.tr('Give a perimeter in m:')+'\n'+self.tr('(works only with metric crs)'), min=0)
                    buffer_geom_crs = layer.crs()
                    buffer_geom = union_geoms.buffer(perim, 40)
                    rb.setToGeometry(buffer_geom, QgsVectorLayer("Polygon?crs="+layer.crs().authid(),"","memory"))
                    
                    if buffer_geom.length == 0 :
                        warning = True
                        errBuffer_Vertices = True
                else:
                    warning = True
                    errBuffer_noAtt = True
            else:
                warning = True
                        
        if len(QgsProject().instance().mapLayers().values()) > 0 and warning == False and ok:
            self.loadingWindow.show()
            self.loadingWindow.activateWindow();
            self.loadingWindow.showNormal();
            for layer in QgsProject().instance().mapLayers().values():
                if layer.type() == QgsMapLayer.VectorLayer and QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible():
                    if self.request == 'buffer' and self.iface.layerTreeView().currentLayer() == layer:
                        layer.selectByIds([])
                        continue
                    self.loadingWindow.reset()
                    self.loadingWindow.setWindowTitle(self.tr('Selecting...'))
                    self.loadingWindow.setLabelText(layer.name())
                    
                    # rubberband reprojection
                    if self.request == 'buffer':
                        if buffer_geom_crs.authid() != layer.crs().authid():
                            g = self.geomTransform(buffer_geom, buffer_geom_crs, layer.crs())
                        else:
                            g = self.geomTransform(buffer_geom, buffer_geom_crs, layer.crs())
                    else:
                        g = self.geomTransform(rb.asGeometry(), self.iface.mapCanvas().mapSettings().destinationCrs(), layer.crs())
                    
                    feat_id = []
                    features = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
                    count = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
                    
                    nbfeatures = 0
                    for feature in count:
                        nbfeatures+=1
                                        
                    # Select attributes intersecting with the rubberband
                    index = 0
                    for feature in features:
                        geom = feature.geometry()
                        try:
                            if g.intersects(geom):
                                feat_id.append(feature.id())
                        except:
                            # There's an error but it intersects
                            print('error with '+layer.name()+' on '+str(feature.id()))
                            feat_id.append(feature.id())
                        index += 1
                        self.loadingWindow.setValue(int((float(index)/nbfeatures)*100))
                        if self.loadingWindow.wasCanceled():
                            self.loadingWindow.reset()
                            break
                        QApplication.processEvents()
                    layer.selectByIds(feat_id)
            
            self.loadingWindow.close()
            self.showAttributesTable()
        else:
            # Display a warning in the message bar depending of the error
            if active == False:
                self.iface.messageBar().pushWarning(self.tr('Warning'), self.tr('There is no active layer !'))
            elif ok == False:
                pass
            elif errBuffer_noAtt:
                self.iface.messageBar().pushWarning(self.tr('Warning'), self.tr('You didn\'t click on a layer\'s attribute !'))
            elif errBuffer_Vertices:
                self.iface.messageBar().pushWarning(self.tr('Warning'), self.tr('You must give a non-null value for a point\'s or line\'s perimeter !'))
            else:
                self.iface.messageBar().pushWarning(self.tr('Warning'), self.tr('There is no selected layer, or it is not vector nor visible !'))
Esempio n. 2
0
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()