예제 #1
0
class TableView(QTableView):
    def __init__(self, parent):
        QTableView.__init__(self, parent)
        self._menu = QMenu(self)
        self._copy_action = QAction(self.tr('Copy'), self)
        self._copy_action.triggered.connect(self.copy)
        self._copy_action.setShortcuts(QKeySequence.Copy)
        self._copy_with_headers_action = QAction(self.tr('Copy With Headers'), self)
        self._copy_with_headers_action.triggered.connect(self.copyWithHeaders)
        self._paste_action = QAction(self.tr('Paste'), self)
        self._paste_action.triggered.connect(self.paste)
        self._paste_action.setShortcuts(QKeySequence.Paste)
        self._menu.addAction(self._copy_action)
        self._menu.addAction(self._copy_with_headers_action)
        self._menu.addAction(self._paste_action)
        self.addAction(self._copy_action)
        self.addAction(self._copy_with_headers_action)
        self.addAction(self._paste_action)
    
    @property
    def pasteEnabled(self):
        return self._paste_action.isEnabled()
    
    @pasteEnabled.setter
    def pasteEnabled(self, value):
        self._paste_action.setEnabled(value)

    @property
    def copyEnabled(self):
        return self._copy_action.isEnabled()
    
    @copyEnabled.setter
    def copyEnabled(self, value):
        self._copy_action.setEnabled(value)
    
    def setModel(self, model):
        QTableView.setModel(self, model)
        if model:
            if model.parent() is None:
                model.setParent(self)
            model.dataChanged.connect(self._handle_data_changed)
            self._update_cell_spans()

    def copy(self):
        self._do_copy()
    
    def copyWithHeaders(self):
        self._do_copy(include_headers=True)
    
    def _do_copy(self, include_headers=False):
        indexes = self._get_selected_indexes()
        current_row = None
        paste_data = ''
        columns = []
        columns_added = []
        row_header_added = False
        for i in indexes:
            if include_headers and i.column() not in columns_added:
                columns.append(self.model().headerData(i.column(), Qt.Horizontal) or '')
                columns_added.append(i.column())
            if current_row is not None:
                if i.row() != current_row:
                    paste_data += '\n'
                    row_header_added = False
                else:
                    paste_data += '\t'
            if include_headers and not row_header_added:
                paste_data += (self.model().headerData(i.row(), Qt.Vertical) or '') + '\t'
                row_header_added = True
            data = self.model().data(i)
            paste_data += str(data) if data is not None else ''
            current_row = i.row()
        if include_headers:
            header_row = '\t' + '\t'.join(columns) + '\n'
            paste_data = header_row + paste_data
        QApplication.clipboard().setText(paste_data)
    
    def paste(self):
        value = QApplication.clipboard().text()
        rows = value.strip('\n').split('\n')
        selected_indexes = self._get_selected_indexes()
        if selected_indexes:
            start_row = selected_indexes[0].row()
            start_column = selected_indexes[0].column()
        else:
            start_row = start_column = 0
        indexes = []
        if hasattr(self.model(), 'beginPaste'):
            self.model().beginPaste()
        for row_id, row_text in zip(range(len(rows)), rows):
            cells = row_text.split('\t')
            for col_id, cell_text in zip(range(len(cells)), cells):
                index = self.model().createIndex(row_id + start_row, col_id + start_column)
                self.model().setData(index, cell_text)
                indexes.append(index)
        if hasattr(self.model(), 'endPaste'):
            self.model().endPaste(indexes[0], indexes[-1])

    def _get_selected_indexes(self):
        return list(sorted(sorted(self.selectedIndexes(), key=lambda i: i.column()), key=lambda i: i.row()))

    def contextMenuEvent(self, event):
        self._menu.exec_(event.globalPos())

    def _handle_data_changed(self, start_index, end_index):
        self._update_cell_spans(start_index, end_index)

    def _update_cell_spans(self, start_index=QModelIndex(), end_index=QModelIndex()):
        if isinstance(self.model(), QSortFilterProxyModel):
            return
        start_index = start_index if start_index.isValid() else self.model().createIndex(0, 0)
        end_index = end_index if end_index.isValid() else self.model().createIndex(self.model().rowCount(), self.model().columnCount())
        for row in range(start_index.row(), end_index.row()):
            for column in range(start_index.column(), end_index.column()):
                current_index = self.model().createIndex(row, column)
                row_span = self.model().data(current_index, RowSpanRole)
                col_span = self.model().data(current_index, ColumnSpanRole)
                if (isinstance(row_span, int) and row_span != self.rowSpan(row, column)) or (isinstance(col_span, int) and col_span != self.columnSpan(row, column)):
                    row_span = row_span if isinstance(row_span, int) else 1
                    col_span = col_span if isinstance(col_span, int) else 1
                    self.setSpan(row, column, row_span, col_span)