Esempio n. 1
0
    def __init__(self, parent=None):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.

        """
        super(CSVImportDialog, self).__init__(parent)
        self._modal = True
        self._windowTitle = u'Import CSV'
        self._encodingKey = None
        self._filename = None
        self._delimiter = None
        self._header = None
        self._detector = Detector()
        self._initUI()
Esempio n. 2
0
    def __init__(self, parent=None):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.

        """
        super(CSVImportDialog, self).__init__(parent)
        self._modal = True
        self._windowTitle = u'Import CSV'
        self._encodingKey = None
        self._filename = None
        self._delimiter = None
        self._header = None
        self._detector = Detector()
        self._initUI()
Esempio n. 3
0
class CSVImportDialog(QtGui.QDialog):
    """A dialog to import any csv file into a pandas data frame.

    This modal dialog enables the user to enter any path to a csv
    file and parse this file with or without a header and with special
    delimiter characters.

    On a successful load, the data can be previewed and the column data
    types may be edited by the user.

    After all configuration is done, the dataframe and the underlying model
    may be used by the main application.

    Attributes:
        load (QtCore.pyqtSignal): This signal is emitted, whenever the
            dialog is successfully closed, e.g. when the ok button is
            pressed. Returns DataFrameModel and path of chosen csv file.
    """

    load = Signal('QAbstractItemModel', str)

    def __init__(self, parent=None):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.

        """
        super(CSVImportDialog, self).__init__(parent)
        self._modal = True
        self._windowTitle = u'Import CSV'
        self._encodingKey = None
        self._filename = None
        self._delimiter = None
        self._header = None
        self._detector = Detector()
        self._initUI()

    def _initUI(self):
        """Initiates the user interface with a grid layout and several widgets.

        """
        self.setModal(self._modal)
        self.setWindowTitle(self._windowTitle)

        layout = QtGui.QGridLayout()

        self._filenameLabel = QtGui.QLabel(u'Choose File', self)
        self._filenameLineEdit = QtGui.QLineEdit(self)
        self._filenameLineEdit.textEdited.connect(self._updateFilename)
        chooseFileButtonIcon = QtGui.QIcon(
            QtGui.QPixmap(':/icons/document-open.png'))
        self._chooseFileAction = QtGui.QAction(self)
        self._chooseFileAction.setIcon(chooseFileButtonIcon)
        self._chooseFileAction.triggered.connect(self._openFile)

        self._chooseFileButton = QtGui.QToolButton(self)
        self._chooseFileButton.setDefaultAction(self._chooseFileAction)

        layout.addWidget(self._filenameLabel, 0, 0)
        layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2)
        layout.addWidget(self._chooseFileButton, 0, 3)

        self._encodingLabel = QtGui.QLabel(u'File Encoding', self)

        encoding_names = map(lambda x: x.upper(),
                             sorted(list(set(_encodings.viewvalues()))))
        self._encodingComboBox = QtGui.QComboBox(self)
        self._encodingComboBox.addItems(encoding_names)
        self._encodingComboBox.activated.connect(self._updateEncoding)

        layout.addWidget(self._encodingLabel, 1, 0)
        layout.addWidget(self._encodingComboBox, 1, 1, 1, 1)

        self._hasHeaderLabel = QtGui.QLabel(u'Header Available?', self)
        self._headerCheckBox = QtGui.QCheckBox(self)
        self._headerCheckBox.toggled.connect(self._updateHeader)

        layout.addWidget(self._hasHeaderLabel, 2, 0)
        layout.addWidget(self._headerCheckBox, 2, 1)

        self._delimiterLabel = QtGui.QLabel(u'Column Delimiter', self)
        self._delimiterBox = DelimiterSelectionWidget(self)
        self._delimiter = self._delimiterBox.currentSelected()
        self._delimiterBox.delimiter.connect(self._updateDelimiter)

        layout.addWidget(self._delimiterLabel, 3, 0)
        layout.addWidget(self._delimiterBox, 3, 1, 1, 3)

        self._tabWidget = QtGui.QTabWidget(self)
        self._previewTableView = QtGui.QTableView(self)
        self._datatypeTableView = QtGui.QTableView(self)
        self._tabWidget.addTab(self._previewTableView, u'Preview')
        self._tabWidget.addTab(self._datatypeTableView, u'Change Column Types')
        layout.addWidget(self._tabWidget, 4, 0, 3, 4)

        self._datatypeTableView.horizontalHeader().setDefaultSectionSize(200)
        self._datatypeTableView.setItemDelegateForColumn(
            1, DtypeComboDelegate(self._datatypeTableView))

        self._loadButton = QtGui.QPushButton(u'Load Data', self)
        #self.loadButton.setAutoDefault(False)

        self._cancelButton = QtGui.QPushButton(u'Cancel', self)
        # self.cancelButton.setDefault(False)
        # self.cancelButton.setAutoDefault(True)

        self._buttonBox = QtGui.QDialogButtonBox(self)
        self._buttonBox.addButton(self._loadButton,
                                  QtGui.QDialogButtonBox.AcceptRole)
        self._buttonBox.addButton(self._cancelButton,
                                  QtGui.QDialogButtonBox.RejectRole)
        self._buttonBox.accepted.connect(self.accepted)
        self._buttonBox.rejected.connect(self.rejected)
        layout.addWidget(self._buttonBox, 9, 2, 1, 2)
        self._loadButton.setDefault(False)
        self._filenameLineEdit.setFocus()

        self._statusBar = QtGui.QStatusBar(self)
        self._statusBar.setSizeGripEnabled(False)
        layout.addWidget(self._statusBar, 8, 0, 1, 4)
        self.setLayout(layout)

    @Slot('QString')
    def updateStatusBar(self, message):
        """Updates the status bar widget of this dialog with the given message.

        This method is also a `SLOT()`.
        The message will be shown for only 5 seconds.

        Args:
            message (QString): The new message which will be displayed.

        """
        self._statusBar.showMessage(message, 5000)

    @Slot()
    def _openFile(self):
        """Opens a file dialog and sets a value for the QLineEdit widget.

        This method is also a `SLOT`.

        """
        ret = QtGui.QFileDialog.getOpenFileName(
            self,
            self.tr(u'open file'),
            filter='Comma Separated Values (*.csv)')
        if ret:
            self._filenameLineEdit.setText(ret)
            self._updateFilename()

    @Slot('QBool')
    def _updateHeader(self, toggled):
        """Changes the internal flag, whether the csv file contains a header or not.

        This method is also a `SLOT`.

        In addition, after toggling the corresponding checkbox, the
        `_previewFile` method will be called.

        Args:
            toggled (boolean): A flag indicating the status of the checkbox.
                The flag will be used to update an internal variable.

        """
        self._header = 0 if toggled else None
        self._previewFile()

    @Slot()
    def _updateFilename(self):
        """Calls several methods after the filename changed.

        This method is also a `SLOT`.
        It checks the encoding of the changed filename and generates a
        preview of the data.

        """
        self._filename = self._filenameLineEdit.text()
        self._guessEncoding(self._filename)
        self._previewFile()

    def _guessEncoding(self, path):
        """Opens a file from the given `path` and checks the file encoding.

        The file must exists on the file system and end with the extension
        `.csv`. The file is read line by line until the encoding could be
        guessed.
        On a successfull identification, the widgets of this dialog will be
        updated.

        Args:
            path (string): Path to a csv file on the file system.

        """
        if os.path.exists(path) and path.lower().endswith('csv'):
            encoding = self._detector.detect(path)

            if encoding is not None:
                if encoding.startswith('utf'):
                    encoding = encoding.replace('-', '')
                encoding = encoding.replace('-', '_')

                viewValue = _encodings.get(encoding)

                self._encodingKey = encoding

                index = self._encodingComboBox.findText(viewValue.upper())
                self._encodingComboBox.setCurrentIndex(index)

    @Slot('int')
    def _updateEncoding(self, index):
        """Changes the value of the encoding combo box to the value of given index.

        This method is also a `SLOT`.
        After the encoding is changed, the file will be reloaded and previewed.

        Args:
            index (int): An valid index of the combo box.

        """
        encoding = self._encodingComboBox.itemText(index)
        encoding = encoding.lower()

        self._encodingKey = _calculateEncodingKey(encoding)
        self._previewFile()

    @Slot('QString')
    def _updateDelimiter(self, delimiter):
        """Changes the value of the delimiter for the csv file.

        This method is also a `SLOT`.

        Args:
            delimiter (string): The new delimiter.

        """
        self._delimiter = delimiter
        self._previewFile()

    def _previewFile(self):
        """Updates the preview widgets with new models for both tab panes.

        """
        dataFrame = self._loadCSVDataFrame()
        dataFrameModel = DataFrameModel(dataFrame)
        dataFrameModel.enableEditing(True)
        self._previewTableView.setModel(dataFrameModel)
        columnModel = dataFrameModel.columnDtypeModel()
        columnModel.changeFailed.connect(self.updateStatusBar)
        self._datatypeTableView.setModel(columnModel)

    def _loadCSVDataFrame(self):
        """Loads the given csv file with pandas and generate a new dataframe.

        The file will be loaded with the configured encoding, delimiter
        and header.git
        If any execptions will occur, an empty Dataframe is generated
        and a message will appear in the status bar.

        Returns:
            pandas.DataFrame: A dataframe containing all the available
                information of the csv file.

        """
        if self._filename and os.path.exists(
                self._filename) and self._filename.endswith('.csv'):
            # default fallback if no encoding was found/selected
            encoding = self._encodingKey or 'uft8'

            try:
                dataFrame = pandas.read_csv(self._filename,
                                            sep=self._delimiter,
                                            encoding=encoding,
                                            header=self._header)
                dataFrame = dataFrame.apply(fillNoneValues)
                dataFrame = dataFrame.apply(convertTimestamps)
            except Exception, err:
                self.updateStatusBar(str(err))
                return pandas.DataFrame()
            self.updateStatusBar('Preview generated.')
            return dataFrame
        self.updateStatusBar('File does not exists or does not end with .csv')
        return pandas.DataFrame()
Esempio n. 4
0
class CSVImportDialog(QtWidgets.QDialog):
    """A dialog to import any csv file into a pandas data frame.

    This modal dialog enables the user to enter any path to a csv
    file and parse this file with or without a header and with special
    delimiter characters.

    On a successful load, the data can be previewed and the column data
    types may be edited by the user.

    After all configuration is done, the dataframe and the underlying model
    may be used by the main application.

    Attributes:
        load (QtCore.pyqtSignal): This signal is emitted, whenever the
            dialog is successfully closed, e.g. when the ok button is
            pressed. Returns DataFrameModel and path of chosen csv file.
    """

    load = Signal('QAbstractItemModel', str)

    def __init__(self, parent=None):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.

        """
        super(CSVImportDialog, self).__init__(parent)
        self._modal = True
        self._windowTitle = u'Import CSV'
        self._encodingKey = None
        self._filename = None
        self._delimiter = None
        self._header = None
        self._detector = Detector()
        self._initUI()

    def _initUI(self):
        """Initiates the user interface with a grid layout and several widgets.

        """
        self.setModal(self._modal)
        self.setWindowTitle(self._windowTitle)

        layout = QtWidgets.QGridLayout()

        self._filenameLabel = QtWidgets.QLabel(u'Choose File', self)
        self._filenameLineEdit = QtWidgets.QLineEdit(self)
        self._filenameLineEdit.textEdited.connect(self._updateFilename)
        chooseFileButtonIcon = QtGui.QIcon(QtGui.QPixmap(':/icons/document-open.png'))
        self._chooseFileAction = QtWidgets.QAction(self)
        self._chooseFileAction.setIcon(chooseFileButtonIcon)
        self._chooseFileAction.triggered.connect(self._openFile)

        self._chooseFileButton = QtWidgets.QToolButton(self)
        self._chooseFileButton.setDefaultAction(self._chooseFileAction)

        layout.addWidget(self._filenameLabel, 0, 0)
        layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2)
        layout.addWidget(self._chooseFileButton, 0, 3)

        self._encodingLabel = QtWidgets.QLabel(u'File Encoding', self)

        encoding_names = map(lambda x: x.upper(), sorted(list(set(_encodings.viewvalues()))))
        self._encodingComboBox = QtWidgets.QComboBox(self)
        self._encodingComboBox.addItems(encoding_names)
        self._encodingComboBox.activated.connect(self._updateEncoding)

        layout.addWidget(self._encodingLabel, 1, 0)
        layout.addWidget(self._encodingComboBox, 1, 1, 1, 1)

        self._hasHeaderLabel = QtWidgets.QLabel(u'Header Available?', self)
        self._headerCheckBox = QtWidgets.QCheckBox(self)
        self._headerCheckBox.toggled.connect(self._updateHeader)

        layout.addWidget(self._hasHeaderLabel, 2, 0)
        layout.addWidget(self._headerCheckBox, 2, 1)

        self._delimiterLabel = QtWidgets.QLabel(u'Column Delimiter', self)
        self._delimiterBox = DelimiterSelectionWidget(self)
        self._delimiter = self._delimiterBox.currentSelected()
        self._delimiterBox.delimiter.connect(self._updateDelimiter)

        layout.addWidget(self._delimiterLabel, 3, 0)
        layout.addWidget(self._delimiterBox, 3, 1, 1, 3)

        self._tabWidget = QtWidgets.QTabWidget(self)
        self._previewTableView = QtWidgets.QTableView(self)
        self._datatypeTableView = QtWidgets.QTableView(self)
        self._tabWidget.addTab(self._previewTableView, u'Preview')
        self._tabWidget.addTab(self._datatypeTableView, u'Change Column Types')
        layout.addWidget(self._tabWidget, 4, 0, 3, 4)

        self._datatypeTableView.horizontalHeader().setDefaultSectionSize(200)
        self._datatypeTableView.setItemDelegateForColumn(1, DtypeComboDelegate(self._datatypeTableView))


        self._loadButton = QtWidgets.QPushButton(u'Load Data', self)
        #self.loadButton.setAutoDefault(False)

        self._cancelButton = QtWidgets.QPushButton(u'Cancel', self)
        # self.cancelButton.setDefault(False)
        # self.cancelButton.setAutoDefault(True)

        self._buttonBox = QtWidgets.QDialogButtonBox(self)
        self._buttonBox.addButton(self._loadButton, QtWidgets.QDialogButtonBox.AcceptRole)
        self._buttonBox.addButton(self._cancelButton, QtWidgets.QDialogButtonBox.RejectRole)
        self._buttonBox.accepted.connect(self.accepted)
        self._buttonBox.rejected.connect(self.rejected)
        layout.addWidget(self._buttonBox, 9, 2, 1, 2)
        self._loadButton.setDefault(False)
        self._filenameLineEdit.setFocus()

        self._statusBar = QtWidgets.QStatusBar(self)
        self._statusBar.setSizeGripEnabled(False)
        layout.addWidget(self._statusBar, 8, 0, 1, 4)
        self.setLayout(layout)

    @Slot('QString')
    def updateStatusBar(self, message):
        """Updates the status bar widget of this dialog with the given message.

        This method is also a `SLOT()`.
        The message will be shown for only 5 seconds.

        Args:
            message (QString): The new message which will be displayed.

        """
        self._statusBar.showMessage(message, 5000)

    @Slot()
    def _openFile(self):
        """Opens a file dialog and sets a value for the QLineEdit widget.

        This method is also a `SLOT`.

        """
        ret = QtWidgets.QFileDialog.getOpenFileName(self, self.tr(u'open file'), filter='Comma Separated Values (*.csv)')
        if ret:
            self._filenameLineEdit.setText(ret)
            self._updateFilename()

    @Slot('QBool')
    def _updateHeader(self, toggled):
        """Changes the internal flag, whether the csv file contains a header or not.

        This method is also a `SLOT`.

        In addition, after toggling the corresponding checkbox, the
        `_previewFile` method will be called.

        Args:
            toggled (boolean): A flag indicating the status of the checkbox.
                The flag will be used to update an internal variable.

        """
        self._header = 0 if toggled else None
        self._previewFile()

    @Slot()
    def _updateFilename(self):
        """Calls several methods after the filename changed.

        This method is also a `SLOT`.
        It checks the encoding of the changed filename and generates a
        preview of the data.

        """
        self._filename = self._filenameLineEdit.text()
        self._guessEncoding(self._filename)
        self._previewFile()

    def _guessEncoding(self, path):
        """Opens a file from the given `path` and checks the file encoding.

        The file must exists on the file system and end with the extension
        `.csv`. The file is read line by line until the encoding could be
        guessed.
        On a successfull identification, the widgets of this dialog will be
        updated.

        Args:
            path (string): Path to a csv file on the file system.

        """
        if os.path.exists(path) and path.lower().endswith('csv'):
            encoding = self._detector.detect(path)

            if encoding is not None:
                if encoding.startswith('utf'):
                    encoding = encoding.replace('-', '')
                encoding = encoding.replace('-','_')

                viewValue = _encodings.get(encoding)

                self._encodingKey = encoding

                index = self._encodingComboBox.findText(viewValue.upper())
                self._encodingComboBox.setCurrentIndex(index)

    @Slot('int')
    def _updateEncoding(self, index):
        """Changes the value of the encoding combo box to the value of given index.

        This method is also a `SLOT`.
        After the encoding is changed, the file will be reloaded and previewed.

        Args:
            index (int): An valid index of the combo box.

        """
        encoding = self._encodingComboBox.itemText(index)
        encoding = encoding.lower()

        self._encodingKey = _calculateEncodingKey(encoding)
        self._previewFile()

    @Slot('QString')
    def _updateDelimiter(self, delimiter):
        """Changes the value of the delimiter for the csv file.

        This method is also a `SLOT`.

        Args:
            delimiter (string): The new delimiter.

        """
        self._delimiter = delimiter
        self._previewFile()

    def _previewFile(self):
        """Updates the preview widgets with new models for both tab panes.

        """
        dataFrame = self._loadCSVDataFrame()
        dataFrameModel = DataFrameModel(dataFrame)
        dataFrameModel.enableEditing(True)
        self._previewTableView.setModel(dataFrameModel)
        columnModel = dataFrameModel.columnDtypeModel()
        columnModel.changeFailed.connect(self.updateStatusBar)
        self._datatypeTableView.setModel(columnModel)

    def _loadCSVDataFrame(self):
        """Loads the given csv file with pandas and generate a new dataframe.

        The file will be loaded with the configured encoding, delimiter
        and header.git
        If any execptions will occur, an empty Dataframe is generated
        and a message will appear in the status bar.

        Returns:
            pandas.DataFrame: A dataframe containing all the available
                information of the csv file.

        """
        if self._filename and os.path.exists(self._filename) and self._filename.endswith('.csv'):
            # default fallback if no encoding was found/selected
            encoding = self._encodingKey or 'uft8'

            try:
                dataFrame = pandas.read_csv(self._filename,
                    sep=self._delimiter, encoding=encoding,
                    header=self._header)
                dataFrame = dataFrame.apply(fillNoneValues)
                dataFrame = dataFrame.apply(convertTimestamps)
            except Exception as err:
                self.updateStatusBar(str(err))
                return pandas.DataFrame()
            self.updateStatusBar('Preview generated.')
            return dataFrame
        self.updateStatusBar('File does not exists or does not end with .csv')
        return pandas.DataFrame()

    def _resetWidgets(self):
        """Resets all widgets of this dialog to its inital state.

        """
        self._filenameLineEdit.setText('')
        self._encodingComboBox.setCurrentIndex(0)
        self._delimiterBox.reset()
        self._headerCheckBox.setChecked(False)
        self._statusBar.showMessage('')
        self._previewTableView.setModel(None)
        self._datatypeTableView.setModel(None)

    @Slot()
    def accepted(self):
        """Successfully close the widget and return the loaded model.

        This method is also a `SLOT`.
        The dialog will be closed, when the `ok` button is pressed. If
        a `DataFrame` was loaded, it will be emitted by the signal `load`.

        """
        model = self._previewTableView.model()
        if model is not None:
            df = model.dataFrame().copy()
            dfModel = DataFrameModel(df)
            self.load.emit(dfModel, self._filename)
        self._resetWidgets()
        self.accept()

    @Slot()
    def rejected(self):
        """Close the widget and reset its inital state.

        This method is also a `SLOT`.
        The dialog will be closed and all changes reverted, when the
        `cancel` button is pressed.

        """
        self._resetWidgets()
        self.reject()