Aug-05-2021, 11:14 AM
self is the main window (QMainWindow)
This is the full code, The paste button is on the right side of the toolbar.
This is the full code, The paste button is on the right side of the toolbar.
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys import csv, codecs import os import pandas as pd import numpy as np from PyQt5.QtCore import Qt, QDir, QItemSelectionModel, QAbstractTableModel, QModelIndex, QVariant, QSize, QSettings from PyQt5.QtWidgets import (QMainWindow, QTableView, QApplication, QToolBar, QLineEdit, QComboBox, QDialog, QAction, QMenu, QFileDialog, QAbstractItemView, QMessageBox, QWidget, QTableWidgetItem) from PyQt5.QtGui import QStandardItemModel, QStandardItem, QCursor, QIcon, QKeySequence, QTextDocument, QTextCursor, QTextTableFormat from PyQt5 import QtPrintSupport class PandasModel(QAbstractTableModel): def __init__(self, df = pd.DataFrame(), parent=None): QAbstractTableModel.__init__(self, parent=None) self._df = df self.setChanged = False self.dataChanged.connect(self.setModified) def setModified(self): self.setChanged = True print(self.setChanged) def headerData(self, section, orientation, role=Qt.DisplayRole): if role != Qt.DisplayRole: return QVariant() if orientation == Qt.Horizontal: try: return self._df.columns.tolist()[section] except (IndexError, ): return QVariant() elif orientation == Qt.Vertical: try: return self._df.index.tolist()[section] except (IndexError, ): return QVariant() def flags(self, index): return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable def data(self, index, role=Qt.DisplayRole): if index.isValid(): if (role == Qt.EditRole): return self._df.values[index.row()][index.column()] elif (role == Qt.DisplayRole): return self._df.values[index.row()][index.column()] return None def setData(self, index, value, role): row = self._df.index[index.row()] col = self._df.columns[index.column()] self._df.values[row][col] = value self.dataChanged.emit(index, index) return True def rowCount(self, parent=QModelIndex()): return len(self._df.index) def columnCount(self, parent=QModelIndex()): return len(self._df.columns) def insertRows(self, position, rows=1, index=QModelIndex()): print ("\n\t\t ...insertRows() Starting position: '%s'"%position, 'with the total rows to be inserted: ', rows) indexSelected=self.index(position, 0) itemSelected=indexSelected.data() ###.toPyObject() self.beginInsertRows(QModelIndex(), position, position + rows - 1) self.endInsertRows() return True def sort(self, column, order): colname = self._df.columns.tolist()[column] self.layoutAboutToBeChanged.emit() self._df.sort_values(colname, ascending= order == Qt.AscendingOrder, inplace=True) self._df.reset_index(inplace=True, drop=True) self.layoutChanged.emit() def moveRows(self, parent, source_first, source_last, parent2, dest): self.beginMoveRows(parent, source_first, source_last, parent2, dest) self.data = self._df.values[index.row()][index.column()] self.endMoveRows() class Viewer(QMainWindow): def __init__(self, parent=None): super(Viewer, self).__init__(parent) self.setWindowIcon(QIcon.fromTheme("calc")) self.MaxRecentFiles = 5 self.windowList = [] self.recentFiles = [] self.settings = QSettings('Axel Schneider', 'QTableViewPandas') self.filename = "" self.hasHeaders = False self.setGeometry(0, 0, 800, 600) self.lb = QTableView() self.lb.verticalHeader().setVisible(True) self.model = PandasModel() self.lb.setModel(self.model) self.lb.setEditTriggers(QAbstractItemView.DoubleClicked) self.lb.setSelectionBehavior(QAbstractItemView.SelectRows) self.lb.setSelectionMode(QAbstractItemView.SingleSelection) self.lb.setDragDropMode(QAbstractItemView.InternalMove) self.lb.setDragDropOverwriteMode(False) self.lb.horizontalHeader().setStretchLastSection(False) self.lb.verticalHeader().setStretchLastSection(False) self.setStyleSheet(stylesheet(self)) self.lb.setAcceptDrops(True) self.setCentralWidget(self.lb) self.setContentsMargins(10, 10, 10, 10) self.createToolBar() self.readSettings() self.lb.setFocus() self.statusBar().showMessage("Ready", 0) def readSettings(self): print("reading settings") if self.settings.contains("geometry"): self.setGeometry(self.settings.value('geometry')) if self.settings.contains("recentFiles"): self.recentFiles = self.settings.value('recentFiles') self.lastFiles.addItem("last Files ...") self.recentFiles = list(dict.fromkeys(self.recentFiles)) print(self.recentFiles) if len(self.recentFiles) > 0: self.lastFiles.addItems(self.recentFiles[:15]) def saveSettings(self): print("saving settings") self.settings.setValue('geometry', self.geometry()) self.settings.setValue('recentFiles', self.recentFiles) def closeEvent(self, event): print(self.model.setChanged) if self.model.setChanged == True: print("is changed, saving?") quit_msg = "<b>The document was changed.<br>Do you want to save the changes?</ b>" reply = QMessageBox.question(self, 'Save Confirmation', quit_msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: self.writeCSV_update() else: print("not saved, goodbye ...") return else: print("nothing changed. goodbye") self.saveSettings() def createToolBar(self): openAction = QAction(QIcon.fromTheme("document-open"), "Open", self, triggered=self.loadCSV, shortcut = QKeySequence.Open) saveAction = QAction(QIcon.fromTheme("document-save"), "Save", self, triggered= self.writeCSV_update, shortcut = QKeySequence.Save) saveAsAction = QAction(QIcon.fromTheme("document-save-as"), "Save as ...", self, triggered=self.writeCSV, shortcut = QKeySequence.SaveAs) self.tbar = self.addToolBar("File") self.tbar.setContextMenuPolicy(Qt.PreventContextMenu) self.tbar.setIconSize(QSize(16, 16)) self.tbar.setMovable(False) self.tbar.addAction(openAction) self.tbar.addAction(saveAction) self.tbar.addAction(saveAsAction) self.tbar.addSeparator() self.tbar.addAction(QIcon.fromTheme("add"), "insert row", self.insertRow) self.tbar.addSeparator() self.tbar.addAction(QIcon.fromTheme("edit"), "first row to headers", self.setHeadersToFirstRow) empty = QWidget() empty.setFixedWidth(10) self.tbar.addWidget(empty) self.lastFiles = QComboBox() self.lastFiles.setToolTip("recent Files") self.lastFiles.setFixedWidth(300) self.lastFiles.currentIndexChanged.connect(self.loadRecent) self.tbar.addWidget(self.lastFiles) empty = QWidget() empty.setFixedWidth(10) self.tbar.addWidget(empty) findbyText = QAction(QIcon.fromTheme("edit-find-symbolic"), "find", self, triggered = self.findInTable) self.lineFind = QLineEdit() self.lineFind.addAction(findbyText, 0) self.lineFind.setPlaceholderText("find") self.lineFind.setClearButtonEnabled(True) self.lineFind.setFixedWidth(250) self.lineFind.returnPressed.connect(self.findInTable) self.tbar.addWidget(self.lineFind) self.tbar.addAction(findbyText) empty = QWidget() empty.setFixedWidth(10) self.tbar.addWidget(empty) self.previewAction = QAction(QIcon.fromTheme("document-print-preview"), "Print Preview", self, triggered = self.handlePreview) self.tbar.addAction(self.previewAction) self.printAction = QAction(QIcon.fromTheme("document-print"), "Print", self, triggered = self.handlePrint) self.tbar.addAction(self.printAction) self.copyAction = QAction(QIcon.fromTheme("edit-copy"), "Copy Document", self, triggered = self.copyTable) self.tbar.addAction(self.copyAction) self.pasteAction = QAction(QIcon.fromTheme("edit-paste"), "Paste from Clipboard", self, triggered = self.pasteTable) self.tbar.addAction(self.pasteAction) def pasteTable(self): df = pd.read_clipboard(sep='\\s+', dtype=str, skip_blank_lines=True, header=None) self.df = df.replace(np.nan, '', regex=True) self.model = PandasModel(self.df) self.lb.setModel(main.model) self.lb.resizeColumnsToContents() self.lb.selectRow(0) self.statusBar().showMessage("clipboard loaded", 0) self.hasHeaders = False def copyTable(self): self.df.to_clipboard(excel=True, sep='\t') def insertRow(self): self.model.insertRows(0) def setHeadersToFirstRow(self): f = open(self.filename, 'r+b') with f: df = pd.read_csv(f, delimiter = '\t', keep_default_na = False, low_memory=False, header=0) f.close() self.model = PandasModel(df) self.lb.setModel(main.model) self.hasHeaders = True def loadRecent(self): if self.lastFiles.currentIndex() > 0: print(self.lastFiles.currentText()) print(self.model.setChanged) if self.model.setChanged == True: print("is changed, saving?") quit_msg = "<b>The document was changed.<br>Do you want to save the changes?</ b>" reply = QMessageBox.question(self, 'Save Confirmation', quit_msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: self.openCSV(self.lastFiles.currentText()) else: self.openCSV(self.lastFiles.currentText()) else: self.openCSV(self.lastFiles.currentText()) self.filename = self.lastFiles.currentText() self.hasHeaders = False def openCSV(self, path): f = open(path, 'r+b') with f: self.df = pd.read_csv(f, delimiter = '\t', keep_default_na = False, low_memory=False, header=None) f.close() self.model = PandasModel(self.df) self.lb.setModel(main.model) self.lb.resizeColumnsToContents() self.lb.selectRow(0) self.statusBar().showMessage("%s %s" % (path, "loaded"), 0) self.filename = f self.hasHeaders = False def findInTable(self): self.lb.clearSelection() text = self.lineFind.text() model = self.lb.model() for column in range(self.model.columnCount()): start = model.index(0, column) matches = model.match(start, Qt.DisplayRole, text, -1, Qt.MatchContains) if matches: for index in matches: # print(index.row(), index.column()) self.lb.selectionModel().select(index, QItemSelectionModel.Select) def openFile(self, path=None): print(self.model.setChanged) if self.model.setChanged == True: print("is changed, saving?") quit_msg = "<b>The document was changed.<br>Do you want to save the changes?</ b>" reply = QMessageBox.question(self, 'Save Confirmation', quit_msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: self.writeCSV_update() else: print("not saved, loading ...") return path, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.homePath() + "/Dokumente/CSV/","CSV Files (*.csv)") if path: return path def loadCSV(self): fileName = self.openFile() if fileName: self.recentFiles.insert(0, fileName) self.filename = fileName self.lastFiles.insertItem(1, fileName) self.hasHeaders = False self.lastFiles.setCurrentIndex(1) def writeCSV(self): fileName, _ = QFileDialog.getSaveFileName(self, "Open File", self.filename,"CSV Files (*.csv)") if fileName: print(fileName + " saved") f = open(fileName, 'w') newModel = self.model dataFrame = newModel._df.copy() if self.hasHeaders == False: dataFrame.to_csv(f, sep='\t', index = False, header = False) else: dataFrame.to_csv(f, sep='\t', index = False, header = True) self.lastFiles.insertItem(1, fileName) def writeCSV_update(self): if self.filename: f = open(self.filename, 'w') newModel = self.model dataFrame = newModel._df.copy() if self.hasHeaders == False: dataFrame.to_csv(f, sep='\t', index = False, header = False) else: dataFrame.to_csv(f, sep='\t', index = False, header = True) self.model.setChanged = False print("%s %s" % (self.filename, "saved")) self.statusBar().showMessage("%s %s" % (self.filename, "saved"), 0) def handlePrint(self): if self.model.rowCount() == 0: self.msg("no rows") else: dialog = QtPrintSupport.QPrintDialog() if dialog.exec_() == QDialog.Accepted: self.handlePaintRequest(dialog.printer()) print("Document printed") def handlePreview(self): if self.model.rowCount() == 0: self.msg("no rows") else: dialog = QtPrintSupport.QPrintPreviewDialog() dialog.setFixedSize(1000,700) dialog.paintRequested.connect(self.handlePaintRequest) dialog.exec_() print("Print Preview closed") def handlePaintRequest(self, printer): printer.setDocName(self.filename) document = QTextDocument() cursor = QTextCursor(document) model = self.lb.model() tableFormat = QTextTableFormat() tableFormat.setBorder(0.2) tableFormat.setBorderStyle(3) tableFormat.setCellSpacing(0); tableFormat.setTopMargin(0); tableFormat.setCellPadding(4) table = cursor.insertTable(model.rowCount() + 1, model.columnCount(), tableFormat) model = self.lb.model() ### get headers myheaders = [] for i in range(0, model.columnCount()): myheader = model.headerData(i, Qt.Horizontal) cursor.insertText(str(myheader)) cursor.movePosition(QTextCursor.NextCell) ### get cells for row in range(0, model.rowCount()): for col in range(0, model.columnCount()): index = model.index( row, col ) cursor.insertText(str(index.data())) cursor.movePosition(QTextCursor.NextCell) document.print_(printer) def stylesheet(self): return """ QMainWindow { background: qlineargradient(y1: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); } QMenuBar { background: transparent; border: 0px; } QTableView { border: 1px outset #babdb6; border-radius: 0px; font-size: 8pt; background: transparent; margin-top: 8px; } QTableView::item:hover { color: #eeeeec; background: #c4a000;; } QTableView::item:selected { font-weight: bold; color: #F4F4F4; background: qlineargradient(y1:0, y2:1, stop:0 #729fcf, stop:1 #2a82da); } QTableView QTableCornerButton::section { background: qlineargradient( y1: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); border: 1px solid #d3d7cf; } QHeaderView { background: qlineargradient( y1: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); color: #888a85; } QToolBar { background: transparent; border: 0px; } QStatusBar { background: transparent; border: 0px; color: #555753; font-size: 7pt; } QToolTip { border: 1px solid darkkhaki; padding: 1px; border-radius: 3px; opacity: 255; font-size: 8pt; } """ if __name__ == "__main__": app = QApplication(sys.argv) main = Viewer() main.show() if len(sys.argv) > 1: main.openCSV(sys.argv[1]) sys.exit(app.exec_())