flags_box.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. from PyQt5.QtWidgets import QTableWidget, QTabWidget, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QWidget, \
  2. QSizePolicy, QHeaderView, QMenu, QComboBox, QHBoxLayout, QLabel, QWidget
  3. from PyQt5.QtGui import QGuiApplication, QCursor
  4. from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
  5. from .flags_search import get_flags_index, query
  6. from collections import OrderedDict
  7. from html.parser import HTMLParser
  8. import pandas as pd
  9. class ButtonCopyableLabel(QPushButton):
  10. def __init__(self, label):
  11. super().__init__(label)
  12. def contextMenuEvent(self, QContextMenuEvent):
  13. qmenu = QMenu(self)
  14. copy_action = qmenu.addAction("Copy name")
  15. copy_action.triggered.connect(self.copy_name_to_clipboard)
  16. qmenu.exec(QCursor().pos())
  17. def copy_name_to_clipboard(self):
  18. clipboard = QGuiApplication.clipboard()
  19. clipboard.setText(self.text())
  20. class FlagSubgroupPage(QTableWidget):
  21. return_flag_signal = pyqtSignal(str, str, name="return_flag_signal")
  22. def __init__(self, parent, flags_default_values_descriptions_df):
  23. super().__init__(parent=parent)
  24. self.setRowCount(flags_default_values_descriptions_df.shape[0])
  25. self.setColumnCount(2)
  26. self.setHorizontalHeaderLabels(["Flag Name", "Flag Value"])
  27. self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
  28. self.flag_values_descriptions_df = flags_default_values_descriptions_df.set_index("Flag Name")
  29. self.flag_values_descriptions_df.rename(columns={"Flag Default Value": "Flag Value"}, inplace=True)
  30. for index, (flag_name, flag_value, flags_description, selectable_options_str, flag_value_type) in \
  31. flags_default_values_descriptions_df.iterrows():
  32. if flag_value_type.find("bool") >= 0:
  33. to_update = "True:\nFalse:\n1: same as True\n0: same as False"
  34. if not pd.isnull(selectable_options_str):
  35. to_update = f"{to_update}\n{selectable_options_str}"
  36. selectable_options_str = to_update
  37. self.flag_values_descriptions_df.loc[flag_name, "Selectable Options"] = selectable_options_str
  38. name_button = ButtonCopyableLabel(flag_name)
  39. name_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  40. name_button.setToolTip("Click here for a description of flag function and possible choices")
  41. name_button.clicked.connect(self.display_description)
  42. self.setCellWidget(index, 0, name_button)
  43. combobox = QComboBox(self)
  44. combobox.setToolTip(
  45. "Click on flag name (button just to the left) "
  46. "for a description of flag function and possible choices")
  47. combobox.setInsertPolicy(QComboBox.InsertAtBottom)
  48. combobox.setAccessibleName(flag_name)
  49. combobox.setEditable(True)
  50. if not pd.isnull(selectable_options_str):
  51. selectable_options_dict = self.parse_selectable_options_str(selectable_options_str)
  52. combobox.addItems(selectable_options_dict.keys())
  53. combobox.lineEdit().editingFinished.connect(self.return_flag)
  54. combobox.currentIndexChanged.connect(self.return_flag)
  55. self.flag_values_descriptions_df.loc[flag_name, "combobox"] = combobox
  56. # set_flag accesses the column "combobox", so it needs to be set beforehand
  57. self.set_flag(flag_name, flag_value)
  58. self.setCellWidget(index, 1, combobox)
  59. self.flag_values_descriptions_df.loc[flag_name, "button"] = name_button
  60. def parse_selectable_options_str(self, selectable_options_str):
  61. selectable_options_dict = {}
  62. for line in selectable_options_str.splitlines():
  63. if ":" in line:
  64. splits = line.split(":")
  65. k, d = splits[0], ":".join(splits[1:])
  66. selectable_options_dict[k.rstrip().lstrip()] = d
  67. return selectable_options_dict
  68. def set_flag(self, flag_name, flag_value):
  69. self.flag_values_descriptions_df.loc[flag_name, "combobox"].setCurrentText(str(flag_value))
  70. self.flag_values_descriptions_df.loc[flag_name, "Flag Value"] = str(flag_value)
  71. def reset_flag(self, flag_name, flag_value):
  72. self.flag_values_descriptions_df.loc[flag_name, "combobox"].setCurrentText(str(flag_value))
  73. def display_description(self):
  74. sender = QObject.sender(self)
  75. flag_name = sender.text()
  76. flag_descr = self.flag_values_descriptions_df.loc[flag_name, "Flag Description"]
  77. flag_selectable_values = self.flag_values_descriptions_df.loc[flag_name, "Selectable Options"]
  78. descr = flag_descr[:] # make a copy
  79. if not pd.isnull(flag_selectable_values):
  80. descr = f"{descr}\n\nValid values:\n\n{flag_selectable_values}"
  81. QMessageBox.information(self, f"Description of flag '{flag_name}'", descr)
  82. def return_flag(self):
  83. sender_le = QObject.sender(self)
  84. if sender_le is not None:
  85. flag_name = sender_le.accessibleName()
  86. if flag_name in self.flag_values_descriptions_df.index.values: # not sure why this check is needed
  87. self.return_flag_signal.emit(flag_name, sender_le.currentText())
  88. def jump_to_flag(self, flag_name):
  89. flag_index = self.flag_values_descriptions_df.index.values.tolist().index(flag_name)
  90. # this programmatic change will otherwise send currentChanged signal
  91. self.blockSignals(True)
  92. self.setCurrentCell(flag_index, 1)
  93. self.blockSignals(False)
  94. class FlagsDisplayChoiceTabs(QTabWidget):
  95. def __init__(self, parent, flags):
  96. super().__init__(parent=parent)
  97. self.subgroup_pages = OrderedDict()
  98. self.setMovable(True)
  99. self.search_widget = FlagsSearchWidget(self, flags)
  100. self.search_widget.raise_jump_to_flag_signal.connect(self.jump_to_flag)
  101. self.addTab(self.search_widget, "Search")
  102. self.flag_name_subgroup_mapping = {}
  103. for subgroup in flags.get_subgroups():
  104. subgroup_flag_def_subset_df = flags.get_subgroup_definition(subgroup)
  105. subgroup_page = FlagSubgroupPage(parent=None,
  106. flags_default_values_descriptions_df=subgroup_flag_def_subset_df
  107. )
  108. self.flag_name_subgroup_mapping.update({flag_name: subgroup
  109. for flag_name in subgroup_flag_def_subset_df["Flag Name"]})
  110. self.subgroup_pages[subgroup] = subgroup_page
  111. widget = QWidget(self)
  112. vbox = QVBoxLayout()
  113. vbox.addWidget(subgroup_page)
  114. widget.setLayout(vbox)
  115. self.addTab(widget, subgroup)
  116. def block_flags_update_signals(self, b):
  117. for subgroup_page in self.subgroup_pages.values():
  118. subgroup_page.blockSignals(b)
  119. def set_flags(self, flags):
  120. for flag_name, flag_value in flags.items():
  121. # this can happen when a request is raised for updating a deprecated or an unknown flag
  122. if flag_name in self.flag_name_subgroup_mapping:
  123. subgroup = self.flag_name_subgroup_mapping[flag_name]
  124. self.subgroup_pages[subgroup].set_flag(flag_name, flag_value)
  125. def reset_flag(self, flag_name, flag_value):
  126. subgroup = self.flag_name_subgroup_mapping[flag_name]
  127. self.subgroup_pages[subgroup].reset_flag(flag_name, flag_value)
  128. @pyqtSlot(str, name="jump to flag")
  129. def jump_to_flag(self, flag_name):
  130. target_subgroup_name = self.flag_name_subgroup_mapping[flag_name]
  131. target_subgroup_page = self.subgroup_pages[target_subgroup_name]
  132. subgroup_index = list(self.subgroup_pages.keys()).index(target_subgroup_name)
  133. # this programmatic change will otherwise send currentChanged signal
  134. self.blockSignals(True)
  135. self.setCurrentIndex(subgroup_index + 1) # index 0 is search page
  136. self.blockSignals(False)
  137. target_subgroup_page.jump_to_flag(flag_name)
  138. class FlagNameParser(HTMLParser):
  139. def __init__(self, line):
  140. super().__init__()
  141. self.flag_name = None
  142. self.feed(line)
  143. def handle_data(self, data):
  144. self.flag_name = data
  145. class FlagsSearchWidget(QWidget):
  146. raise_jump_to_flag_signal = pyqtSignal(str)
  147. def __init__(self, parent, flags):
  148. super().__init__(parent)
  149. self.search_index = get_flags_index(flags)
  150. vbox = QVBoxLayout(self)
  151. self.query_le = QLineEdit()
  152. self.query_le.setPlaceholderText("--- Search for flags here ---")
  153. self.query_le.textEdited.connect(self.query)
  154. vbox.addWidget(self.query_le)
  155. self.search_results_table = QTableWidget(self)
  156. self.search_results_table.setColumnCount(3)
  157. self.search_results_table.setHorizontalHeaderLabels(["Flag Name", "Flag Subgroup", "Flag Description"])
  158. self.search_results_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
  159. vbox.addWidget(self.search_results_table)
  160. self.flag_name_push_button_mapping_2way = {}
  161. @pyqtSlot(str, name="query and refresh")
  162. def query(self, text):
  163. self.flag_name_push_button_mapping_2way = {}
  164. highlights = query(index=self.search_index, query_str=text, max_results=20)
  165. self.search_results_table.clearContents()
  166. self.search_results_table.setRowCount(len(highlights))
  167. for ind, highlight in enumerate(highlights):
  168. self.search_results_table.setCellWidget(ind, 0, QLabel(highlight["flag_name"]))
  169. flag_name_parser = FlagNameParser(highlight["flag_name"])
  170. widget = QWidget()
  171. layout = QHBoxLayout(widget)
  172. layout.addWidget(QLabel(highlight["flag_subgroup"]))
  173. push_button = QPushButton("Go to flag")
  174. self.flag_name_push_button_mapping_2way[flag_name_parser.flag_name] = push_button
  175. self.flag_name_push_button_mapping_2way[push_button] = flag_name_parser.flag_name
  176. push_button.clicked.connect(self.raise_jump_to_flag)
  177. layout.addWidget(push_button)
  178. self.search_results_table.setCellWidget(ind, 1, widget)
  179. self.search_results_table.setCellWidget(ind, 2, QLabel(highlight["flag_description"]))
  180. self.search_results_table.resizeColumnsToContents()
  181. self.search_results_table.resizeRowsToContents()
  182. @pyqtSlot(name="raise jump to flag")
  183. def raise_jump_to_flag(self):
  184. sender = QObject.sender(self)
  185. self.raise_jump_to_flag_signal.emit(self.flag_name_push_button_mapping_2way[sender])