custom_widgets.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QWidget, QAbstractItemView
  2. from PyQt5.QtGui import QIcon
  3. from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
  4. from view.python_core.get_internal_files import get_internal_icons
  5. import pandas as pd
  6. import logging
  7. class QTableWidgetPandasDF(QTableWidget):
  8. def __init__(self, parent):
  9. super().__init__(parent)
  10. def remove_all_rows(self):
  11. self.clear()
  12. self.setRowCount(0)
  13. def refresh(self, df: pd.DataFrame):
  14. self.remove_all_rows()
  15. self.setSortingEnabled(False)
  16. self.setEditTriggers(QAbstractItemView.NoEditTriggers)
  17. self.setColumnCount(df.shape[1])
  18. self.setHorizontalHeaderLabels(df.columns)
  19. df_no_index = df.reset_index(drop=True)
  20. for row_ind, row in df_no_index.iterrows():
  21. table_widget_items = []
  22. for x in row.values:
  23. table_widget_item = QTableWidgetItem()
  24. table_widget_item.setData(Qt.DisplayRole, str(x))
  25. table_widget_items.append(table_widget_item)
  26. self._add_row_items(row_index=str(row_ind), items=table_widget_items)
  27. self.setVerticalHeaderLabels([str(x) for x in df.index.values])
  28. self.setSortingEnabled(True)
  29. def get_headers(self):
  30. return [self.horizontalHeaderItem(col_ind).text() for col_ind in range(self.columnCount())]
  31. def add_row(self, s: pd.Series):
  32. cols_input = s.index.values.tolist()
  33. headers = self.get_headers()
  34. # if header is empty initialize
  35. if len(headers) == 0:
  36. assert self.rowCount() == 0 and self.columnCount() == 0, "Table has entries without headers, cannot " \
  37. "figure out how to insert entries from series"
  38. self.setHorizontalHeader(cols_input)
  39. vals2add = s.values
  40. else:
  41. vals2add = {k: v for k, v in enumerate(headers) if v in cols_input}
  42. cols_missing = set(vals2add.values()) - set(cols_input)
  43. if len(cols_missing) > 0:
  44. logging.warning(f"Adding a row: Ignoring the following columns, for which values were not specified: "
  45. f"{cols_missing}")
  46. ind_item_mapping = {col_ind: QTableWidgetItem(str(s[col_name]), 0)
  47. for col_ind, col_name in vals2add.items()}
  48. self._add_row_items(row_index=s.name, items=ind_item_mapping)
  49. def _add_row_items(self, row_index: str, items):
  50. """
  51. add a row of items
  52. :param row_index: str, vertical header label for the newly added row
  53. :param items: list or dict, if dict, keys must be column numbers and values column entries. if list, then
  54. indices of values will be interpreted as column numbers
  55. :return:
  56. """
  57. assert type(items) in (list, dict)
  58. if type(items) == list:
  59. items2use = {k: v for k, v in enumerate(items)}
  60. else:
  61. items2use = items
  62. new_row_ind = self.rowCount()
  63. self.insertRow(new_row_ind)
  64. for col_ind, item in items2use.items():
  65. self.setItem(new_row_ind, col_ind, item)
  66. self.setVerticalHeaderItem(new_row_ind, QTableWidgetItem(row_index, 0))
  67. class QTableWidgetPandasDFDeletable(QTableWidgetPandasDF):
  68. remove_data_signal = pyqtSignal(int, name="delete data")
  69. def __init__(self, parent):
  70. super().__init__(parent)
  71. self.cellClicked.connect(self.send_delete_signal)
  72. self.horizontalHeader().sectionClicked.connect(self.delete_all)
  73. def setColumnCount(self, p_int):
  74. super().setColumnCount(p_int + 1)
  75. def setItem(self, row_ind: int, col_ind: int, item: QTableWidgetItem):
  76. super().setItem(row_ind, col_ind + 1, item)
  77. def setCellWidget(self, row_ind, col_ind, item: QWidget):
  78. super().setCellWidget(row_ind, col_ind + 1, item)
  79. def _add_row_items(self, row_index, items):
  80. super()._add_row_items(row_index, items)
  81. close_icon = QIcon(get_internal_icons("twotone-delete_forever-24px.svg"))
  82. super().setItem(self.rowCount() - 1, 0, QTableWidgetItem(close_icon, "", 0))
  83. def setHorizontalHeaderLabels(self, Iterable, p_str=None):
  84. super().setHorizontalHeaderLabels([""] + list(Iterable))
  85. close_icon = QIcon(get_internal_icons("twotone-delete_forever-24px.svg"))
  86. super().setHorizontalHeaderItem(0, QTableWidgetItem(close_icon, "", 0))
  87. def get_headers(self):
  88. to_return = super().get_headers()
  89. del to_return[0]
  90. return to_return
  91. @pyqtSlot(int, int, name="cell clicked")
  92. def send_delete_signal(self, row_ind, col_ind):
  93. if col_ind == 0:
  94. self.removeRow(row_ind)
  95. self.remove_data_signal.emit(row_ind)
  96. @pyqtSlot(int, name="header clicked")
  97. def delete_all(self, col_ind):
  98. if col_ind == 0:
  99. row_count = self.rowCount()
  100. for row_ind in list(range(row_count))[::-1]:
  101. self.send_delete_signal(row_ind=row_ind, col_ind=0)