#!/usr/bin/env python3 """ project: clsTableWidget file: clsTableWidget.py summary: Implements my own TableWidget class author: Paul Salajean (p.salajean[at]gmx.de) license: GPL version: 0.1 """ # Standard library imports import sys import os # Third party imports from PySide2.QtWidgets import QApplication, QTableWidget, QAbstractItemView, QTableWidgetItem, QMenu, \ QMainWindow, QMessageBox from PySide2.QtCore import Qt, QItemSelectionModel, QCoreApplication from PySide2.QtGui import QIcon # local globals SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) ICON_CLEAR = SCRIPT_PATH + "./img/icons8-clear-symbol-16.png" ICON_COPY = SCRIPT_PATH + "./img/icons8-copy-16.png" ICON_ERASER = SCRIPT_PATH + "/img/icons8-eraser-16.png" ICON_INSERT = SCRIPT_PATH + "/img/icons8-insert-clip-16.png" ICON_APPEND = SCRIPT_PATH + "/img/icons8-append-clip-16.png" ICON_PASTE = SCRIPT_PATH + "/img/icons8-paste-16.png" ICON_CUT = SCRIPT_PATH + "/img/icons8-scissors-16.png" ICON_ADD_ROW = SCRIPT_PATH + "/img/icons8-add-row-16.png" ICON_DEL = SCRIPT_PATH + "/img/icons8-delete-16.png" # def resource_path(relative_path): # """ Get absolute path to resource, works for dev and for PyInstaller """ # try: # # PyInstaller creates a temp folder and stores path in _MEIPASS # base_path = sys._MEIPASS # print("base_path", base_path) # except Exception: # base_path = os.path.abspath(".") # return os.path.join(base_path, relative_path) class TableWidget(QTableWidget): def __init__(self, parent=None): super().__init__() self.parent = parent self.setParent(parent) # self.setSelectionMode(QAbstractItemView.ContiguousSelection) self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSelectionBehavior(QTableWidget.SelectItems) #self.setSelectionBehavior(QTableWidget.SelectRows) self.setAlternatingRowColors(True) self.setSortingEnabled(True) self.horizontalHeader().setSectionsMovable(True) self.horizontalHeader().setStretchLastSection(True) # Context-menu self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.on_context_menu) # self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.AnyKeyPressed) self.vertHeader = self.verticalHeader() self.vertHeader.setContextMenuPolicy(Qt.CustomContextMenu) self.vertHeader.customContextMenuRequested.connect(self.on_rowheadercontext_menu) self.vertHeader.setSelectionMode(QAbstractItemView.SingleSelection) self.vertHeader.sectionClicked.connect(self.select_row) self.vertHeader.setSectionsMovable(True) # optimize row height self.resizeRowsToContents() self.row_selected = False def select_row(self, row): self.setSelectionBehavior(QTableWidget.SelectRows) self.selectRow(row) self.setSelectionBehavior(QTableWidget.SelectItems) self.row_selected = True def on_context_menu(self, position): menu = QMenu() item_cut = menu.addAction(QIcon(ICON_CUT), QCoreApplication.translate("TableWidget", "Cut") + "\tCtrl+X") item_copy = menu.addAction(QIcon(ICON_COPY), QCoreApplication.translate("TableWidget", "Copy") + "\tCtrl+C") item_paste = menu.addAction(QIcon(ICON_PASTE), QCoreApplication.translate("TableWidget", "Paste") + "\tCtrl+V") menu.addSeparator() print(ICON_ERASER) item_delete = menu.addAction(QIcon(ICON_ERASER), QCoreApplication.translate("TableWidget", "Delete") + "\tDel") ac = menu.exec_(self.mapToGlobal(position)) if ac == item_cut: self.item_cut() elif ac == item_copy: self.item_copy() elif ac == item_paste: self.item_paste() elif ac == item_delete: self.item_del() def on_rowheadercontext_menu(self, position): menu = QMenu() row_cut = menu.addAction(QIcon(ICON_CUT), QCoreApplication.translate("TableWidget", "Cut row")) row_copy = menu.addAction(QIcon(ICON_COPY), QCoreApplication.translate("TableWidget", "Copy row")) row_paste = menu.addAction(QIcon(ICON_PASTE), QCoreApplication.translate("TableWidget", "Paste row")) menu.addSeparator() row_insert_before = menu.addAction(QIcon(ICON_INSERT), QCoreApplication.translate("TableWidget", "Insert row before")) row_insert_after = menu.addAction(QIcon(ICON_APPEND), QCoreApplication.translate("TableWidget", "Insert row after")) menu.addSeparator() row_remove = menu.addAction(QIcon(ICON_DEL), QCoreApplication.translate("TableWidget", "Remove row")) row_delete_items = menu.addAction(QIcon(ICON_ERASER), QCoreApplication.translate("TableWidget", "Delete items")) ac = menu.exec_(self.mapToGlobal(position)) row = self.vertHeader.logicalIndexAt(position) if ac == row_cut: self.item_cut() elif ac == row_copy: self.item_copy() elif ac == row_paste: self.item_paste() elif ac == row_insert_before: self.row_insert(row) elif ac == row_insert_after: self.row_insert(row+1) elif ac == row_remove: self.row_remove(row) elif ac == row_delete_items: self.row_delete_items() def row_insert(self, row): self.insertRow(row) def has_data(self, row): """ Check if target already contains data. """ # sel_idx = self.selectionModel().selectedIndexes() for col in range(self.columnCount()): item = self.item(row, col) if item: if len(item.text()) > 0: return True return False def row_remove(self, row): if self.has_data(row): msg = QCoreApplication.translate("TableWidget", "Row Nr.") + " " + str(row+1) + " " + QCoreApplication.translate("TableWidget", "to be removed?") reply = QMessageBox.question(self, QCoreApplication.translate("TableWidget", "Remove"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False self.removeRow(row) return True def row_delete_items(self): self.item_del() def keyPressEvent(self, event): super().keyPressEvent(event) modifiers = QApplication.keyboardModifiers() key = event.key() if modifiers == Qt.ControlModifier: if key == Qt.Key_C: self.item_copy() elif key == Qt.Key_V: self.item_paste() elif key == Qt.Key_X: self.item_cut() elif key == Qt.Key_A: self.setSelectionMode(QAbstractItemView.ContiguousSelection) self.selectAll() self.setSelectionMode(QAbstractItemView.SingleSelection) if key == Qt.Key_Delete: self.item_del() elif key == Qt.Key_Escape: self.clearSelection() def item_paste(self): cur_row = self.currentRow() cur_col = self.currentColumn() # ask_confirmation = True if self.row_selected: cur_col = 0 col = 0 if len(self.clipboard_data) == 1: data = self.clipboard_data[0] item = QTableWidgetItem(data) self.setItem(cur_row, cur_col, item) item.setSelected(True) else: for data in self.clipboard_data: item = QTableWidgetItem(data) # if item: # if len(item.text()) >0: # if ask_confirmation: # msg = QCoreApplication.translate("TableWidget", "Zelle enthält bereits Daten. Überschreiben?") # reply = QMessageBox.question(self, QCoreApplication.translate("TableWidget", "Überschreiben"), msg, \ # QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # if reply == QMessageBox.No: # return False # ask_confirmation = False self.setItem(cur_row, col, item) item.setSelected(True) col += 1 def item_cut(self): self.item_copy() sel_idx = self.selectedIndexes() for idx in sel_idx: item = self.itemFromIndex(idx) try: item.setData(Qt.DisplayRole, None) except AttributeError: pass def item_copy(self): sel_idx = self.selectedIndexes() sel_rows = self.selectionModel().selectedRows() if len(sel_idx) == 1: self.row_selected = False self.sel_ranges = self.selectedRanges() self.clipboard_data = [] for idx in sel_idx: self.clipboard_data.append(idx.data()) def item_del(self): ask_cofirmation = True sel_idx = self.selectionModel().selectedIndexes() for idx in sel_idx: row = idx.row() col = idx.column() item = self.item(row, col) if item: if self.has_data(row) and ask_cofirmation: msg = QCoreApplication.translate("TableWidget", "Delete cell content?") reply = QMessageBox.question(self, QCoreApplication.translate("TableWidget", "Delete"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False ask_cofirmation = False item.setData(Qt.DisplayRole, None)