Exemple #1
0
class ProviderToolBar(QToolBar):
    '''
    Widget to display the dataprovider status
    '''

    triggered = pyqtSignal(str)

    def __init__(self, parent=None):
        super(ProviderToolBar, self).__init__(parent)
        self.signalMapper = QSignalMapper(self)
        self.setMovable(True)
        self.setFloatable(True)
        self.actions = []
        self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.signalMapper.mapped['QString'].connect(self.triggered)

    def createAction(self, provider):
        icon = QIcon(':/plugins/PosiView/ledgreen.png')
        icon.addFile(':/plugins/PosiView/ledgrey.png', QSize(), QIcon.Disabled, QIcon.Off)
        action = QAction(icon, provider.name, None)
        button = QToolButton()
        button.setDefaultAction(action)
        action.setEnabled(False)
        provider.deviceConnected.connect(action.setEnabled)
        provider.deviceDisconnected.connect(action.setDisabled)
        self.signalMapper.setMapping(action, provider.name)
        action.triggered.connect(self.signalMapper.map)
        self.addAction(action)
        self.actions.append(action)
Exemple #2
0
class DocumentViewManager(QMainWindow):
    """
    MDI area for displaying supporting documents within a given context e.g.
    supporting documents for a specific household based on the lifetime of the
    'SourceDocumentManager' instance.
    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowFlags(Qt.Window)

        self._mdi_area = QMdiArea()
        self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self._mdi_area)
        # set the size of mid_area and DocumentViewManager based on the
        # screen size.
        screen = QDesktopWidget().availableGeometry()
        self._mdi_area.resize(screen.width() - 30, screen.height() - 80)
        self.resize(self._mdi_area.size())
        self._mdi_area.subWindowActivated.connect(self.update_actions)
        self._viewer_mapper = QSignalMapper(self)
        self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window)

        win_title = QApplication.translate("DocumentViewManager",
                                           "Document Viewer")
        self.setWindowTitle(win_title)
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.statusBar().showMessage(
            QApplication.translate("DocumentViewManager", "Ready"))
        self._doc_viewers = {}

        self._create_menu_actions()
        self.update_actions()

    def center(self):
        """
        Move the Document viewer to the center of the screen.
        """
        # Get the current screens' dimensions...
        screen = QDesktopWidget().availableGeometry()
        # ... and get this windows' dimensions
        mdi_area_size = self.frameGeometry()
        # The horizontal position
        hpos = (screen.width() - mdi_area_size.width()) / 2
        # vertical position
        vpos = (screen.height() - mdi_area_size.height()) / 2
        # repositions the window
        self.move(hpos, vpos)

    def _create_menu_actions(self):
        self._window_menu = self.menuBar().addMenu(
            QApplication.translate("DocumentViewManager", "&Windows"))

        self._close_act = QAction(
            QApplication.translate("DocumentViewManager", "Cl&ose"), self)
        self._close_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close the active document viewer"))
        self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow)

        self._close_all_act = QAction(
            QApplication.translate("DocumentViewManager", "Close &All"), self)
        self._close_all_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close all the document viewers"))
        self._close_all_act.triggered.connect(
            self._mdi_area.closeAllSubWindows)

        self._tile_act = QAction(
            QApplication.translate("DocumentViewManager", "&Tile"), self)
        self._tile_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Tile the document viewers"))
        self._tile_act.triggered.connect(self.tile_windows)

        self._cascade_act = QAction(
            QApplication.translate("DocumentViewManager", "&Cascade"), self)
        self._cascade_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Cascade the document viewers"))
        self._cascade_act.triggered.connect(self.cascade_windows)

        self._next_act = QAction(
            QApplication.translate("DocumentViewManager", "Ne&xt"), self)
        self._next_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the next document viewer"))
        self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow)

        self._previous_act = QAction(
            QApplication.translate("DocumentViewManager", "Pre&vious"), self)
        self._previous_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the previous document viewer"))
        self._previous_act.triggered.connect(
            self._mdi_area.activatePreviousSubWindow)

        self._separator_act = QAction(self)
        self._separator_act.setSeparator(True)

        self.update_window_menu()
        self._window_menu.aboutToShow.connect(self.update_window_menu)

    def cascade_windows(self):
        # Cascade document windows
        self._mdi_area.cascadeSubWindows()

    def tile_windows(self):
        # Arrange document windows to occupy the available space in mdi area
        self._mdi_area.tileSubWindows()

    def update_actions(self):
        if self._mdi_area.activeSubWindow():
            has_mdi_child = True

        else:
            has_mdi_child = False

        self._close_act.setEnabled(has_mdi_child)
        self._close_all_act.setEnabled(has_mdi_child)
        self._tile_act.setEnabled(has_mdi_child)
        self._cascade_act.setEnabled(has_mdi_child)
        self._previous_act.setEnabled(has_mdi_child)
        self._next_act.setEnabled(has_mdi_child)
        self._separator_act.setVisible(has_mdi_child)

    def update_window_menu(self):
        self._window_menu.clear()
        self._window_menu.addAction(self._close_act)
        self._window_menu.addAction(self._close_all_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._tile_act)
        self._window_menu.addAction(self._cascade_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._next_act)
        self._window_menu.addAction(self._previous_act)
        self._window_menu.addAction(self._separator_act)

        windows = self._mdi_area.subWindowList()
        self._separator_act.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            text = "%d. %s" % (i + 1, window.windowTitle())

            win_action = self._window_menu.addAction(text)
            win_action.setCheckable(True)
            win_action.setChecked(window is self._mdi_area.activeSubWindow())
            win_action.triggered.connect(self._viewer_mapper.map)
            self._viewer_mapper.setMapping(win_action, window)

    def load_viewer(self, document_widget, visible=True):
        """
        Open a new instance of the viewer or activate an existing one if the
        document had been previously loaded.
        :param document_widget: Contains all the necessary information required
        to load the specific document.
        :type document_widget: DocumentWidget
        :param visible: True to show the view manager after the viewer has
        been loaded, otherwise it will be the responsibility of the caller to
        enable visibility.
        :type visible: bool
        :returns: True if the document was successfully loaded, else False.
        :rtype: bool
        """
        doc_identifier = document_widget.file_identifier()

        if doc_identifier in self._doc_viewers:

            doc_sw = self._doc_viewers[doc_identifier]

            self._mdi_area.setActiveSubWindow(doc_sw)
            doc_sw.showNormal()

        else:
            abs_doc_path = self.absolute_document_path(document_widget)

            if not QFile.exists(abs_doc_path):
                msg = QApplication.translate(
                    "DocumentViewManager",
                    "The selected document does not exist."
                    "\nPlease check the supporting documents' "
                    "repository setting.")
                QMessageBox.critical(
                    self,
                    QApplication.translate("DocumentViewManager",
                                           "Invalid Document"), msg)

                return False

            file_info = QFileInfo(abs_doc_path)
            ext = file_info.suffix().lower()
            if ext == 'pdf':
                os.startfile(abs_doc_path)
                return True

            doc_viewer = self._create_viewer(document_widget)

            doc_viewer.load_document(abs_doc_path)

            self._doc_viewers[doc_identifier] = doc_viewer

            self._mdi_area.addSubWindow(doc_viewer)

            doc_viewer.show()

        if not self.isVisible() and visible:
            self.setVisible(True)

        if self.isMinimized():
            self.showNormal()

        self.center()

        return True

    def set_active_sub_window(self, viewer):
        if viewer:
            self._mdi_area.setActiveSubWindow(viewer)

    def absolute_document_path(self, document_widget):
        """
        Build the absolute document path using info from the document widget.
        :param document_widget: Instance of document widget.
        :return: Absolute path of the supporting document.
        :rtype: str
        """
        abs_path = ''

        file_manager = document_widget.fileManager
        if not file_manager is None:
            network_repository = file_manager.networkPath
            file_id = document_widget.file_identifier()
            source_entity = document_widget.doc_source_entity()
            profile_name = current_profile().name
            doc_type = document_widget.doc_type_value().lower().replace(
                ' ', '_')
            file_name, file_extension = guess_extension(
                document_widget.displayName())

            abs_path = network_repository + "/" + profile_name + '/' + \
                       str(source_entity) + "/" + str(doc_type) + "/" + \
                       str(file_id) + str(file_extension)

        return abs_path

    def reset(self):
        """
        Removes all document viewers in the view area.
        The QCloseEvent sent to each sub-window will decrement the register.
        """
        self._mdi_area.closeAllSubWindows()

    def _create_viewer(self, document_widget):
        """
        Creates a new instance of a document viewer.
        :param document_widget: Contains all
        the necessary information required
        to load the specific document.
        :return: Document viewer object
        :rtype: DocumentViewer
        """
        doc_viewer = DocumentViewer(self._mdi_area,
                                    document_widget.file_identifier())
        doc_viewer.setAttribute(Qt.WA_DeleteOnClose)
        doc_viewer.setWindowTitle(document_widget.displayName())

        # TODO: Incorporate logic for determining
        # TODO: viewer based on document type
        ph_viewer = PhotoViewer()

        # v_layout = QVBoxLayout()
        # v_layout.addWidget(ph_viewer)
        # doc_viewer.setLayout(v_layout)

        doc_viewer.set_view_widget(ph_viewer)

        doc_viewer.closed.connect(self._on_viewer_closed)

        return doc_viewer

    def remove_viewer(self, viewer_id):
        """
        Close and remove the viewer with the specified viewer ID.
        """
        if viewer_id in self._doc_viewers:
            viewer = self._doc_viewers[viewer_id]
            self._mdi_area.setActiveSubWindow(viewer)
            self._mdi_area.closeActiveSubWindow()

        self._on_viewer_closed(viewer_id)

    def _on_viewer_closed(self, file_id):
        """
        Slot raised when a document viewer is closed.
        """
        if file_id in self._doc_viewers:
            del self._doc_viewers[file_id]
Exemple #3
0
class ImportData(WIDGET, BASE):
    def __init__(self, parent=None):
        QWizard.__init__(self, parent)
        self.setupUi(self)

        self.btnSrcUp.setIcon(GuiUtils.get_icon('up.png'))
        self.btnSrcDown.setIcon(GuiUtils.get_icon('down.png'))
        self.btn_add_translator.setIcon(GuiUtils.get_icon('add.png'))
        self.btn_edit_translator.setIcon(GuiUtils.get_icon('edit.png'))
        self.btn_delete_translator.setIcon(GuiUtils.get_icon('remove.png'))
        self.btnDestUp.setIcon(GuiUtils.get_icon('up.png'))
        self.btnDestDown.setIcon(GuiUtils.get_icon('down.png'))

        self.curr_profile = current_profile()

        # Connect signals
        self.btnBrowseSource.clicked.connect(self.setSourceFile)
        self.lstDestTables.itemClicked.connect(self.destSelectChanged)
        self.btnSrcUp.clicked.connect(self.srcItemUp)
        self.btnSrcDown.clicked.connect(self.srcItemDown)
        self.btnSrcAll.clicked.connect(self.checkSrcItems)
        self.btnSrcNone.clicked.connect(self.uncheckSrcItems)
        self.btnDestUp.clicked.connect(self.targetItemUp)
        self.btnDestDown.clicked.connect(self.targetItemDown)
        self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged)
        self.lstTargetFields.currentRowChanged[int].connect(
            self.destRowChanged)
        self.lstTargetFields.currentRowChanged[int].connect(
            self._enable_disable_trans_tools)
        self.chk_virtual.toggled.connect(self._on_load_virtual_columns)

        # Data Reader
        self.dataReader = None

        # Init
        self.registerFields()

        # Geometry columns
        self.geomcols = []

        # Initialize value translators from definitions
        self._init_translators()

        # self._set_target_fields_stylesheet()

    def _init_translators(self):
        translator_menu = QMenu(self)

        self._trans_widget_mgr = TranslatorWidgetManager(self)
        self._trans_signal_mapper = QSignalMapper(self)

        for trans_name, config in ValueTranslatorConfig.translators.items():
            trans_action = QAction('{}...'.format(trans_name), translator_menu)

            self._trans_signal_mapper.setMapping(trans_action, trans_name)
            trans_action.triggered.connect(self._trans_signal_mapper.map)

            translator_menu.addAction(trans_action)

        if len(translator_menu.actions()) == 0:
            self.btn_add_translator.setEnabled(False)

        else:
            self.btn_add_translator.setMenu(translator_menu)

            self._trans_signal_mapper.mapped[str].connect(
                self._load_translator_dialog)

        self.btn_edit_translator.setEnabled(False)
        self.btn_delete_translator.setEnabled(False)

        self.btn_edit_translator.clicked.connect(self._on_edit_translator)
        self.btn_delete_translator.clicked.connect(self._on_delete_translator)

    def _load_translator_dialog(self, config_key):
        """
        Load translator dialog.
        """
        dest_column = self._selected_destination_column()
        src_column = self._selected_source_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            if trans_dlg is None:
                trans_config = ValueTranslatorConfig.translators.get(
                    config_key, None)

                # Safety precaution
                if trans_config is None: return

                try:
                    trans_dlg = trans_config.create(self,
                                                    self._source_columns(),
                                                    self.targetTab,
                                                    dest_column, src_column)

                except RuntimeError as re:
                    QMessageBox.critical(
                        self,
                        QApplication.translate('ImportData',
                                               'Value Translator'), str(re))

                    return

            self._handle_translator_dlg(dest_column, trans_dlg)

    def _handle_translator_dlg(self, key, dlg):
        if dlg.exec_() == QDialog.Accepted:
            self._trans_widget_mgr.add_widget(key, dlg)

        self._enable_disable_trans_tools()

    def _on_edit_translator(self):
        """
        Slot to load the translator widget specific for the selected column for editing.
        """
        dest_column = self._selected_destination_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            self._handle_translator_dlg(dest_column, trans_dlg)

    def _on_delete_translator(self):
        """
        Slot for deleting the translator widget for the selected column.
        """
        dest_column = self._selected_destination_column()

        self._delete_translator(dest_column)

    def _delete_translator(self, destination_column):
        if not destination_column:
            return

        res = self._trans_widget_mgr.remove_translator_widget(
            destination_column)

        self._enable_disable_trans_tools()

    def _enable_disable_trans_tools(self, index=-1):
        """
        Enable/disable appropriate value translator tools based on the selected
        column.
        """
        dest_column = self._selected_destination_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            if trans_dlg is None:
                self.btn_add_translator.setEnabled(True)
                self.btn_edit_translator.setEnabled(False)
                self.btn_delete_translator.setEnabled(False)

            else:
                self.btn_add_translator.setEnabled(False)
                self.btn_edit_translator.setEnabled(True)
                self.btn_delete_translator.setEnabled(True)

        else:
            self.btn_add_translator.setEnabled(False)
            self.btn_edit_translator.setEnabled(False)
            self.btn_delete_translator.setEnabled(False)

    def _selected_destination_column(self):
        dest_field_item = self.lstTargetFields.currentItem()

        if dest_field_item is None:
            return ""

        else:
            return dest_field_item.text()

    def _selected_source_column(self):
        src_field_item = self.lstSrcFields.currentItem()

        if src_field_item is None:
            return ""

        else:
            return src_field_item.text()

    def _set_target_fields_stylesheet(self):
        self.lstTargetFields.setStyleSheet(
            "QListWidget#lstTargetFields::item:selected"
            " { selection-background-color: darkblue }")

    def registerFields(self):
        # Register wizard fields
        pgSource = self.page(0)
        pgSource.registerField("srcFile*", self.txtDataSource)
        pgSource.registerField("typeText", self.rbTextType)
        pgSource.registerField("typeSpatial", self.rbSpType)

        # Destination table configuration
        destConf = self.page(1)
        destConf.registerField("optAppend", self.rbAppend)
        destConf.registerField("optOverwrite", self.rbOverwrite)
        destConf.registerField("tabIndex*", self.lstDestTables)
        destConf.registerField("geomCol", self.geomClm, "currentText",
                               QComboBox.currentIndexChanged[int])

    def initializePage(self, pageid):
        # Re-implementation of wizard page initialization
        if pageid == 1:
            # Reference to checked listwidget item representing table name
            self.destCheckedItem = None
            self.geomClm.clear()

            if self.field("typeText"):
                self.loadTables("textual")
                self.geomClm.setEnabled(False)

            elif self.field("typeSpatial"):
                self.loadTables("spatial")
                self.geomClm.setEnabled(True)

        if pageid == 2:
            self.lstSrcFields.clear()
            self.lstTargetFields.clear()
            self.assignCols()
            self._enable_disable_trans_tools()

    def _source_columns(self):
        return self.dataReader.getFields()

    def assignCols(self):
        # Load source and target columns respectively
        srcCols = self._source_columns()

        for c in srcCols:
            srcItem = QListWidgetItem(c, self.lstSrcFields)
            srcItem.setCheckState(Qt.Unchecked)
            srcItem.setIcon(GuiUtils.get_icon("column.png"))
            self.lstSrcFields.addItem(srcItem)

        # Destination Columns
        tabIndex = int(self.field("tabIndex"))
        self.targetTab = self.destCheckedItem.text()
        targetCols = table_column_names(self.targetTab, False, True)

        # Remove geometry columns in the target columns list
        for gc in self.geomcols:
            colIndex = getIndex(targetCols, gc)
            if colIndex != -1:
                targetCols.remove(gc)

        # Remove 'id' column if there
        id_idx = getIndex(targetCols, 'id')
        if id_idx != -1:
            targetCols.remove('id')

        self._add_target_table_columns(targetCols)

    def _add_target_table_columns(self, items, style=False):
        for item in items:
            list_item = QListWidgetItem(item)

            if style:
                color = QColor(0, 128, 255)
                list_item.setTextColor(color)

            self.lstTargetFields.addItem(list_item)

    def _on_load_virtual_columns(self, state):
        """
        Load/unload relationships in the list of destination table columns.
        """
        virtual_columns = self.dataReader.entity_virtual_columns(
            self.targetTab)

        if state:
            if len(virtual_columns) == 0:
                msg = QApplication.translate(
                    "ImportData",
                    "There are no virtual columns for the specified table.")
                QMessageBox.warning(
                    self, QApplication.translate('ImportData', 'Import Data'),
                    msg)
                self.chk_virtual.setChecked(False)

                return

            self._add_target_table_columns(virtual_columns, True)

        else:
            self._remove_destination_table_fields(virtual_columns)

    def _remove_destination_table_fields(self, fields):
        """Remove the specified columns from the destination view."""
        for f in fields:
            list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString)
            if len(list_items) > 0:
                list_item = list_items[0]

                row = self.lstTargetFields.row(list_item)

                rem_item = self.lstTargetFields.takeItem(row)
                del rem_item

                # Delete translator if already defined for the given column
                self._delete_translator(f)

    def loadGeomCols(self, table):
        # Load geometry columns based on the selected table
        self.geomcols = table_column_names(table, True, True)
        self.geomClm.clear()
        self.geomClm.addItems(self.geomcols)

    def loadTables(self, type):
        # Load textual or spatial tables
        self.lstDestTables.clear()
        tables = None
        if type == "textual":
            tables = profile_user_tables(self.curr_profile, False, True)

        elif type == "spatial":
            tables = profile_spatial_tables(self.curr_profile)
        if tables is not None:
            for t in tables:
                tabItem = QListWidgetItem(t, self.lstDestTables)
                tabItem.setCheckState(Qt.Unchecked)
                tabItem.setIcon(GuiUtils.get_icon("table.png"))
                self.lstDestTables.addItem(tabItem)

    def validateCurrentPage(self):
        # Validate the current page before proceeding to the next one
        validPage = True

        if not QFile.exists(str(self.field("srcFile"))):
            self.ErrorInfoMessage("The specified source file does not exist.")
            validPage = False

        else:
            if self.dataReader:
                self.dataReader.reset()
            self.dataReader = OGRReader(str(self.field("srcFile")))

            if not self.dataReader.isValid():
                self.ErrorInfoMessage("The source file could not be opened."
                                      "\nPlease check is the given file type "
                                      "is supported")
                validPage = False

        if self.currentId() == 1:
            if self.destCheckedItem == None:
                self.ErrorInfoMessage("Please select the destination table.")
                validPage = False

        if self.currentId() == 2:
            validPage = self.execImport()

        return validPage

    def setSourceFile(self):
        # Set the file path to the source file
        imageFilters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)"
        sourceFile, _ = QFileDialog.getOpenFileName(self, "Select Source File",
                                                    vectorFileDir(),
                                                    imageFilters)
        if sourceFile:
            self.txtDataSource.setText(sourceFile)

    def getSrcDestPairs(self):
        # Return the matched source and destination columns
        srcDest = {}
        for l in range(self.lstTargetFields.count()):
            if l < self.lstSrcFields.count():
                srcItem = self.lstSrcFields.item(l)
                if srcItem.checkState() == Qt.Checked:
                    destItem = self.lstTargetFields.item(l)
                    srcDest[srcItem.text()] = destItem.text()

        return srcDest

    def execImport(self):
        # Initiate the import process
        success = False
        matchCols = self.getSrcDestPairs()

        # Specify geometry column
        geom_column = None

        if self.field("typeSpatial"):
            geom_column = self.field("geomCol")

        # Ensure that user has selected at least one column if it is a
        # non-spatial table
        if len(matchCols) == 0:
            self.ErrorInfoMessage("Please select at least one source column.")
            return success

        value_translator_manager = self._trans_widget_mgr.translator_manager()

        try:
            if self.field("optOverwrite"):
                entity = self.curr_profile.entity_by_name(self.targetTab)
                dependencies = entity.dependencies()
                view_dep = dependencies['views']
                entity_dep = [e.name for e in entity.children()]
                entities_dep_str = ', '.join(entity_dep)
                views_dep_str = ', '.join(view_dep)

                if len(entity_dep) > 0 or len(view_dep) > 0:
                    del_msg = QApplication.translate(
                        'ImportData',
                        "Overwriting existing records will permanently \n"
                        "remove records from other tables linked to the \n"
                        "records. The following tables will be affected."
                        "\n{}\n{}"
                        "\nClick Yes to proceed importing or No to cancel.".
                        format(entities_dep_str, views_dep_str))
                    del_result = QMessageBox.critical(
                        self,
                        QApplication.translate(
                            "ImportData", "Overwrite Import Data Warning"),
                        del_msg, QMessageBox.Yes | QMessageBox.No)

                    if del_result == QMessageBox.Yes:
                        self.dataReader.featToDb(
                            self.targetTab,
                            matchCols,
                            False,
                            self,
                            geom_column,
                            translator_manager=value_translator_manager)
                        # Update directory info in the registry
                        setVectorFileDir(self.field("srcFile"))

                        self.InfoMessage(
                            "All features have been imported successfully!")
                        success = True

                    else:
                        success = False
            else:
                self.dataReader.featToDb(
                    self.targetTab,
                    matchCols,
                    True,
                    self,
                    geom_column,
                    translator_manager=value_translator_manager)
                self.InfoMessage(
                    "All features have been imported successfully!")
                # Update directory info in the registry
                setVectorFileDir(self.field("srcFile"))
                success = True
        except:
            self.ErrorInfoMessage(str(sys.exc_info()[1]))

        return success

    def _clear_dest_table_selections(self, exclude=None):
        # Clears checked items in destination table list view
        if exclude is None:
            exclude = []

        for i in range(self.lstDestTables.count()):
            item = self.lstDestTables.item(i)
            if item.checkState() == Qt.Checked and not item.text() in exclude:
                item.setCheckState(Qt.Unchecked)

    def destSelectChanged(self, item):
        """
        Handler when a list widget item is clicked,
        clears previous selections
        """
        if not self.destCheckedItem is None:
            if item.checkState() == Qt.Checked:
                self.destCheckedItem.setCheckState(Qt.Unchecked)
            else:
                self.destCheckedItem = None

        if item.checkState() == Qt.Checked:
            self.destCheckedItem = item

            # Ensure other selected items have been cleared
            self._clear_dest_table_selections(exclude=[item.text()])

            # Load geometry columns if selection is a spatial table
            if self.field("typeSpatial"):
                self.loadGeomCols(item.text())

    def syncRowSelection(self, srcList, destList):
        """
        Sync the selection of an srcList item to the corresponding one in
        the destination column list.
        """
        if (srcList.currentRow() + 1) <= destList.count():
            destList.setCurrentRow(srcList.currentRow())

    def sourceRowChanged(self):
        # Slot when the source list's current row changes
        self.syncRowSelection(self.lstSrcFields, self.lstTargetFields)

    def destRowChanged(self):
        # Slot when the destination list's current row changes
        self.syncRowSelection(self.lstTargetFields, self.lstSrcFields)

    def itemUp(self, listWidget):
        # Moves the selected item in the list widget one level up
        curIndex = listWidget.currentRow()
        curItem = listWidget.takeItem(curIndex)
        listWidget.insertItem(curIndex - 1, curItem)
        listWidget.setCurrentRow(curIndex - 1)

    def itemDown(self, listWidget):
        # Moves the selected item in the list widget one level down
        curIndex = listWidget.currentRow()
        curItem = listWidget.takeItem(curIndex)
        listWidget.insertItem(curIndex + 1, curItem)
        listWidget.setCurrentRow(curIndex + 1)

    def checkAllItems(self, listWidget, state):
        # Checks all items in the list widget
        for l in range(listWidget.count()):
            item = listWidget.item(l)
            if state:
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)

    def checkSrcItems(self):
        # Slot for checking all source table columns
        self.checkAllItems(self.lstSrcFields, True)

    def uncheckSrcItems(self):
        # Slot for unchecking all source table columns
        self.checkAllItems(self.lstSrcFields, False)

    def srcItemUp(self):
        # Slot for moving source list item up
        self.itemUp(self.lstSrcFields)

    def srcItemDown(self):
        # Slot for moving source list item down
        self.itemDown(self.lstSrcFields)

    def targetItemUp(self):
        # Slot for moving target item up
        self.itemUp(self.lstTargetFields)

    def targetItemDown(self):
        # Slot for moving target item down
        self.itemDown(self.lstTargetFields)

    def keyPressEvent(self, e):
        """
        Override method for preventing the dialog from
        closing itself when the escape key is hit
        """
        if e.key() == Qt.Key_Escape:
            pass

    def InfoMessage(self, message):
        # Information message box
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(message)
        msg.exec_()

    def ErrorInfoMessage(self, message):
        # Error Message Box
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setText(message)
        msg.exec_()
Exemple #4
0
class ImportData(WIDGET, BASE):
    ROLE_TABLE_NAME = Qt.UserRole + 1

    def __init__(self, parent=None):
        QWizard.__init__(self, parent)
        self.setupUi(self)

        self.btnSrcUp.setIcon(GuiUtils.get_icon('up.png'))
        self.btnSrcDown.setIcon(GuiUtils.get_icon('down.png'))
        self.btn_add_translator.setIcon(GuiUtils.get_icon('add.png'))
        self.btn_edit_translator.setIcon(GuiUtils.get_icon('edit.png'))
        self.btn_delete_translator.setIcon(GuiUtils.get_icon('remove.png'))
        self.btnDestUp.setIcon(GuiUtils.get_icon('up.png'))
        self.btnDestDown.setIcon(GuiUtils.get_icon('down.png'))

        self.curr_profile = current_profile()

        # Connect signals
        self.btnBrowseSource.clicked.connect(self.setSourceFile)
        self.lstDestTables.itemClicked.connect(self.destSelectChanged)
        self.btnSrcUp.clicked.connect(self.srcItemUp)
        self.btnSrcDown.clicked.connect(self.srcItemDown)
        self.btnSrcAll.clicked.connect(self.checkSrcItems)
        self.btnSrcNone.clicked.connect(self.uncheckSrcItems)
        self.btnDestUp.clicked.connect(self.targetItemUp)
        self.btnDestDown.clicked.connect(self.targetItemDown)
        self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged)
        self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged)
        self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools)
        self.chk_virtual.toggled.connect(self._on_load_virtual_columns)
        self.button_save_configuration.clicked.connect(self._save_column_mapping)
        self.button_load_configuration.clicked.connect(self._load_column_mapping)

        self.targetTab = ''

        self.import_was_successful = False
        self.restored_config = {}

        # Data Reader
        self.dataReader = None

        # Init
        self.registerFields()

        # Geometry columns
        self.geom_cols = []

        # Initialize value translators from definitions
        self._init_translators()

        # self._set_target_fields_stylesheet()

        if os.path.exists(BACKUP_IMPORT_CONFIG_PATH):
            self._restore_previous_configuration()

    def closeEvent(self, event):
        self._save_if_unfinished()
        super().closeEvent(event)

    def reject(self):
        self._save_if_unfinished()
        super().reject()

    def accept(self):
        super().accept()
        self._save_if_unfinished()

    def _save_if_unfinished(self):
        """
        If an unfinished import is in progress, then automatically save the settings to a hidden file
        """
        if self.import_was_successful:
            return

        if not self.field("srcFile"):
            # user hasn't even started the process by picking a file, so we've nothing of value to save...
            return

        current_config = self._get_column_config()
        with open(BACKUP_IMPORT_CONFIG_PATH, 'wt') as f:
            f.write(json.dumps(current_config, indent=4))

    def _restore_previous_configuration(self):
        """
        Loads the previously unfinished configuration
        """
        with open(BACKUP_IMPORT_CONFIG_PATH, 'rt') as f:
            config = json.loads(''.join(f.readlines()))

        os.remove(BACKUP_IMPORT_CONFIG_PATH)
        if not config:
            return

        if QMessageBox.question(self,
                                self.tr('Import Data'),
                                self.tr(
                                    'A previously incomplete or unsuccessful import was detected. Would you like to restore the previous configuration and retry?'),
                                QMessageBox.Yes | QMessageBox.No,
                                QMessageBox.Yes
                                ) != QMessageBox.Yes:
            return

        self.restored_config = config
        self.txtDataSource.setText(self.restored_config.get('source_file'))

        self.rbTextType.setChecked(self.restored_config.get('is_text', True))
        self.rbSpType.setChecked(self.restored_config.get('is_spatial', False))

    def _init_translators(self):
        translator_menu = QMenu(self)

        self._trans_widget_mgr = TranslatorWidgetManager(self)
        self._trans_signal_mapper = QSignalMapper(self)

        for trans_name, config in ValueTranslatorConfig.translators.items():
            trans_action = QAction('{}...'.format(trans_name),
                                   translator_menu
                                   )

            self._trans_signal_mapper.setMapping(trans_action, trans_name)
            trans_action.triggered.connect(self._trans_signal_mapper.map)

            translator_menu.addAction(trans_action)

        if len(translator_menu.actions()) == 0:
            self.btn_add_translator.setEnabled(False)

        else:
            self.btn_add_translator.setMenu(translator_menu)

            self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog)

        self.btn_edit_translator.setEnabled(False)
        self.btn_delete_translator.setEnabled(False)

        self.btn_edit_translator.clicked.connect(self._on_edit_translator)
        self.btn_delete_translator.clicked.connect(self._on_delete_translator)

    def _load_translator_dialog(self, config_key):
        """
        Load translator dialog.
        """
        dest_column = self._selected_destination_column()
        src_column = self._selected_source_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            if trans_dlg is None:
                trans_config = ValueTranslatorConfig.translators.get(config_key, None)

                # Safety precaution
                if trans_config is None:
                    return

                try:
                    trans_dlg = trans_config.create(
                        self,
                        self._source_columns(),
                        self.targetTab,
                        dest_column,
                        src_column
                    )

                except RuntimeError as re:
                    QMessageBox.critical(
                        self,
                        QApplication.translate(
                            'ImportData',
                            'Value Translator'
                        ),
                        str(re)
                    )

                    return

            self._handle_translator_dlg(dest_column, trans_dlg)

    def _handle_translator_dlg(self, key, dlg):
        if dlg.exec_() == QDialog.Accepted:
            self._trans_widget_mgr.add_widget(key, dlg)

        self._enable_disable_trans_tools()

    def _on_edit_translator(self):
        """
        Slot to load the translator widget specific for the selected column for editing.
        """
        dest_column = self._selected_destination_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            self._handle_translator_dlg(dest_column, trans_dlg)

    def _on_delete_translator(self):
        """
        Slot for deleting the translator widget for the selected column.
        """
        dest_column = self._selected_destination_column()

        self._delete_translator(dest_column)

    def _delete_translator(self, destination_column):
        if not destination_column:
            return

        _ = self._trans_widget_mgr.remove_translator_widget(destination_column)

        self._enable_disable_trans_tools()

    def _enable_disable_trans_tools(self, index=-1):
        """
        Enable/disable appropriate value translator tools based on the selected
        column.
        """
        dest_column = self._selected_destination_column()

        if dest_column:
            # Check if there is an existing dialog in the manager
            trans_dlg = self._trans_widget_mgr.translator_widget(dest_column)

            if trans_dlg is None:
                self.btn_add_translator.setEnabled(True)
                self.btn_edit_translator.setEnabled(False)
                self.btn_delete_translator.setEnabled(False)

            else:
                self.btn_add_translator.setEnabled(False)
                self.btn_edit_translator.setEnabled(True)
                self.btn_delete_translator.setEnabled(True)

        else:
            self.btn_add_translator.setEnabled(False)
            self.btn_edit_translator.setEnabled(False)
            self.btn_delete_translator.setEnabled(False)

    def _selected_destination_column(self):
        dest_field_item = self.lstTargetFields.currentItem()

        if dest_field_item is None:
            return ""

        else:
            return dest_field_item.text()

    def _selected_source_column(self):
        src_field_item = self.lstSrcFields.currentItem()

        if src_field_item is None:
            return ""

        else:
            return src_field_item.text()

    def _set_target_fields_stylesheet(self):
        self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected"
                                           " { selection-background-color: darkblue }")

    def registerFields(self):
        # Register wizard fields
        pgSource = self.page(0)
        pgSource.registerField("srcFile*", self.txtDataSource)
        pgSource.registerField("typeText", self.rbTextType)
        pgSource.registerField("typeSpatial", self.rbSpType)

        # Destination table configuration
        destConf = self.page(1)
        destConf.registerField("optAppend", self.rbAppend)
        destConf.registerField("optOverwrite", self.rbOverwrite)
        destConf.registerField("tabIndex*", self.lstDestTables)
        destConf.registerField("geomCol", self.geomClm, "currentText", QComboBox.currentIndexChanged[int])

    def initializePage(self, page_id):
        # Re-implementation of wizard page initialization
        if page_id == 1:
            # Reference to checked list widget item representing table name
            self.geomClm.clear()

            if self.field("typeText"):
                self.load_tables_of_type("textual", self.restored_config.get('dest_table'))
                self.geomClm.setEnabled(False)
            elif self.field("typeSpatial"):
                self.load_tables_of_type("spatial", self.restored_config.get('dest_table'))

                if self.selected_destination_table():
                    self.loadGeomCols(self.selected_destination_table())
                self.geomClm.setEnabled(True)

            if self.restored_config:
                if self.restored_config.get('overwrite', False):
                    self.rbOverwrite.setChecked(True)
                else:
                    self.rbAppend.setChecked(True)

        if page_id == 2:
            self.lstSrcFields.clear()
            self.lstTargetFields.clear()

            self.assignCols()
            if self.restored_config and self.selected_destination_table() == self.restored_config.get('dest_table'):
                self._restore_column_config(self.restored_config)

            self._enable_disable_trans_tools()

    def _source_columns(self):
        return self.dataReader.getFields()

    @staticmethod
    def format_name_for_matching(name: str) -> str:
        """
        Returns a column name formatted for tolerant matching, e.g. we ignore
        case, _ characters, etc
        """
        return name.strip().lower().replace(' ', '').replace('_', '').replace('-', '')

    @staticmethod
    def names_are_matching(name1: str, name2: str) -> bool:
        """
        Returns True if the specified column name pairs should be considered a tolerant
        match
        """
        return ImportData.format_name_for_matching(name1) == ImportData.format_name_for_matching(name2)

    def selected_destination_table(self) -> Optional[str]:
        """
        Returns the selected (checked) destination table
        """
        for i in range(self.lstDestTables.count()):
            item = self.lstDestTables.item(i)
            if item.checkState() == Qt.Checked:
                return item.data(ImportData.ROLE_TABLE_NAME)

        return None

    def assignCols(self):
        # Load source and target columns respectively
        source_columns = self._source_columns()

        # Destination Columns
        self.targetTab = self.selected_destination_table()
        target_columns = table_column_names(self.targetTab, False, True)

        # Remove geometry columns and 'id' column in the target columns list
        target_columns = [c for c in target_columns if c not in self.geom_cols and c != 'id']

        # now synchronize the lists, as much as possible
        # this consists of moving columns with matching names in the source and target lists to the same
        # placement at the top of the lists, and filtering out lists of remaining unmatched columns

        matched_source_columns = []
        unmatched_source_columns = source_columns[:]
        matched_target_columns = []
        unmatched_target_columns = target_columns[:]
        for source in source_columns:
            for target in unmatched_target_columns:
                if ImportData.names_are_matching(source, target):
                    matched_source_columns.append(source)
                    unmatched_source_columns = [c for c in unmatched_source_columns if c != source]
                    matched_target_columns.append(target)
                    unmatched_target_columns = [c for c in unmatched_target_columns if c != target]
                    break

        # any matching columns get added to the start of the lists, and unmatched get added
        # to the end of the list
        for c in matched_source_columns + unmatched_source_columns:
            src_item = QListWidgetItem(c, self.lstSrcFields)
            # automatically check any columns we could match
            src_item.setCheckState(Qt.Checked if c in matched_source_columns else Qt.Unchecked)
            src_item.setIcon(GuiUtils.get_icon("column.png"))
            self.lstSrcFields.addItem(src_item)

        self._add_target_table_columns(matched_target_columns + unmatched_target_columns)

    def _add_target_table_columns(self, items, style=False):
        for item in items:
            list_item = QListWidgetItem(item)

            if style:
                color = QColor(0, 128, 255)
                list_item.setTextColor(color)

            self.lstTargetFields.addItem(list_item)

    def _on_load_virtual_columns(self, state):
        """
        Load/unload relationships in the list of destination table columns.
        """
        virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab)

        if state:
            if len(virtual_columns) == 0:
                msg = QApplication.translate("ImportData",
                                             "There are no virtual columns for the specified table.")
                QMessageBox.warning(
                    self,
                    QApplication.translate(
                        'ImportData',
                        'Import Data'
                    ),
                    msg
                )
                self.chk_virtual.setChecked(False)

                return

            self._add_target_table_columns(virtual_columns, True)

        else:
            self._remove_destination_table_fields(virtual_columns)

    def _remove_destination_table_fields(self, fields):
        """Remove the specified columns from the destination view."""
        for f in fields:
            list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString)
            if len(list_items) > 0:
                list_item = list_items[0]

                row = self.lstTargetFields.row(list_item)

                rem_item = self.lstTargetFields.takeItem(row)
                del rem_item

                # Delete translator if already defined for the given column
                self._delete_translator(f)

    def loadGeomCols(self, table):
        # Load geometry columns based on the selected table
        self.geom_cols = table_column_names(table, True, True)
        self.geomClm.clear()
        self.geomClm.addItems(self.geom_cols)

        if self.restored_config.get('geom_column'):
            self.geomClm.setCurrentIndex(self.geomClm.findText(self.restored_config['geom_column']))

    def load_tables_of_type(self, type: str, initial_selection: Optional[str] = None):
        """
        Load textual or spatial tables

        If initial_selection is specified then that table will be initially checked
        """
        self.lstDestTables.clear()
        tables = None
        if type == "textual":
            tables = profile_user_tables(self.curr_profile, False, True, include_read_only=False)
        elif type == "spatial":
            tables = profile_spatial_tables(self.curr_profile, include_read_only=False)

        if tables is not None:
            for table_name, table_label in tables.items():
                table_item = QListWidgetItem(table_label, self.lstDestTables)
                table_item.setData(ImportData.ROLE_TABLE_NAME, table_name)
                if initial_selection:
                    table_item.setCheckState(
                        Qt.Checked if ImportData.names_are_matching(table_name, initial_selection) else Qt.Unchecked)
                else:
                    table_item.setCheckState(Qt.Unchecked)
                table_item.setIcon(GuiUtils.get_icon("table.png"))
                self.lstDestTables.addItem(table_item)

    def validateCurrentPage(self):
        # Validate the current page before proceeding to the next one
        validPage = True

        if not QFile.exists(str(self.field("srcFile"))):
            self.show_error_message("The specified source file does not exist.")
            validPage = False

        else:
            if self.dataReader:
                self.dataReader.reset()
            self.dataReader = OGRReader(str(self.field("srcFile")))

            if not self.dataReader.isValid():
                self.show_error_message("The source file could not be opened."
                                        "\nPlease check is the given file type "
                                        "is supported")
                validPage = False

        if self.currentId() == 1:
            if self.selected_destination_table() is None:
                self.show_error_message("Please select the destination table.")
                validPage = False

        if self.currentId() == 2:
            self.import_was_successful = self.execImport()
            validPage = self.import_was_successful

        return validPage

    def setSourceFile(self):
        # Set the file path to the source file
        filters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)"
        sourceFile, _ = QFileDialog.getOpenFileName(self, "Select Source File", vectorFileDir(), filters)
        if sourceFile:
            self.txtDataSource.setText(sourceFile)

    def get_source_dest_pairs(self) -> dict:
        """
        Builds a dictionary of source field to destination field name
        """
        mapping = {}
        for target_row in range(self.lstTargetFields.count()):
            if target_row < self.lstSrcFields.count():
                source_item = self.lstSrcFields.item(target_row)
                if source_item.checkState() == Qt.Checked:
                    dest_item = self.lstTargetFields.item(target_row)
                    mapping[source_item.text()] = dest_item.text()

        return mapping

    def set_source_dest_pairs(self, mapping: dict):
        """
        Sets the source to destination pairs for fields to match

        Any existing mapping will be cleared
        """
        self.uncheckSrcItems()

        index = 0
        for target_source, target_dest in mapping.items():
            # move source row up
            for source_row in range(self.lstSrcFields.count()):
                if ImportData.names_are_matching(target_source, self.lstSrcFields.item(source_row).text()):
                    source_item = self.lstSrcFields.takeItem(source_row)
                    self.lstSrcFields.insertItem(index, source_item)
                    source_item.setCheckState(Qt.Checked)

            # move target row up
            for dest_row in range(self.lstTargetFields.count()):
                if ImportData.names_are_matching(target_dest, self.lstTargetFields.item(dest_row).text()):
                    dest_item = self.lstTargetFields.takeItem(dest_row)
                    self.lstTargetFields.insertItem(index, dest_item)

            index += 1

    def execImport(self):
        # Initiate the import process
        success = False
        matchCols = self.get_source_dest_pairs()

        # Specify geometry column
        geom_column = None

        if self.field("typeSpatial"):
            geom_column = self.field("geomCol")

        # Ensure that user has selected at least one column if it is a
        # non-spatial table
        if len(matchCols) == 0:
            self.show_error_message("Please select at least one source column.")
            return success

        value_translator_manager = self._trans_widget_mgr.translator_manager()

        try:
            if self.field("optOverwrite"):
                entity = self.curr_profile.entity_by_name(self.targetTab)
                dependencies = entity.dependencies()
                view_dep = dependencies['views']
                entity_dep = [e.name for e in entity.children()]
                entities_dep_str = ', '.join(entity_dep)
                views_dep_str = ', '.join(view_dep)

                if len(entity_dep) > 0 or len(view_dep) > 0:
                    del_msg = QApplication.translate(
                        'ImportData',
                        "Overwriting existing records will permanently \n"
                        "remove records from other tables linked to the \n"
                        "records. The following tables will be affected."
                        "\n{}\n{}"
                        "\nClick Yes to proceed importing or No to cancel.".
                            format(entities_dep_str, views_dep_str)
                    )
                    del_result = QMessageBox.critical(
                        self,
                        QApplication.translate(
                            "ImportData",
                            "Overwrite Import Data Warning"
                        ),
                        del_msg,
                        QMessageBox.Yes | QMessageBox.No
                    )

                    if del_result == QMessageBox.Yes:
                        self.dataReader.featToDb(
                            self.targetTab, matchCols, False, self, geom_column,
                            translator_manager=value_translator_manager
                        )
                        # Update directory info in the registry
                        setVectorFileDir(self.field("srcFile"))

                        self.show_info_message(
                            "All features have been imported successfully!"
                        )
                        success = True

                    else:
                        success = False
            else:
                self.dataReader.featToDb(
                    self.targetTab, matchCols, True, self, geom_column,
                    translator_manager=value_translator_manager
                )
                self.show_info_message(
                    "All features have been imported successfully!"
                )
                # Update directory info in the registry
                setVectorFileDir(self.field("srcFile"))
                success = True
        except ImportFeatureException as e:
            self.show_error_message(str(e))

        return success

    def _clear_dest_table_selections(self, exclude=None):
        # Clears checked items in destination table list view
        if exclude is None:
            exclude = []

        for i in range(self.lstDestTables.count()):
            item = self.lstDestTables.item(i)
            if item.checkState() == Qt.Checked and not item.data(ImportData.ROLE_TABLE_NAME) in exclude:
                item.setCheckState(Qt.Unchecked)

    def destSelectChanged(self, item):
        """
        Handler when a list widget item is clicked,
        clears previous selections
        """
        if item.checkState() == Qt.Checked:
            selected_table = item.data(ImportData.ROLE_TABLE_NAME)
            # Ensure other selected items have been cleared
            self._clear_dest_table_selections(exclude=[selected_table])

            # Load geometry columns if selection is a spatial table
            if self.field("typeSpatial"):
                self.loadGeomCols(selected_table)

    def syncRowSelection(self, srcList, destList):
        """
        Sync the selection of an srcList item to the corresponding one in
        the destination column list.
        """
        if (srcList.currentRow() + 1) <= destList.count():
            destList.setCurrentRow(srcList.currentRow())

    def sourceRowChanged(self):
        # Slot when the source list's current row changes
        self.syncRowSelection(self.lstSrcFields, self.lstTargetFields)

    def destRowChanged(self):
        # Slot when the destination list's current row changes
        self.syncRowSelection(self.lstTargetFields, self.lstSrcFields)

    def itemUp(self, listWidget):
        # Moves the selected item in the list widget one level up
        curIndex = listWidget.currentRow()
        curItem = listWidget.takeItem(curIndex)
        listWidget.insertItem(curIndex - 1, curItem)
        listWidget.setCurrentRow(curIndex - 1)

    def itemDown(self, listWidget):
        # Moves the selected item in the list widget one level down
        curIndex = listWidget.currentRow()
        curItem = listWidget.takeItem(curIndex)
        listWidget.insertItem(curIndex + 1, curItem)
        listWidget.setCurrentRow(curIndex + 1)

    def checkAllItems(self, listWidget, state):
        # Checks all items in the list widget
        for l in range(listWidget.count()):
            item = listWidget.item(l)
            if state:
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)

    def checkSrcItems(self):
        # Slot for checking all source table columns
        self.checkAllItems(self.lstSrcFields, True)

    def uncheckSrcItems(self):
        # Slot for unchecking all source table columns
        self.checkAllItems(self.lstSrcFields, False)

    def srcItemUp(self):
        # Slot for moving source list item up
        self.itemUp(self.lstSrcFields)

    def srcItemDown(self):
        # Slot for moving source list item down
        self.itemDown(self.lstSrcFields)

    def targetItemUp(self):
        # Slot for moving target item up
        self.itemUp(self.lstTargetFields)

    def targetItemDown(self):
        # Slot for moving target item down
        self.itemDown(self.lstTargetFields)

    def keyPressEvent(self, e):
        """
        Override method for preventing the dialog from
        closing itself when the escape key is hit
        """
        if e.key() == Qt.Key_Escape:
            pass

    def show_info_message(self, message):
        # Information message box
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(message)
        msg.exec_()

    def show_error_message(self, message):
        # Error Message Box
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setText(message)
        msg.exec_()

    def _save_column_mapping(self):
        """
        Exports the current column mapping to a JSON definition file
        """
        config = RegistryConfig()
        prev_folder = config.read(["LastImportConfigFolder"]).get("LastImportConfigFolder")
        if not prev_folder:
            prev_folder = QDir.homePath()

        dest_path, _ = QFileDialog.getSaveFileName(self, self.tr("Save Configuration"),
                                                   prev_folder,
                                                   "{0} (*.json)".format(self.tr('Configuration files')))

        if not dest_path:
            return

        dest_path = QgsFileUtils.ensureFileNameHasExtension(dest_path, ['.json'])

        config.write({"LastImportConfigFolder": QFileInfo(dest_path).path()})

        with open(dest_path, 'wt') as f:
            f.write(json.dumps(self._get_column_config(), indent=4))

    def _get_column_config(self) -> dict:
        """
        Returns a dictionary encapsulating the column mapping configuration
        """
        return {
            'column_mapping': self.get_source_dest_pairs(),
            'source_file': str(self.field("srcFile")),
            'is_text': bool(self.field("typeText")),
            'is_spatial': bool(self.field("typeSpatial")),
            'geom_column': self.field("geomCol") or None,
            'overwrite': bool(self.field("optOverwrite")),
            'dest_table': self.targetTab or ''
        }

    def _load_column_mapping(self):
        """
        Imports the current column mapping from a JSON definition file
        """
        config = RegistryConfig()
        prev_folder = config.read(["LastImportConfigFolder"]).get("LastImportConfigFolder")
        if not prev_folder:
            prev_folder = QDir.homePath()

        source_path, _ = QFileDialog.getOpenFileName(self, self.tr("Load Configuration"),
                                                     prev_folder,
                                                     "{0} (*.json)".format(self.tr('Configuration files')))

        if not source_path:
            return

        config.write({"LastImportConfigFolder": QFileInfo(source_path).path()})

        with open(source_path, 'rt') as f:
            imported_config = json.loads(''.join(f.readlines()))
            self._restore_column_config(imported_config)

    def _restore_column_config(self, config: dict):
        """
        Restores a previously saved column configuration
        """
        column_mapping = config.get('column_mapping', {})

        # test validity -- ensure that all the referenced source and destination columns
        # from the saved file are available
        for saved_source, saved_dest in column_mapping.items():

            for source_row in range(self.lstSrcFields.count()):
                if ImportData.names_are_matching(saved_source, self.lstSrcFields.item(source_row).text()):
                    break
            else:
                self.show_error_message(self.tr('Source column {} not found in dataset'.format(saved_source)))
                return

            for destination_row in range(self.lstTargetFields.count()):
                if ImportData.names_are_matching(saved_dest, self.lstTargetFields.item(destination_row).text()):
                    break
            else:
                self.show_error_message(self.tr('Destination column {} not found in dataset'.format(saved_dest)))
                return

        self.set_source_dest_pairs(column_mapping)