123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- from PyQt5.QtWidgets import QTableWidget, QTabWidget, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QWidget, \
- QSizePolicy, QHeaderView, QMenu, QComboBox, QHBoxLayout, QLabel, QWidget
- from PyQt5.QtGui import QGuiApplication, QCursor
- from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
- from .flags_search import get_flags_index, query
- from collections import OrderedDict
- from html.parser import HTMLParser
- import pandas as pd
- class ButtonCopyableLabel(QPushButton):
- def __init__(self, label):
- super().__init__(label)
- def contextMenuEvent(self, QContextMenuEvent):
- qmenu = QMenu(self)
- copy_action = qmenu.addAction("Copy name")
- copy_action.triggered.connect(self.copy_name_to_clipboard)
- qmenu.exec(QCursor().pos())
- def copy_name_to_clipboard(self):
- clipboard = QGuiApplication.clipboard()
- clipboard.setText(self.text())
- class FlagSubgroupPage(QTableWidget):
- return_flag_signal = pyqtSignal(str, str, name="return_flag_signal")
- def __init__(self, parent, flags_default_values_descriptions_df):
- super().__init__(parent=parent)
- self.setRowCount(flags_default_values_descriptions_df.shape[0])
- self.setColumnCount(2)
- self.setHorizontalHeaderLabels(["Flag Name", "Flag Value"])
- self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
- self.flag_values_descriptions_df = flags_default_values_descriptions_df.set_index("Flag Name")
- self.flag_values_descriptions_df.rename(columns={"Flag Default Value": "Flag Value"}, inplace=True)
- for index, (flag_name, flag_value, flags_description, selectable_options_str, flag_value_type) in \
- flags_default_values_descriptions_df.iterrows():
- if flag_value_type.find("bool") >= 0:
- to_update = "True:\nFalse:\n1: same as True\n0: same as False"
- if not pd.isnull(selectable_options_str):
- to_update = f"{to_update}\n{selectable_options_str}"
- selectable_options_str = to_update
- self.flag_values_descriptions_df.loc[flag_name, "Selectable Options"] = selectable_options_str
- name_button = ButtonCopyableLabel(flag_name)
- name_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
- name_button.setToolTip("Click here for a description of flag function and possible choices")
- name_button.clicked.connect(self.display_description)
- self.setCellWidget(index, 0, name_button)
- combobox = QComboBox(self)
- combobox.setToolTip(
- "Click on flag name (button just to the left) "
- "for a description of flag function and possible choices")
- combobox.setInsertPolicy(QComboBox.InsertAtBottom)
- combobox.setAccessibleName(flag_name)
- combobox.setEditable(True)
- if not pd.isnull(selectable_options_str):
- selectable_options_dict = self.parse_selectable_options_str(selectable_options_str)
- combobox.addItems(selectable_options_dict.keys())
- combobox.lineEdit().editingFinished.connect(self.return_flag)
- combobox.currentIndexChanged.connect(self.return_flag)
- self.flag_values_descriptions_df.loc[flag_name, "combobox"] = combobox
- # set_flag accesses the column "combobox", so it needs to be set beforehand
- self.set_flag(flag_name, flag_value)
- self.setCellWidget(index, 1, combobox)
- self.flag_values_descriptions_df.loc[flag_name, "button"] = name_button
- def parse_selectable_options_str(self, selectable_options_str):
- selectable_options_dict = {}
- for line in selectable_options_str.splitlines():
- if ":" in line:
- splits = line.split(":")
- k, d = splits[0], ":".join(splits[1:])
- selectable_options_dict[k.rstrip().lstrip()] = d
- return selectable_options_dict
- def set_flag(self, flag_name, flag_value):
- self.flag_values_descriptions_df.loc[flag_name, "combobox"].setCurrentText(str(flag_value))
- self.flag_values_descriptions_df.loc[flag_name, "Flag Value"] = str(flag_value)
- def reset_flag(self, flag_name, flag_value):
- self.flag_values_descriptions_df.loc[flag_name, "combobox"].setCurrentText(str(flag_value))
- def display_description(self):
- sender = QObject.sender(self)
- flag_name = sender.text()
- flag_descr = self.flag_values_descriptions_df.loc[flag_name, "Flag Description"]
- flag_selectable_values = self.flag_values_descriptions_df.loc[flag_name, "Selectable Options"]
- descr = flag_descr[:] # make a copy
- if not pd.isnull(flag_selectable_values):
- descr = f"{descr}\n\nValid values:\n\n{flag_selectable_values}"
- QMessageBox.information(self, f"Description of flag '{flag_name}'", descr)
- def return_flag(self):
- sender_le = QObject.sender(self)
- if sender_le is not None:
- flag_name = sender_le.accessibleName()
- if flag_name in self.flag_values_descriptions_df.index.values: # not sure why this check is needed
- self.return_flag_signal.emit(flag_name, sender_le.currentText())
- def jump_to_flag(self, flag_name):
- flag_index = self.flag_values_descriptions_df.index.values.tolist().index(flag_name)
- # this programmatic change will otherwise send currentChanged signal
- self.blockSignals(True)
- self.setCurrentCell(flag_index, 1)
- self.blockSignals(False)
- class FlagsDisplayChoiceTabs(QTabWidget):
- def __init__(self, parent, flags):
- super().__init__(parent=parent)
- self.subgroup_pages = OrderedDict()
- self.setMovable(True)
- self.search_widget = FlagsSearchWidget(self, flags)
- self.search_widget.raise_jump_to_flag_signal.connect(self.jump_to_flag)
- self.addTab(self.search_widget, "Search")
- self.flag_name_subgroup_mapping = {}
- for subgroup in flags.get_subgroups():
- subgroup_flag_def_subset_df = flags.get_subgroup_definition(subgroup)
- subgroup_page = FlagSubgroupPage(parent=None,
- flags_default_values_descriptions_df=subgroup_flag_def_subset_df
- )
- self.flag_name_subgroup_mapping.update({flag_name: subgroup
- for flag_name in subgroup_flag_def_subset_df["Flag Name"]})
- self.subgroup_pages[subgroup] = subgroup_page
- widget = QWidget(self)
- vbox = QVBoxLayout()
- vbox.addWidget(subgroup_page)
- widget.setLayout(vbox)
- self.addTab(widget, subgroup)
- def block_flags_update_signals(self, b):
- for subgroup_page in self.subgroup_pages.values():
- subgroup_page.blockSignals(b)
- def set_flags(self, flags):
- for flag_name, flag_value in flags.items():
- # this can happen when a request is raised for updating a deprecated or an unknown flag
- if flag_name in self.flag_name_subgroup_mapping:
- subgroup = self.flag_name_subgroup_mapping[flag_name]
- self.subgroup_pages[subgroup].set_flag(flag_name, flag_value)
- def reset_flag(self, flag_name, flag_value):
- subgroup = self.flag_name_subgroup_mapping[flag_name]
- self.subgroup_pages[subgroup].reset_flag(flag_name, flag_value)
- @pyqtSlot(str, name="jump to flag")
- def jump_to_flag(self, flag_name):
- target_subgroup_name = self.flag_name_subgroup_mapping[flag_name]
- target_subgroup_page = self.subgroup_pages[target_subgroup_name]
- subgroup_index = list(self.subgroup_pages.keys()).index(target_subgroup_name)
- # this programmatic change will otherwise send currentChanged signal
- self.blockSignals(True)
- self.setCurrentIndex(subgroup_index + 1) # index 0 is search page
- self.blockSignals(False)
-
- target_subgroup_page.jump_to_flag(flag_name)
- class FlagNameParser(HTMLParser):
- def __init__(self, line):
- super().__init__()
- self.flag_name = None
- self.feed(line)
- def handle_data(self, data):
- self.flag_name = data
- class FlagsSearchWidget(QWidget):
- raise_jump_to_flag_signal = pyqtSignal(str)
- def __init__(self, parent, flags):
- super().__init__(parent)
- self.search_index = get_flags_index(flags)
- vbox = QVBoxLayout(self)
- self.query_le = QLineEdit()
- self.query_le.setPlaceholderText("--- Search for flags here ---")
- self.query_le.textEdited.connect(self.query)
- vbox.addWidget(self.query_le)
- self.search_results_table = QTableWidget(self)
- self.search_results_table.setColumnCount(3)
- self.search_results_table.setHorizontalHeaderLabels(["Flag Name", "Flag Subgroup", "Flag Description"])
- self.search_results_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
- vbox.addWidget(self.search_results_table)
- self.flag_name_push_button_mapping_2way = {}
- @pyqtSlot(str, name="query and refresh")
- def query(self, text):
- self.flag_name_push_button_mapping_2way = {}
- highlights = query(index=self.search_index, query_str=text, max_results=20)
- self.search_results_table.clearContents()
- self.search_results_table.setRowCount(len(highlights))
- for ind, highlight in enumerate(highlights):
- self.search_results_table.setCellWidget(ind, 0, QLabel(highlight["flag_name"]))
- flag_name_parser = FlagNameParser(highlight["flag_name"])
- widget = QWidget()
- layout = QHBoxLayout(widget)
- layout.addWidget(QLabel(highlight["flag_subgroup"]))
- push_button = QPushButton("Go to flag")
- self.flag_name_push_button_mapping_2way[flag_name_parser.flag_name] = push_button
- self.flag_name_push_button_mapping_2way[push_button] = flag_name_parser.flag_name
- push_button.clicked.connect(self.raise_jump_to_flag)
- layout.addWidget(push_button)
- self.search_results_table.setCellWidget(ind, 1, widget)
- self.search_results_table.setCellWidget(ind, 2, QLabel(highlight["flag_description"]))
- self.search_results_table.resizeColumnsToContents()
- self.search_results_table.resizeRowsToContents()
- @pyqtSlot(name="raise jump to flag")
- def raise_jump_to_flag(self):
- sender = QObject.sender(self)
- self.raise_jump_to_flag_signal.emit(self.flag_name_push_button_mapping_2way[sender])
|