示例#1
0
class AequilibraEMenu:
    def __init__(self, iface):

        self.translator = None
        self.iface = iface
        self.project = None  # type: Project
        self.link_layer = None  # type: QgsVectorLayer
        self.node_layer = None  # type: QgsVectorLayer

        self.dock = QDockWidget(self.trlt('AequilibraE'))
        self.manager = QWidget()

        # The self.toolbar will hold everything
        self.toolbar = QToolBar()
        self.toolbar.setOrientation(2)

        # # ########################################################################
        # # #######################   PROJECT SUB-MENU   ############################

        projectMenu = QMenu()
        self.open_project_action = QAction(self.trlt('Open Project'),
                                           self.manager)
        self.open_project_action.triggered.connect(self.run_load_project)
        projectMenu.addAction(self.open_project_action)

        self.project_from_osm_action = QAction(
            self.trlt('Create project from OSM'), self.manager)
        self.project_from_osm_action.triggered.connect(
            self.run_project_from_osm)
        projectMenu.addAction(self.project_from_osm_action)

        self.create_transponet_action = QAction(
            self.trlt('Create Project from layers'), self.manager)
        self.create_transponet_action.triggered.connect(
            self.run_create_transponet)
        projectMenu.addAction(self.create_transponet_action)

        projectButton = QToolButton()
        projectButton.setText(self.trlt('Project'))
        projectButton.setPopupMode(2)
        projectButton.setMenu(projectMenu)

        self.toolbar.addWidget(projectButton)

        # # ########################################################################
        # # ################# NETWORK MANIPULATION SUB-MENU  #######################

        netMenu = QMenu()
        self.action_netPrep = QAction(self.trlt('Network Preparation'),
                                      self.manager)
        self.action_netPrep.triggered.connect(self.run_net_prep)
        netMenu.addAction(self.action_netPrep)

        self.add_connectors_action = QAction(
            self.trlt('Add centroid connectors'), self.manager)
        self.add_connectors_action.triggered.connect(self.run_add_connectors)
        netMenu.addAction(self.add_connectors_action)

        netbutton = QToolButton()
        netbutton.setText(self.trlt('Network Manipulation'))
        netbutton.setMenu(netMenu)
        netbutton.setPopupMode(2)

        self.toolbar.addWidget(netbutton)
        # # ########################################################################
        # # ####################  DATA UTILITIES SUB-MENU  #########################

        dataMenu = QMenu()
        self.display_custom_formats_action = QAction(
            self.trlt('Display AequilibraE formats'), self.manager)
        self.display_custom_formats_action.triggered.connect(
            self.run_display_aequilibrae_formats)
        dataMenu.addAction(self.display_custom_formats_action)

        self.load_matrix_action = QAction(self.trlt('Import matrices'),
                                          self.manager)
        self.load_matrix_action.triggered.connect(self.run_load_matrices)
        dataMenu.addAction(self.load_matrix_action)

        self.load_database_action = QAction(self.trlt('Import dataset'),
                                            self.manager)
        self.load_database_action.triggered.connect(self.run_load_database)
        dataMenu.addAction(self.load_database_action)

        databutton = QToolButton()
        databutton.setText(self.trlt('Data'))
        databutton.setPopupMode(2)
        databutton.setMenu(dataMenu)

        self.toolbar.addWidget(databutton)

        # # # ########################################################################
        # # # ##################  TRIP DISTRIBUTION SUB-MENU  ########################

        distributionButton = QToolButton()
        distributionButton.setText(self.trlt('Trip Distribution'))
        distributionButton.clicked.connect(self.run_distribution_models)
        self.toolbar.addWidget(distributionButton)

        # # ########################################################################
        # # ###################  PATH COMPUTATION SUB-MENU   #######################
        pathMenu = QMenu()

        self.shortest_path_action = QAction(self.trlt('Shortest path'),
                                            self.manager)
        self.shortest_path_action.triggered.connect(self.run_shortest_path)
        pathMenu.addAction(self.shortest_path_action)

        self.dist_matrix_action = QAction(self.trlt('Impedance matrix'),
                                          self.manager)
        self.dist_matrix_action.triggered.connect(self.run_dist_matrix)
        pathMenu.addAction(self.dist_matrix_action)

        self.traffic_assignment_action = QAction(
            self.trlt('Traffic Assignment'), self.manager)
        self.traffic_assignment_action.triggered.connect(
            self.run_traffic_assig)
        pathMenu.addAction(self.traffic_assignment_action)

        pathButton = QToolButton()
        pathButton.setText(self.trlt('Paths and assignment'))
        pathButton.setPopupMode(2)
        pathButton.setMenu(pathMenu)

        self.toolbar.addWidget(pathButton)

        # # ########################################################################
        # # #######################   ROUTING SUB-MENU   ###########################
        if has_ortools:
            routingMenu = QMenu()
            self.tsp_action = QAction(self.trlt('Travelling Salesman Problem'),
                                      self.manager)
            self.tsp_action.triggered.connect(self.run_tsp)
            routingMenu.addAction(self.tsp_action)

            routingButton = QToolButton()
            routingButton.setText(self.trlt('Routing'))
            routingButton.setPopupMode(2)
            routingButton.setMenu(routingMenu)

            self.toolbar.addWidget(routingButton)

        # # ########################################################################
        # # #######################   TRANSIT SUB-MENU   ###########################
        transitMenu = QMenu()
        self.gtfs_import_action = QAction(
            self.trlt('Convert GTFS to SpatiaLite'), self.manager)
        self.gtfs_import_action.triggered.connect(self.run_import_gtfs)
        transitMenu.addAction(self.gtfs_import_action)

        transitButton = QToolButton()
        transitButton.setText(self.trlt('Public Transport'))
        transitButton.setPopupMode(2)
        transitButton.setMenu(transitMenu)

        self.toolbar.addWidget(transitButton)

        # ########################################################################
        # #################        GIS TOOLS SUB-MENU    #########################

        gisMenu = QMenu()
        self.simple_tag_action = QAction(self.trlt('Simple tag'), self.manager)
        self.simple_tag_action.triggered.connect(self.run_simple_tag)
        gisMenu.addAction(self.simple_tag_action)

        self.lcd_action = QAction(self.trlt('Lowest common denominator'),
                                  self.manager)
        self.lcd_action.triggered.connect(self.run_lcd)
        gisMenu.addAction(self.lcd_action)

        self.dlines_action = QAction(self.trlt('Desire Lines'), self.manager)
        self.dlines_action.triggered.connect(self.run_dlines)
        gisMenu.addAction(self.dlines_action)

        self.bandwidth_action = QAction(self.trlt('Stacked Bandwidth'),
                                        self.manager)
        self.bandwidth_action.triggered.connect(self.run_bandwidth)
        gisMenu.addAction(self.bandwidth_action)

        self.scenario_comparison_action = QAction(
            self.trlt('Scenario Comparison'), self.manager)
        self.scenario_comparison_action.triggered.connect(
            self.run_scenario_comparison)
        gisMenu.addAction(self.scenario_comparison_action)

        gisButton = QToolButton()
        gisButton.setText(self.trlt('GIS'))
        gisButton.setPopupMode(2)
        gisButton.setMenu(gisMenu)

        self.toolbar.addWidget(gisButton)

        # ########################################################################
        # #################          LOOSE STUFF         #########################

        parametersButton = QToolButton()
        parametersButton.setText(self.trlt('Parameters'))
        parametersButton.clicked.connect(self.run_change_parameters)
        self.toolbar.addWidget(parametersButton)

        aboutButton = QToolButton()
        aboutButton.setText(self.trlt('About'))
        aboutButton.clicked.connect(self.run_about)
        self.toolbar.addWidget(aboutButton)

        logButton = QToolButton()
        logButton.setText(self.trlt('logfile'))
        logButton.clicked.connect(self.run_log)
        self.toolbar.addWidget(logButton)

        helpButton = QToolButton()
        helpButton.setText(self.trlt('Help'))
        helpButton.clicked.connect(self.run_help)
        self.toolbar.addWidget(helpButton)

        if no_binary:
            binariesButton = QToolButton()
            binariesButton.setText(self.trlt('Download binaries'))
            binariesButton.clicked.connect(self.run_binary_download)
            self.toolbar.addWidget(binariesButton)

        if not extra_packages:
            xtrapkgButton = QToolButton()
            xtrapkgButton.setText(self.trlt('Install extra packages'))
            xtrapkgButton.clicked.connect(self.install_extra_packages)
            self.toolbar.addWidget(xtrapkgButton)

        # ########################################################################
        # #################        PROJECT MANAGER       #########################

        self.showing = QCheckBox()
        self.showing.setText('Show project info')
        self.showing.setChecked(True)
        self.toolbar.addWidget(self.showing)

        self.showing.toggled.connect(self.hide_info_pannel)
        self.projectManager = QTabWidget()
        self.toolbar.addWidget(self.projectManager)

        # # # ########################################################################
        self.tabContents = []
        self.toolbar.setIconSize(QSize(16, 16))

        p1_vertical = QVBoxLayout()
        p1_vertical.setContentsMargins(0, 0, 0, 0)
        p1_vertical.addWidget(self.toolbar)
        self.manager.setLayout(p1_vertical)

        self.dock.setWidget(self.manager)
        self.dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dock)

    def run_help(self):
        url = 'http://aequilibrae.com/qgis'
        if sys.platform == 'darwin':  # in case of OS X
            subprocess.Popen(['open', url])
        else:
            webbrowser.open_new_tab(url)

    def run_log(self):
        dlg2 = LogDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def unload(self):
        del self.dock

    def trlt(self, message):
        # In the near future, we will use this function to automatically translate the AequilibraE menu
        # To any language we can get people to translate it to
        # return QCoreApplication.translate('AequilibraE', message)
        return message

    def initGui(self):
        pass

    def removes_temporary_files(self):
        # pass
        # Removes all the temporary files from previous uses
        p = tempfile.gettempdir() + "/aequilibrae_*"
        for f in glob.glob(p):
            try:
                os.unlink(f)
            except:
                pass

    def hide_info_pannel(self):
        if len(self.tabContents) > 0:
            if self.showing.isChecked():
                for v in self.tabContents:
                    self.projectManager.addTab(v[0], v[1])
            else:
                tab_count = 1
                for i in range(tab_count):
                    self.projectManager.removeTab(i)

    def run_load_project(self):
        formats = ["AequilibraE Project(*.sqlite)"]
        path, dtype = GetOutputFileName(
            QtWidgets.QDialog(),
            "AequilibraE Project",
            formats,
            ".sqlite",
            standard_path(),
        )

        # Cleans the project descriptor
        tab_count = 1
        for i in range(tab_count):
            self.projectManager.removeTab(i)

        if dtype is not None:
            self.contents = []
            self.showing.setVisible(True)
            self.project = Project()
            self.project.load(path)
            self.project.conn = qgis.utils.spatialite_connect(path)
            self.project.network.conn = self.project.conn

            uri = QgsDataSourceUri()
            uri.setDatabase(path)
            uri.setDataSource('', 'links', 'geometry')
            self.link_layer = QgsVectorLayer(uri.uri(), 'links', 'spatialite')
            QgsProject.instance().addMapLayer(self.link_layer)

            uri.setDataSource('', 'nodes', 'geometry')
            self.node_layer = QgsVectorLayer(uri.uri(), 'nodes', 'spatialite')
            QgsProject.instance().addMapLayer(self.node_layer)

            descr = QWidget()
            descrlayout = QVBoxLayout()
            # We create a tab with the main description of the project
            p1 = QLabel('Project: {}'.format(path))
            p2 = QLabel('Modes: {}'.format(', '.join(
                self.project.network.modes())))
            p3 = QLabel('Total Links: {:,}'.format(
                self.project.network.count_links()))
            p4 = QLabel('Total Nodes: {:,}'.format(
                self.project.network.count_nodes()))

            for p in [p1, p2, p3, p4]:
                descrlayout.addWidget(p)

            descr.setLayout(descrlayout)
            self.tabContents = [(descr, "Project")]
            self.projectManager.addTab(descr, "Project")

    def run_change_parameters(self):
        dlg2 = ParameterDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_about(self):
        dlg2 = AboutDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_load_matrices(self):
        dlg2 = LoadMatrixDialog(self.iface,
                                sparse=True,
                                multiple=True,
                                single_use=False)
        dlg2.show()
        dlg2.exec_()

    def run_load_database(self):
        dlg2 = LoadDatasetDialog(self.iface, single_use=False)
        dlg2.show()
        dlg2.exec_()

    def run_display_aequilibrae_formats(self):
        dlg2 = DisplayAequilibraEFormatsDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def install_extra_packages(self):
        dlg2 = DownloadExtraPackages(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_binary_download(self):
        dlg2 = BinaryDownloaderDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    # run method that calls the network preparation section of the code
    def run_net_prep(self):
        dlg2 = NetworkPreparationDialog(self.iface)
        dlg2.show()
        dlg2.exec_()
        # If we wanted modal, we would eliminate the dlg2.show()

    # run method that calls the network preparation section of the code
    def run_create_transponet(self):
        if no_binary:
            self.message_binary()
        else:
            dlg2 = CreatesTranspoNetDialog(self.iface)
            dlg2.show()
            dlg2.exec_()
        # If we wanted modal, we would eliminate the dlg2.show()

    def run_add_connectors(self):
        dlg2 = AddConnectorsDialog(self.iface, self.project)
        dlg2.show()
        dlg2.exec_()

    def run_distribution_models(self):
        dlg2 = DistributionModelsDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_shortest_path(self):
        if no_binary:
            self.message_binary()
        else:
            if self.project is None:
                self.show_message_no_project()
            else:
                dlg2 = ShortestPathDialog(self.iface, self.project,
                                          self.link_layer, self.node_layer)
                dlg2.show()
                dlg2.exec_()

    def run_dist_matrix(self):
        if no_binary:
            self.message_binary()
        else:
            if self.project is None:
                self.show_message_no_project()
            else:
                dlg2 = ImpedanceMatrixDialog(self.iface, self.project,
                                             self.link_layer)
                dlg2.show()
                dlg2.exec_()

    def run_traffic_assig(self):
        if no_binary:
            self.message_binary()
        else:
            if self.project is None:
                self.show_message_no_project()
            else:
                dlg2 = TrafficAssignmentDialog(self.iface, self.project,
                                               self.link_layer)
                dlg2.show()
                dlg2.exec_()

    def run_tsp(self):
        if self.project is None:
            self.show_message_no_project()
        else:
            dlg2 = TSPDialog(self.iface, self.project, self.link_layer,
                             self.node_layer)
            dlg2.show()
            dlg2.exec_()

    def run_import_gtfs(self):
        dlg2 = GtfsImportDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_project_from_osm(self):
        dlg2 = ProjectFromOSMDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_simple_tag(self):
        dlg2 = SimpleTagDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_lcd(self):
        dlg2 = LeastCommonDenominatorDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_dlines(self):
        if no_binary:
            self.message_binary()
        else:
            dlg2 = DesireLinesDialog(self.iface)
            dlg2.show()
            dlg2.exec_()

    def run_bandwidth(self):
        dlg2 = CreateBandwidthsDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def run_scenario_comparison(self):
        dlg2 = CompareScenariosDialog(self.iface)
        dlg2.show()
        dlg2.exec_()

    def message_binary(self):
        qgis.utils.iface.messageBar().pushMessage(
            "Binary Error: ",
            "Please download it from the repository using the downloader from the menu",
            level=3)

    def show_message_no_project(self):
        self.iface.messageBar().pushMessage("Error",
                                            "You need to load a project first",
                                            level=3,
                                            duration=10)
示例#2
0
class DebuggerWidget(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setWindowTitle("First Aid - Debugger")

        self.text_edits = {}  # fully expanded path of the file -> associated SourceWidget
        self.toolbar = self.addToolBar("General")
        self.toolbar.setObjectName("ToolbarGeneral")

        self.tab_widget = QTabWidget()
        self.tab_widget.setTabsClosable(True)
        self.tab_widget.tabCloseRequested.connect(self.on_tab_close_requested)
        self.tab_widget.currentChanged.connect(self.on_pos_changed)

        self.setCentralWidget(self.tab_widget)

        _icon = lambda x: QIcon(os.path.join(os.path.dirname(__file__), "icons", x + ".svg"))

        self.action_load = self.toolbar.addAction(_icon("folder-outline"), "Load Python file (Ctrl+O)", self.on_load)
        self.action_load.setShortcut("Ctrl+O")
        self.action_run = self.toolbar.addAction(_icon("run"), "Run Python file (Ctrl+R)", self.on_run)
        self.action_run.setShortcut("Ctrl+R")
        self.action_bp = self.toolbar.addAction(_icon("record"), "Toggle breakpoint (F9)", self.on_toggle_breakpoint)
        self.action_bp.setShortcut("F9")
        self.toolbar.addSeparator()
        self.action_continue = self.toolbar.addAction(_icon("play"), "Continue (F5)", self.on_continue)
        self.action_continue.setShortcut("F5")
        self.action_step_into = self.toolbar.addAction(_icon("debug-step-into"), "Step into (F11)", self.on_step_into)
        self.action_step_into.setShortcut("F11")
        self.action_step_over = self.toolbar.addAction(_icon("debug-step-over"), "Step over (F10)", self.on_step_over)
        self.action_step_over.setShortcut("F10")
        self.action_step_out = self.toolbar.addAction(_icon("debug-step-out"), "Step out (Shift+F11)", self.on_step_out)
        self.action_step_out.setShortcut("Shift+F11")
        self.action_run_to_cursor = self.toolbar.addAction(_icon("cursor-default-outline"), "Run to cursor (Ctrl+F10)",
                                                           self.on_run_to_cursor)
        self.action_run_to_cursor.setShortcut("Ctrl+F10")

        self.vars_view = VariablesView()
        self.frames_view = FramesView()

        self.dock_frames = QDockWidget("Frames", self)
        self.dock_frames.setObjectName("DockFrames")
        self.dock_frames.setWidget(self.frames_view)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_frames)

        self.dock_vars = QDockWidget("Variables", self)
        self.dock_vars.setObjectName("DockVariables")
        self.dock_vars.setWidget(self.vars_view)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_vars)

        self.resize(800, 800)

        self.debugger = Debugger(self)

        self.update_buttons()

        settings = QSettings()
        self.restoreGeometry(settings.value("/plugins/firstaid/debugger-geometry", b''))
        self.restoreState(settings.value("/plugins/firstaid/debugger-windowstate", b''))

        filenames = settings.value("/plugins/firstaid/debugger-files", [])
        if filenames is None:
            filenames = []

        # load files from previous session
        for filename in filenames:
            self.load_file(filename)

        if self.tab_widget.count() > 1:
            self.tab_widget.setCurrentIndex(0)

        # start tracing
        self.start_tracing()

    def start_tracing(self):
        """ called from constructor or when the debugger window is opened again """
        sys.settrace(self.debugger.trace_function)

    def closeEvent(self, event):

        # disable tracing
        sys.settrace(None)

        settings = QSettings()
        settings.setValue("/plugins/firstaid/debugger-geometry", self.saveGeometry())
        settings.setValue("/plugins/firstaid/debugger-windowstate", self.saveState())

        filenames = list(self.text_edits.keys())
        settings.setValue("/plugins/firstaid/debugger-files", filenames)

        QMainWindow.closeEvent(self, event)

    def load_file(self, filename):
        filename = os.path.normpath(os.path.realpath(filename))

        if filename in self.text_edits:
            self.switch_to_file(filename)
            return  # already there...
        try:
            self.text_edits[filename] = SourceWidget(filename)
        except IOError:
            # TODO: display warning we failed to read the file
            return
        tab_text = os.path.basename(filename)
        self.tab_widget.addTab(self.text_edits[filename], tab_text)
        self.tab_widget.setTabToolTip(self.tab_widget.count() - 1, filename)
        self.tab_widget.setCurrentWidget(self.text_edits[filename])
        self.text_edits[filename].cursorPositionChanged.connect(self.on_pos_changed)
        self.on_pos_changed()

    def switch_to_file(self, filename):
        if filename in self.text_edits:
            self.tab_widget.setCurrentWidget(self.text_edits[filename])

    def unload_file(self, filename):
        for index in range(self.tab_widget.count()):
            if self.text_edits[filename] == self.tab_widget.widget(index):
                self.tab_widget.removeTab(index)
                del self.text_edits[filename]
                break

    def get_file_name(self, args):
        if isinstance(args, tuple):
            return args[0]
        elif isinstance(args, str):
            return args

        return ""


    def on_load(self):

        settings = QSettings()
        folder = settings.value("firstaid/lastFolder", '')

        args = QFileDialog.getOpenFileName(self, "Load", folder, "Python files (*.py)")
        filename = self.get_file_name(args)
        if not filename: return 

        settings.setValue("firstaid/lastFolder", os.path.dirname(filename))
        self.load_file(filename)

    def on_tab_close_requested(self, index):
        self.unload_file(self.tab_widget.widget(index).filename)

    def on_pos_changed(self):
        if not self.current_text_edit():
            self.statusBar().showMessage("[no file]")
            return
        c = self.current_text_edit().textCursor()
        line = c.blockNumber() + 1
        col = c.positionInBlock() + 1
        self.statusBar().showMessage("%d:%d" % (line, col))

    def on_run(self):
        globals = None
        locals = None
        if globals is None:
            import __main__
            globals = __main__.__dict__
        if locals is None:
            locals = globals
        execfile(self.tab_widget.currentWidget().filename, globals, locals)

    def current_text_edit(self):
        return self.tab_widget.currentWidget()

    def on_toggle_breakpoint(self):
        if self.current_text_edit():
            self.current_text_edit().toggle_breakpoint()

    def update_buttons(self):
        active = self.debugger.stopped
        self.action_step_into.setEnabled(active)
        self.action_step_over.setEnabled(active)
        self.action_step_out.setEnabled(active)
        self.action_run_to_cursor.setEnabled(active)
        self.action_continue.setEnabled(active)

    def on_step_into(self):
        self.debugger.stepping = True
        self.debugger.next_step = None
        self.debugger.ev_loop.exit(0)

    def on_step_over(self):
        self.debugger.stepping = True
        self.debugger.next_step = (
        'over', self.debugger.current_frame.f_code.co_filename, self.debugger.current_frame.f_lineno)
        self.debugger.ev_loop.exit(0)

    def on_step_out(self):
        self.debugger.stepping = True
        self.debugger.next_step = ('out', frame_depth(self.debugger.current_frame))
        self.debugger.ev_loop.exit(0)

    def on_run_to_cursor(self):
        self.debugger.stepping = True
        filename = self.tab_widget.currentWidget().filename
        line_no = self.tab_widget.currentWidget().textCursor().blockNumber() + 1
        self.debugger.next_step = ('at', filename, line_no)
        self.debugger.ev_loop.exit(0)

    def on_continue(self):
        self.debugger.stepping = False
        self.current_text_edit().debug_line = -1
        self.current_text_edit().update_highlight()
        self.vars_view.setVariables({})
        self.frames_view.setTraceback(None)
        self.debugger.ev_loop.exit(0)
示例#3
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()