예제 #1
0
class ABDataFrameView(QTableView):
    """ Base class for showing pandas dataframe objects as tables.
    """
    ALL_FILTER = "All Files (*.*)"
    CSV_FILTER = "CSV (*.csv);; All Files (*.*)"
    TSV_FILTER = "TSV (*.tsv);; All Files (*.*)"
    EXCEL_FILTER = "Excel (*.xlsx);; All Files (*.*)"

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setVerticalScrollMode(QTableView.ScrollPerPixel)
        self.setHorizontalScrollMode(QTableView.ScrollPerPixel)
        self.setWordWrap(True)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(True)
        self.verticalHeader().setDefaultSectionSize(22)  # row height
        self.verticalHeader().setVisible(True)
        # Use a custom ViewOnly delegate by default.
        # Can be overridden table-wide or per column in child classes.
        self.setItemDelegate(ViewOnlyDelegate(self))

        self.table_name = 'LCA results'
        # Initialize attributes which are set during the `sync` step.
        # Creating (and typing) them here allows PyCharm to see them as
        # valid attributes.
        self.model: Optional[PandasModel] = None
        self.proxy_model: Optional[QSortFilterProxyModel] = None

    def get_max_height(self) -> int:
        return (self.verticalHeader().count())*self.verticalHeader().defaultSectionSize() + \
                 self.horizontalHeader().height() + self.horizontalScrollBar().height() + 5

    def sizeHint(self) -> QSize:
        return QSize(self.width(), self.get_max_height())

    def rowCount(self) -> int:
        return 0 if self.model is None else self.model.rowCount()

    @Slot(name="updateProxyModel")
    def update_proxy_model(self) -> None:
        self.proxy_model = QSortFilterProxyModel(self)
        self.proxy_model.setSourceModel(self.model)
        self.proxy_model.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.setModel(self.proxy_model)

    @Slot(name="resizeView")
    def custom_view_sizing(self) -> None:
        """ Custom table resizing to perform after setting new (proxy) model.
        """
        self.setMaximumHeight(self.get_max_height())

    @Slot(name="exportToClipboard")
    def to_clipboard(self):
        """ Copy dataframe to clipboard
        """
        rows = list(range(self.model.rowCount()))
        cols = list(range(self.model.columnCount()))
        self.model.to_clipboard(rows, cols, include_header=True)

    def savefilepath(self,
                     default_file_name: str,
                     caption: str = None,
                     file_filter: str = None):
        """ Construct and return default path where data is stored

        Uses the application directory for AB
        """
        safe_name = safe_filename(default_file_name, add_hash=False)
        caption = caption or "Choose location to save lca results"
        filepath, _ = QFileDialog.getSaveFileName(
            parent=self,
            caption=caption,
            dir=os.path.join(ab_settings.data_dir, safe_name),
            filter=file_filter or self.ALL_FILTER,
        )
        # getSaveFileName can now weirdly return Path objects.
        return str(filepath) if filepath else filepath

    @Slot(name="exportToCsv")
    def to_csv(self):
        """ Save the dataframe data to a CSV file.
        """
        filepath = self.savefilepath(self.table_name,
                                     file_filter=self.CSV_FILTER)
        if filepath:
            if not filepath.endswith('.csv'):
                filepath += '.csv'
            self.model.to_csv(filepath)

    @Slot(name="exportToExcel")
    def to_excel(self, caption: str = None):
        """ Save the dataframe data to an excel file.
        """
        filepath = self.savefilepath(self.table_name,
                                     caption,
                                     file_filter=self.EXCEL_FILTER)
        if filepath:
            if not filepath.endswith('.xlsx'):
                filepath += '.xlsx'
            self.model.to_excel(filepath)

    @Slot(QKeyEvent, name="copyEvent")
    def keyPressEvent(self, e):
        """ Allow user to copy selected data from the table

        NOTE: by default, the table headers (column names) are also copied.
        """
        if e.modifiers() & Qt.ControlModifier:
            # Should we include headers?
            headers = e.modifiers() & Qt.ShiftModifier
            if e.key() == Qt.Key_C:  # copy
                selection = [
                    self.model.proxy_to_source(p)
                    for p in self.selectedIndexes()
                ]
                rows = [index.row() for index in selection]
                columns = [index.column() for index in selection]
                rows = sorted(set(rows), key=rows.index)
                columns = sorted(set(columns), key=columns.index)
                self.model.to_clipboard(rows, columns, headers)
예제 #2
0
class ObjListWindow(PBDialog):
    """Create a window managing a list (of bibtexs or of experiments)"""
    def __init__(self, parent=None, gridLayout=False):
        """Init using parent class and create common definitions

        Parameters:
            parent: the parent object
            gridLayout (boolean, default False):
                if True, use a QGridLayout, otherwise a QVBoxLayout
        """
        super(ObjListWindow, self).__init__(parent)
        self.tableWidth = None
        self.proxyModel = None
        self.gridLayout = gridLayout
        self.filterInput = None
        self.proxyModel = None
        self.tableview = None
        if gridLayout:
            self.currLayout = QGridLayout()
        else:
            self.currLayout = QVBoxLayout()
        self.setLayout(self.currLayout)

    def triggeredContextMenuEvent(self, row, col, event):
        """Not implemented: requires a subclass"""
        raise NotImplementedError()

    def handleItemEntered(self, index):
        """Not implemented: requires a subclass"""
        raise NotImplementedError()

    def cellClick(self, index):
        """Not implemented: requires a subclass"""
        raise NotImplementedError()

    def cellDoubleClick(self, index):
        """Not implemented: requires a subclass"""
        raise NotImplementedError()

    def createTable(self, *args, **kwargs):
        """Not implemented: requires a subclass"""
        raise NotImplementedError()

    def changeFilter(self, string):
        """Change the filter of the current view.

        Parameter:
            string: the filter string to be matched
        """
        self.proxyModel.setFilterRegExp(str(string))

    def addFilterInput(self, placeholderText, gridPos=(1, 0)):
        """Add a `QLineEdit` to change the filter of the list.

        Parameter:
            placeholderText: the text to be shown
                when no filter is present
            gridPos (tuple): if gridLayout is active,
                the position of the `QLineEdit` in the `QGridLayout`
        """
        self.filterInput = QLineEdit("", self)
        self.filterInput.setPlaceholderText(placeholderText)
        self.filterInput.textChanged.connect(self.changeFilter)

        if self.gridLayout:
            self.currLayout.addWidget(self.filterInput, *gridPos)
        else:
            self.currLayout.addWidget(self.filterInput)
        self.filterInput.setFocus()

    def setProxyStuff(self, sortColumn, sortOrder):
        """Prepare the proxy model to filter and sort the view.

        Parameter:
            sortColumn: the index of the column to use
                for sorting at the beginning
            sortOrder: the order for sorting
                (`Qt.AscendingOrder` or `Qt.DescendingOrder`)
        """
        self.proxyModel = QSortFilterProxyModel(self)
        self.proxyModel.setSourceModel(self.tableModel)
        self.proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.proxyModel.setFilterKeyColumn(-1)

        self.tableview = PBTableView(self)
        self.tableview.setModel(self.proxyModel)
        self.tableview.setSortingEnabled(True)
        self.tableview.setMouseTracking(True)
        self.tableview.setSelectionBehavior(QAbstractItemView.SelectRows)
        try:
            self.tableview.sortByColumn(self.tableModel.header.index("bibkey"),
                                        Qt.AscendingOrder)
        except (IndexError, ValueError):
            pass
        self.tableview.sortByColumn(sortColumn, sortOrder)
        try:
            self.proxyModel.sort(self.tableModel.header.index("bibkey"),
                                 Qt.AscendingOrder)
        except (IndexError, ValueError):
            pass
        self.proxyModel.sort(sortColumn, sortOrder)
        self.currLayout.addWidget(self.tableview)

    def finalizeTable(self, gridPos=(1, 0)):
        """Resize the table to fit the contents,
        connect functions, add to layout

        Parameter:
            gridPos (tuple): if gridLayout is active,
            the position of the `QLineEdit` in the `QGridLayout`
        """
        self.tableview.resizeColumnsToContents()

        maxh = QDesktopWidget().availableGeometry().height()
        maxw = QDesktopWidget().availableGeometry().width()
        self.setMaximumHeight(maxh)
        self.setMaximumWidth(maxw)

        hwidth = self.tableview.horizontalHeader().length()
        swidth = self.tableview.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        fwidth = self.tableview.frameWidth() * 2

        if self.tableWidth is None:
            if hwidth > maxw - (swidth + fwidth):
                self.tableWidth = maxw - (swidth + fwidth)
            else:
                self.tableWidth = hwidth + swidth + fwidth
        self.tableview.setFixedWidth(self.tableWidth)

        self.setMinimumHeight(600)

        self.tableview.resizeColumnsToContents()
        self.tableview.resizeRowsToContents()

        self.tableview.entered.connect(self.handleItemEntered)
        self.tableview.clicked.connect(self.cellClick)
        self.tableview.doubleClicked.connect(self.cellDoubleClick)

        if self.gridLayout:
            self.currLayout.addWidget(self.tableview, *gridPos)
        else:
            self.currLayout.addWidget(self.tableview)

    def recreateTable(self):
        """Delete the previous table widget and other layout items,
        then create new ones
        """
        self.cleanLayout()
        self.createTable()
예제 #3
0
class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        self.proxyModel = QSortFilterProxyModel()
        self.proxyModel.setDynamicSortFilter(True)

        self.sourceGroupBox = QGroupBox("Original Model")
        self.proxyGroupBox = QGroupBox("Sorted/Filtered Model")

        self.sourceView = QTreeView()
        self.sourceView.setRootIsDecorated(False)
        self.sourceView.setAlternatingRowColors(True)

        self.proxyView = QTreeView()
        self.proxyView.setRootIsDecorated(False)
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setModel(self.proxyModel)
        self.proxyView.setSortingEnabled(True)

        self.sortCaseSensitivityCheckBox = QCheckBox("Case sensitive sorting")
        self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter")

        self.filterPatternLineEdit = QLineEdit()
        self.filterPatternLineEdit.setClearButtonEnabled(True)
        self.filterPatternLabel = QLabel("&Filter pattern:")
        self.filterPatternLabel.setBuddy(self.filterPatternLineEdit)

        self.filterSyntaxComboBox = QComboBox()
        self.filterSyntaxComboBox.addItem("Regular expression",
                                          REGULAR_EXPRESSION)
        self.filterSyntaxComboBox.addItem("Wildcard",
                                          WILDCARD)
        self.filterSyntaxComboBox.addItem("Fixed string",
                                          FIXED_STRING)
        self.filterSyntaxLabel = QLabel("Filter &syntax:")
        self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox)

        self.filterColumnComboBox = QComboBox()
        self.filterColumnComboBox.addItem("Subject")
        self.filterColumnComboBox.addItem("Sender")
        self.filterColumnComboBox.addItem("Date")
        self.filterColumnLabel = QLabel("Filter &column:")
        self.filterColumnLabel.setBuddy(self.filterColumnComboBox)

        self.filterPatternLineEdit.textChanged.connect(self.filterRegExpChanged)
        self.filterSyntaxComboBox.currentIndexChanged.connect(self.filterRegExpChanged)
        self.filterColumnComboBox.currentIndexChanged.connect(self.filterColumnChanged)
        self.filterCaseSensitivityCheckBox.toggled.connect(self.filterRegExpChanged)
        self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged)

        sourceLayout = QHBoxLayout()
        sourceLayout.addWidget(self.sourceView)
        self.sourceGroupBox.setLayout(sourceLayout)

        proxyLayout = QGridLayout()
        proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3)
        proxyLayout.addWidget(self.filterPatternLabel, 1, 0)
        proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2)
        proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0)
        proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2)
        proxyLayout.addWidget(self.filterColumnLabel, 3, 0)
        proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2)
        proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2)
        proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2)
        self.proxyGroupBox.setLayout(proxyLayout)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.sourceGroupBox)
        mainLayout.addWidget(self.proxyGroupBox)
        self.setLayout(mainLayout)

        self.setWindowTitle("Basic Sort/Filter Model")
        self.resize(500, 450)

        self.proxyView.sortByColumn(1, Qt.AscendingOrder)
        self.filterColumnComboBox.setCurrentIndex(1)

        self.filterPatternLineEdit.setText("Andy|Grace")
        self.filterCaseSensitivityCheckBox.setChecked(True)
        self.sortCaseSensitivityCheckBox.setChecked(True)

    def setSourceModel(self, model):
        self.proxyModel.setSourceModel(model)
        self.sourceView.setModel(model)

    def filterRegExpChanged(self):
        syntax_nr = self.filterSyntaxComboBox.currentData()
        pattern = self.filterPatternLineEdit.text()
        if syntax_nr == WILDCARD:
            pattern = QRegularExpression.wildcardToRegularExpression(pattern)
        elif syntax_nr == FIXED_STRING:
            pattern = QRegularExpression.escape(pattern)

        regExp = QRegularExpression(pattern)
        if not self.filterCaseSensitivityCheckBox.isChecked():
            options = regExp.patternOptions()
            options |= QRegularExpression.CaseInsensitiveOption
            regExp.setPatternOptions(options)
        self.proxyModel.setFilterRegularExpression(regExp)

    def filterColumnChanged(self):
        self.proxyModel.setFilterKeyColumn(self.filterColumnComboBox.currentIndex())

    def sortChanged(self):
        if self.sortCaseSensitivityCheckBox.isChecked():
            caseSensitivity = Qt.CaseSensitive
        else:
            caseSensitivity = Qt.CaseInsensitive

        self.proxyModel.setSortCaseSensitivity(caseSensitivity)