def populate(self): """ Populates the table of indexed strings """ self.widgets = [] # If no widgets, just drop that in attrs = [] for anum in range(self.data.numAttributes()): attr = self.data.attributeInfo(anum) if attr.type == partio.INDEXEDSTR: attrs.append(attr) if not attrs: self.table.hide() self.noStringsLabel.show() return self.table.show() self.noStringsLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(len(attrs)) for row, attr in enumerate(attrs): item = QTableWidgetItem(attr.name) self.table.setVerticalHeaderItem(row, item) strings = self.data.indexedStrs(attr) table = QTableWidget() table.setColumnCount(1) table.setRowCount(len(strings)) table.horizontalHeader().hide() table.setVerticalHeaderLabels( [str(i) for i in range(len(strings))]) for i, string in enumerate(strings): widget = QLabel(string) table.setCellWidget(i, 0, widget) self.widgets.append(widget) self.table.setCellWidget(row, 0, table) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections( QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections( QHeaderView.ResizeToContents)
def populate(self): """ Populates the table of indexed strings """ self.widgets = [] # If no widgets, just drop that in attrs = [] for anum in range(self.data.numAttributes()): attr = self.data.attributeInfo(anum) if attr.type == partio.INDEXEDSTR: attrs.append(attr) if not attrs: self.table.hide() self.noStringsLabel.show() return self.table.show() self.noStringsLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(len(attrs)) for row, attr in enumerate(attrs): item = QTableWidgetItem(attr.name) self.table.setVerticalHeaderItem(row, item) strings = self.data.indexedStrs(attr) table = QTableWidget() table.setColumnCount(1) table.setRowCount(len(strings)) table.horizontalHeader().hide() table.setVerticalHeaderLabels([str(i) for i in range(len(strings))]) for i, string in enumerate(strings): widget = QLabel(string) table.setCellWidget(i, 0, widget) self.widgets.append(widget) self.table.setCellWidget(row, 0, table) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections(QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections(QHeaderView.ResizeToContents)
class Variables(QWidget): changed = Signal(object) def __init__(self, parent=None): QWidget.__init__(self, parent) self.setMinimumWidth(500) self.table = QTableWidget() self.table.setSelectionMode(QTableWidget.SingleSelection) self.table.itemChanged.connect(self._item_changed) self.table.currentItemChanged.connect(self._current_item_changed) self.table.horizontalHeader().hide() self.table.verticalHeader().hide() self.table.setColumnCount(3) self.table.horizontalHeader().resizeSection(0, 200) self.table.horizontalHeader().resizeSection(1, 200) self.table.horizontalHeader().resizeSection(2, 20) self.new = QPushButton('New variable') self.new.clicked.connect(self._new) self.color = Color() self.color.changed.connect(self._color_changed) layout = QGridLayout(self) layout.addWidget(self.table) layout.addWidget(self.color) layout.addWidget(self.new) @property def variables(self): variables = dict() for row_index in range(self.table.rowCount()): name = self.table.item(row_index, 0).text() value = self.table.item(row_index, 1).text() if value.startswith('[') and value.endswith(']'): value = eval(value) # OULA LA variables[name] = value return variables @variables.setter def variables(self, variables): self.table.blockSignals(True) self.table.clear() for name, value in variables.items(): self._add_row(name, value) self.table.blockSignals(False) self.changed.emit(variables) def _item_changed(self, item): self.changed.emit(self.variables) self._current_item_changed(item) def _current_item_changed(self, item): self.color.setEnabled(False) if item is None or item.column() != 1: return text = item.text().strip() if not (text.startswith('[') and text.endswith(']')): return r, g, b = [int(channel.strip()) for channel in text[1:-1].split(',')] self.color.blockSignals(True) self.color.set_rgb(r, g, b) self.color.setEnabled(True) self.color.blockSignals(False) def _color_changed(self, r, g, b): self.table.blockSignals(True) self.table.currentItem().setText('[{}, {}, {}]'.format(r, g, b)) self.table.blockSignals(False) self.changed.emit(self.variables) def _add_row(self, name, value): row = self.table.rowCount() self.table.setRowCount(row + 1) name = QTableWidgetItem(name) value = QTableWidgetItem(str(value)) delete = QPushButton("X") delete.name_item = name delete.clicked.connect(self._delete) self.table.blockSignals(True) self.table.setItem(row, 0, name) self.table.setItem(row, 1, value) self.table.setCellWidget(row, 2, delete) self.table.blockSignals(False) def _new(self): self._add_row('new_variable', '[255, 255, 255]') self.changed.emit(self.variables) def _delete(self): name_item = self.sender().name_item self.table.removeRow(name_item.row()) self.changed.emit(self.variables)
class Thermochem(ToolInstance): """tool for calculating free energy corrections based on frequencies and energies associated with FileReaders there are two tabs: absolute and relative the absolute tab can be used to combine the thermo corrections from one FileReader with the energy of another the relative tab can do the same, but it prints the energies relative to those of another FileReader multiple molecule groups can be added (i.e. for reactions with multiple reactants and products) each molecule group can have multiple conformers the energies of these conformers are boltzmann weighted, and the boltzmann-weighted energy is used to calculate the energy of either the reference group or the 'other' group""" SESSION_ENDURING = False SESSION_SAVE = False help = "https://github.com/QChASM/SEQCROW/wiki/Process-Thermochemistry-Tool" theory_helper = { "Grimme's Quasi-RRHO": "https://doi.org/10.1002/chem.201200497", "Truhlar's Quasi-Harmonic": "https://doi.org/10.1021/jp205508z" } def __init__(self, session, name): super().__init__(session, name) self.display_name = "Thermochemistry" self.tool_window = MainToolWindow(self) self.settings = _ComputeThermoSettings(self.session, name) self.nrg_fr = {} self.thermo_fr = {} self.thermo_co = {} self._headers = [] self._data = [] self._build_ui() self.set_sp() self.set_thermo_mdl() self.calc_relative_thermo() def _build_ui(self): #each group has an empty widget at the bottom so they resize the way I want while also having the #labels where I want them layout = QGridLayout() self.tab_widget = QTabWidget() layout.addWidget(self.tab_widget) #layout for absolute thermo stuff absolute_widget = QWidget() absolute_layout = QGridLayout(absolute_widget) #box for sp sp_area_widget = QGroupBox("Single-point") sp_layout = QFormLayout(sp_area_widget) self.sp_selector = FilereaderComboBox(self.session, otherItems=['energy']) self.sp_selector.currentIndexChanged.connect(self.set_sp) sp_layout.addRow(self.sp_selector) self.sp_table = QTableWidget() self.sp_table.setColumnCount(3) self.sp_table.setShowGrid(False) self.sp_table.horizontalHeader().hide() self.sp_table.verticalHeader().hide() self.sp_table.setFrameShape(QTableWidget.NoFrame) self.sp_table.setSelectionMode(QTableWidget.NoSelection) self.sp_table.insertRow(0) sp_layout.addRow(self.sp_table) #box for thermo therm_area_widget = QGroupBox("Thermal corrections") thermo_layout = QFormLayout(therm_area_widget) self.thermo_selector = FilereaderComboBox(self.session, otherItems=['frequency']) self.thermo_selector.currentIndexChanged.connect(self.set_thermo_mdl) thermo_layout.addRow(self.thermo_selector) self.temperature_line = QDoubleSpinBox() self.temperature_line.setMaximum(2**31 - 1) self.temperature_line.setValue(298.15) self.temperature_line.setSingleStep(10) self.temperature_line.setSuffix(" K") self.temperature_line.setMinimum(0) self.temperature_line.valueChanged.connect(self.set_thermo) thermo_layout.addRow("T =", self.temperature_line) self.v0_edit = QDoubleSpinBox() self.v0_edit.setMaximum(4000) self.v0_edit.setValue(self.settings.w0) self.v0_edit.setSingleStep(25) self.v0_edit.setSuffix(" cm\u207b\u00b9") self.v0_edit.valueChanged.connect(self.set_thermo) self.v0_edit.setMinimum(0) self.v0_edit.setToolTip( "frequency parameter for quasi treatments of entropy") thermo_layout.addRow("𝜔<sub>0</sub> =", self.v0_edit) self.thermo_table = QTableWidget() self.thermo_table.setColumnCount(3) self.thermo_table.setShowGrid(False) self.thermo_table.horizontalHeader().hide() self.thermo_table.verticalHeader().hide() self.thermo_table.setFrameShape(QTableWidget.NoFrame) self.thermo_table.setSelectionMode(QTableWidget.NoSelection) thermo_layout.addRow(self.thermo_table) # for for total sum_area_widget = QGroupBox("Thermochemistry") sum_layout = QFormLayout(sum_area_widget) self.sum_table = QTableWidget() self.sum_table.setColumnCount(3) self.sum_table.setShowGrid(False) self.sum_table.horizontalHeader().hide() self.sum_table.verticalHeader().hide() self.sum_table.setFrameShape(QTableWidget.NoFrame) self.sum_table.setSelectionMode(QTableWidget.NoSelection) sum_layout.addRow(self.sum_table) splitter = QSplitter(Qt.Horizontal) splitter.setChildrenCollapsible(False) splitter.addWidget(sp_area_widget) splitter.addWidget(therm_area_widget) splitter.addWidget(sum_area_widget) absolute_layout.addWidget(splitter) self.status = QStatusBar() self.status.setSizeGripEnabled(False) self.status.setStyleSheet("color: red") absolute_layout.addWidget(self.status, 1, 0, 1, 1, Qt.AlignTop) self.tab_widget.addTab(absolute_widget, "absolute") relative_widget = QWidget() relative_layout = QGridLayout(relative_widget) size = [self.settings.ref_col_1, self.settings.ref_col_2] self.ref_group = ThermoGroup("reference group", self.session, self.nrg_fr, self.thermo_co, size) self.ref_group.changes.connect(self.calc_relative_thermo) relative_layout.addWidget(self.ref_group, 0, 0, 1, 3, Qt.AlignTop) size = [self.settings.other_col_1, self.settings.other_col_2] self.other_group = ThermoGroup("other group", self.session, self.nrg_fr, self.thermo_co, size) self.other_group.changes.connect(self.calc_relative_thermo) relative_layout.addWidget(self.other_group, 0, 3, 1, 3, Qt.AlignTop) self.relative_temperature = QDoubleSpinBox() self.relative_temperature.setMaximum(2**31 - 1) self.relative_temperature.setValue(self.settings.rel_temp) self.relative_temperature.setSingleStep(10) self.relative_temperature.setSuffix(" K") self.relative_temperature.setMinimum(0) self.relative_temperature.valueChanged.connect( self.calc_relative_thermo) relative_layout.addWidget(QLabel("T ="), 1, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) relative_layout.addWidget(self.relative_temperature, 1, 1, 1, 5, Qt.AlignLeft | Qt.AlignVCenter) self.relative_v0 = QDoubleSpinBox() self.relative_v0.setMaximum(2**31 - 1) self.relative_v0.setValue(self.settings.w0) self.relative_v0.setSingleStep(25) self.relative_v0.setSuffix(" cm\u207b\u00b9") self.relative_v0.setMinimum(0) self.relative_v0.setToolTip( "frequency parameter for quasi treatments of entropy") self.relative_v0.valueChanged.connect(self.calc_relative_thermo) relative_layout.addWidget(QLabel("𝜔<sub>0</sub> ="), 2, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) relative_layout.addWidget(self.relative_v0, 2, 1, 1, 5, Qt.AlignLeft | Qt.AlignVCenter) relative_layout.addWidget( QLabel("Boltzmann-weighted relative energies in kcal/mol:"), 3, 0, 1, 6, Qt.AlignVCenter | Qt.AlignLeft) self.relative_table = QTextBrowser() self.relative_table.setMaximumHeight( 4 * self.relative_table.fontMetrics().boundingRect("Q").height()) relative_layout.addWidget(self.relative_table, 4, 0, 1, 6, Qt.AlignTop) relative_layout.setRowStretch(0, 1) relative_layout.setRowStretch(1, 0) relative_layout.setRowStretch(2, 0) relative_layout.setRowStretch(3, 0) relative_layout.setRowStretch(4, 0) self.tab_widget.addTab(relative_widget, "relative") #menu stuff menu = QMenuBar() export = menu.addMenu("&Export") copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area) copy.triggered.connect(self.copy_csv) shortcut = QKeySequence(Qt.CTRL + Qt.Key_C) copy.setShortcut(shortcut) export.addAction(copy) self.copy = copy save = QAction("&Save CSV...", self.tool_window.ui_area) save.triggered.connect(self.save_csv) #this shortcut interferes with main window's save shortcut #I've tried different shortcut contexts to no avail #thanks Qt... #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S) #save.setShortcut(shortcut) #save.setShortcutContext(Qt.WidgetShortcut) export.addAction(save) delimiter = export.addMenu("Delimiter") comma = QAction("comma", self.tool_window.ui_area, checkable=True) comma.setChecked(self.settings.delimiter == "comma") comma.triggered.connect(lambda *args, delim="comma": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(comma) tab = QAction("tab", self.tool_window.ui_area, checkable=True) tab.setChecked(self.settings.delimiter == "tab") tab.triggered.connect(lambda *args, delim="tab": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(tab) space = QAction("space", self.tool_window.ui_area, checkable=True) space.setChecked(self.settings.delimiter == "space") space.triggered.connect(lambda *args, delim="space": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(space) semicolon = QAction("semicolon", self.tool_window.ui_area, checkable=True) semicolon.setChecked(self.settings.delimiter == "semicolon") semicolon.triggered.connect(lambda *args, delim="semicolon": self. settings.__setattr__("delimiter", delim)) delimiter.addAction(semicolon) add_header = QAction("&Include CSV header", self.tool_window.ui_area, checkable=True) add_header.setChecked(self.settings.include_header) add_header.triggered.connect(self.header_check) export.addAction(add_header) comma.triggered.connect( lambda *args, action=tab: action.setChecked(False)) comma.triggered.connect( lambda *args, action=space: action.setChecked(False)) comma.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) tab.triggered.connect( lambda *args, action=comma: action.setChecked(False)) tab.triggered.connect( lambda *args, action=space: action.setChecked(False)) tab.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) space.triggered.connect( lambda *args, action=comma: action.setChecked(False)) space.triggered.connect( lambda *args, action=tab: action.setChecked(False)) space.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=comma: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=tab: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=space: action.setChecked(False)) menu.setNativeMenuBar(False) self._menu = menu layout.setMenuBar(menu) menu.setVisible(True) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def calc_relative_thermo(self, *args): """changes the values on the 'relative' tab called when the tool is opened and whenever something changes on the relative tab""" def calc_free_energies(nrg_list, co_list, T, w0): """returns lists for ZPVE, H, RRHO G, QRRHO G, and QHARM G for the items in nrg_list and co_list at temperature T and frequency parameter w0""" ZPVEs = [] if all(co.frequency.anharm_data for co_group in co_list for co in co_group): anharm_ZVPEs = [] else: anharm_ZVPEs = None Hs = [] Gs = [] RRHOG = [] QHARMG = [] for i in range(0, len(nrg_list)): ZPVEs.append([]) if anharm_ZVPEs is not None: anharm_ZVPEs.append([]) Hs.append([]) Gs.append([]) RRHOG.append([]) QHARMG.append([]) for nrg, co in zip(nrg_list[i], co_list[i]): ZPVE = co.ZPVE ZPVEs[-1].append(nrg + ZPVE) if anharm_ZVPEs is not None: zpve_anharm = co.calc_zpe(anharmonic=True) anharm_ZVPEs[-1].append(nrg + zpve_anharm) dH = co.therm_corr(T, w0, CompOutput.RRHO)[1] Hs[-1].append(nrg + dH) dG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.RRHO) Gs[-1].append(nrg + dG) dQRRHOG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.QUASI_RRHO) RRHOG[-1].append(nrg + dQRRHOG) dQHARMG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.QUASI_HARMONIC) QHARMG[-1].append(nrg + dQHARMG) return ZPVEs, anharm_ZVPEs, Hs, Gs, RRHOG, QHARMG def boltzmann_weight(energies1, energies2, T): """ energies - list of lists list axis 0 - molecule groups axis 1 - energies of conformers boltzmann weight energies for conformers combine energies for molecule groups return the difference""" totals1 = [] totals2 = [] beta = UNIT.HART_TO_JOULE / (PHYSICAL.KB * T) for energy_group in energies1: if len(energy_group) == 0: continue rezero = min(energy_group) rel_nrgs = [(x - rezero) for x in energy_group] weights = [np.exp(-beta * nrg) for nrg in rel_nrgs] totals1.append(-PHYSICAL.BOLTZMANN * T * np.log(sum(weights)) + rezero * UNIT.HART_TO_KCAL) for energy_group in energies2: if len(energy_group) == 0: continue rezero = min(energy_group) rel_nrgs = [(x - rezero) for x in energy_group] weights = [np.exp(-beta * nrg) for nrg in rel_nrgs] totals2.append(-PHYSICAL.BOLTZMANN * T * np.log(sum(weights)) + rezero * UNIT.HART_TO_KCAL) return sum(totals2) - sum(totals1) ref_Es = self.ref_group.energies() ref_cos = self.ref_group.compOutputs() other_Es = self.other_group.energies() other_cos = self.other_group.compOutputs() if any( len(x) == 0 or all(len(y) == 0 for y in x) for x in [ref_Es, other_Es]): self.relative_table.setText("not enough data") return T = self.relative_temperature.value() self.settings.rel_temp = T w0 = self.relative_v0.value() if w0 != self.settings.w0: self.settings.w0 = w0 rel_E = boltzmann_weight(ref_Es, other_Es, T) headers = ["ΔE"] data = [rel_E] empty_groups = [] for i, group in enumerate(ref_cos): if len(group) == 0: empty_groups.append(i) for ndx in empty_groups[::-1]: ref_Es.pop(ndx) ref_cos.pop(ndx) empty_groups = [] for i, group in enumerate(other_cos): if len(group) == 0: empty_groups.append(i) for ndx in empty_groups[::-1]: other_Es.pop(ndx) other_cos.pop(ndx) if not any( len(x) == 0 or all(len(y) == 0 for y in x) for x in [ref_cos, other_cos]): rel_E = boltzmann_weight(ref_Es, other_Es, T) data = [rel_E] ref_ZPVEs, ref_anharm_zpve, ref_Hs, ref_Gs, ref_QRRHOGs, ref_QHARMGs = calc_free_energies( ref_Es, ref_cos, T, w0) other_ZPVEs, other_anharm_zpve, other_Hs, other_Gs, other_QRRHOGs, other_QHARMGs = calc_free_energies( other_Es, other_cos, T, w0) rel_ZPVE = boltzmann_weight(ref_ZPVEs, other_ZPVEs, T) rel_H = boltzmann_weight(ref_Hs, other_Hs, T) rel_G = boltzmann_weight(ref_Gs, other_Gs, T) rel_QRRHOG = boltzmann_weight(ref_QRRHOGs, other_QRRHOGs, T) rel_QHARMG = boltzmann_weight(ref_QHARMGs, other_QHARMGs, T) if ref_anharm_zpve and other_anharm_zpve: rel_anharm_ZPVE = boltzmann_weight(ref_anharm_zpve, other_anharm_zpve, T) headers.extend([ "ΔZPE", "ΔZPE<sub>anh</sub>", "ΔH<sub>RRHO</sub>", "ΔG<sub>RRHO</sub>", "ΔG<sub>Quasi-RRHO</sub>", "ΔG<sub>Quasi-Harmonic</sub>", ]) data.extend([ rel_ZPVE, rel_anharm_ZPVE, rel_H, rel_G, rel_QRRHOG, rel_QHARMG, ]) else: headers.extend([ "ΔZPE", "ΔH<sub>RRHO</sub>", "ΔG<sub>RRHO</sub>", "ΔG<sub>Quasi-RRHO</sub>", "ΔG<sub>Quasi-Harmonic</sub>", ]) data.extend([ rel_ZPVE, rel_H, rel_G, rel_QRRHOG, rel_QHARMG, ]) self._headers = headers self._data = data s = "<table style=\"border: 1px solid ghostwhite;\">\n" s += "\t<tr>\n" for header in headers: s += "\t\t<th style=\"text-align: center; border: 1px solid ghostwhite; padding-left: 5px; padding-right: 5px\">%s</th>\n" % header s += "\t</tr>\n" s += "\t<tr>\n" for val in data: s += "\t\t<td style=\"text-align: center; border: 1px solid ghostwhite; padding-left: 5px; padding-right: 5px\">%.1f</td>\n" % val s += "\t</tr>\n" s += "</table>" self.relative_table.setText(s) def open_link(self, theory): """open the oft-cited QRRHO or QHARM reference""" link = self.theory_helper[theory] run(self.session, "open %s" % link) def save_csv(self): """save data on current tab to CSV file""" filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)") if filename: s = self.get_csv() with open(filename, 'w') as f: f.write(s.strip()) self.session.logger.status("saved to %s" % filename) def copy_csv(self): """put CSV data for current tab on the clipboard""" app = QApplication.instance() clipboard = app.clipboard() csv = self.get_csv() clipboard.setText(csv) self.session.logger.status("copied to clipboard") def get_csv(self): """get CSV data for the current tab""" if self.settings.delimiter == "comma": delim = "," elif self.settings.delimiter == "space": delim = " " elif self.settings.delimiter == "tab": delim = "\t" elif self.settings.delimiter == "semicolon": delim = ";" if self.tab_widget.currentIndex() == 0: s = "" if self.settings.include_header: link_re = QRegularExpression("<a[\d\D]*>(𝛿?Δ?[\d\D]*)</a>") # get header labels from the tables for table in [ self.sp_table, self.thermo_table, self.sum_table ]: for row in range(0, table.rowCount()): item = table.cellWidget(row, 0) text = item.text() text = text.replace(" =", "") text = text.replace("<sub>", "(") text = text.replace("</sub>", ")") # if there's a link like for QRRHO G, remove the link if "href=" in text: match = link_re.match(text) text = match.captured(1) text = text.replace("𝛿", "d") s += "%s%s" % (text, delim) s += "SP_File%sThermo_File\n" % delim # get values from tables for table in [self.sp_table, self.thermo_table, self.sum_table]: for row in range(0, table.rowCount()): item = table.cellWidget(row, 1) s += "%s%s" % (item.text(), delim) sp_mdl = self.sp_selector.currentData() sp_name = sp_mdl.name therm_mdl = self.thermo_selector.currentData() s += "%s" % sp_name if therm_mdl: therm_name = therm_mdl.name s += "%s%s" % (delim, therm_name) s += "\n" elif self.tab_widget.currentIndex() == 1: s = "" if self.settings.include_header: link_re = QRegularExpression("<a[\d\D]*>(𝛿?Δ?[\d\D]*)</a>") # get header labels from the tables for text in self._headers: text = text.replace(" =", "") text = text.replace("<sub>", "(") text = text.replace("</sub>", ")") # if there's a link like for QRRHO G, remove the link if "href=" in text: match = link_re.match(text) text = match.captured(1) text = text.replace("𝛿", "d") s += "%s%s" % (text, delim) s = s.rstrip(delim) s += "\n" s += delim.join(["%.1f" % val for val in self._data]) return s def header_check(self, state): """user has [un]checked the 'include header' option on the menu""" if state: self.settings.include_header = True else: self.settings.include_header = False def set_sp(self): """set energy entry for when sp model changes""" self.sp_table.setRowCount(0) if self.sp_selector.currentIndex() >= 0: fr = self.sp_selector.currentData() self.check_geometry_rmsd("SP") self.sp_table.insertRow(0) nrg_label = QLabel("E =") nrg_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.sp_table.setCellWidget(0, 0, nrg_label) unit_label = ReadOnlyTableItem() unit_label.setData(Qt.DisplayRole, "E\u2095") unit_label.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.sp_table.setItem(0, 2, unit_label) sp_nrg = SmallLineEdit("%.6f" % fr.other['energy']) sp_nrg.setReadOnly(True) sp_nrg.setFrame(False) self.sp_table.setCellWidget(0, 1, sp_nrg) self.sp_table.resizeRowToContents(0) self.sp_table.resizeColumnToContents(0) self.sp_table.resizeColumnToContents(1) self.sp_table.resizeColumnToContents(2) self.update_sum() def set_thermo_mdl(self): """ frequencies filereader has changed on the absolute tab also changes the temperature option (on the absolute tab only) """ if self.thermo_selector.currentIndex() >= 0: fr = self.thermo_selector.currentData() self.check_geometry_rmsd("THERM") if 'temperature' in fr.other: self.temperature_line.setValue(fr.other['temperature']) self.set_thermo() def check_geometry_rmsd(self, *args): """check RMSD between energy and frequency filereader on the absolute tab if the RMSD is > 10^-5 or the number of atoms is different, put a warning in the status bar""" if self.thermo_selector.currentIndex( ) >= 0 and self.sp_selector.currentIndex() >= 0: fr = self.sp_selector.currentData() fr2 = self.thermo_selector.currentData() if len(fr.atoms) != len(fr2.atoms): self.status.showMessage( "structures are not the same: different number of atoms") return geom = Geometry(fr) geom2 = Geometry(fr2) rmsd = geom.RMSD(geom2) if not isclose(rmsd, 0, atol=10**-5): rmsd = geom.RMSD(geom2, sort=True) if not isclose(rmsd, 0, atol=10**-5): self.status.showMessage( "structures might not be the same - RMSD = %.4f" % rmsd) else: self.status.showMessage("") def set_thermo(self): """computes thermo corrections and sets thermo entries for when thermo model changes""" #index of combobox is -1 when combobox has no entries self.thermo_table.setRowCount(0) if self.thermo_selector.currentIndex() >= 0: fr = self.thermo_selector.currentData() if fr not in self.thermo_co: self.thermo_co[fr] = CompOutput(fr) co = self.thermo_co[fr] v0 = self.v0_edit.value() if v0 != self.settings.w0: self.settings.w0 = v0 T = self.temperature_line.value() if not T: return dZPE = co.ZPVE #compute enthalpy and entropy at this temperature #AaronTools uses Grimme's Quasi-RRHO, but this is the same as RRHO when w0=0 dE, dH, s = co.therm_corr(temperature=T, v0=0, method="RRHO") rrho_dg = dH - T * s #compute G with quasi entropy treatments qrrho_dg = co.calc_G_corr(v0=v0, temperature=T, method="QRRHO") qharm_dg = co.calc_G_corr(v0=v0, temperature=T, method="QHARM") items = [( "𝛿ZPE =", dZPE, None, "lowest energy the molecule can have\n" "no rotational or vibrational modes populated\n" "equal to enthalpy at 0 K", )] if fr.other["frequency"].anharm_data: dZPE_anh = co.calc_zpe(anharmonic=True) items.append(( "𝛿ZPE<sub>anh</sub> =", dZPE_anh, None, "lowest energy the molecule can have\n" "no rotational or vibrational modes populated\n" "includes corrections for anharmonic vibrations", )) items.extend([ ( "𝛿H<sub>RRHO</sub> =", dH, None, "enthalpy of formation", ), ( "𝛿G<sub>RRHO</sub> =", rrho_dg, None, "energy after taking into account the average\n" "population of vibrational, rotational, and translational\n" "degrees of freedom", ), ( "𝛿G<sub>Quasi-RRHO</sub> =", qrrho_dg, "Grimme's Quasi-RRHO", "vibrational entropy of each real mode is damped and complemented\n" "with rotational entropy, with the damping function being stronger for\n" "frequencies < 𝜔\u2080\n" "can mitigate error from inaccuracies in the harmonic oscillator\n" "approximation for low-frequency vibrations", ), ( "𝛿G<sub>Quasi-Harmonic</sub> =", qharm_dg, "Truhlar's Quasi-Harmonic", "real vibrational frequencies below 𝜔\u2080 are treated as 𝜔\u2080\n" "can mitigate error from inaccuracies in the harmonic oscillator\n" "approximation for low-frequency vibrations", ), ]) for i, (label_text, val, link, tooltip) in enumerate(items): self.thermo_table.insertRow(i) label = QLabel(label_text) if link: label = QLabel() label.setText( "<a href=\"%s\" style=\"text-decoration: none;\">%s</a>" % (link, label_text)) label.setTextFormat(Qt.RichText) label.setTextInteractionFlags(Qt.TextBrowserInteraction) label.linkActivated.connect(self.open_link) label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) label.setToolTip(tooltip) self.thermo_table.setCellWidget(i, 0, label) unit_label = ReadOnlyTableItem() unit_label.setData(Qt.DisplayRole, "E\u2095") unit_label.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) unit_label.setToolTip(tooltip) self.thermo_table.setItem(i, 2, unit_label) d_nrg = SmallLineEdit("%.6f" % val) d_nrg.setReadOnly(True) d_nrg.setFrame(False) d_nrg.setToolTip(tooltip) self.thermo_table.setCellWidget(i, 1, d_nrg) self.thermo_table.resizeRowToContents(i) self.thermo_table.resizeColumnToContents(0) self.thermo_table.resizeColumnToContents(1) self.thermo_table.resizeColumnToContents(2) self.update_sum() def update_sum(self): """updates the sum of energy and thermo corrections""" self.sum_table.setRowCount(0) if not self.sp_table.rowCount(): return sp_nrg = float(self.sp_table.cellWidget(0, 1).text()) for row in range(0, self.thermo_table.rowCount()): self.sum_table.insertRow(row) label = self.thermo_table.cellWidget(row, 0) tooltip = label.toolTip() text = label.text().replace("𝛿", "") sum_label = QLabel(text) if "href=" in text: sum_label = QLabel() sum_label.setText(text) sum_label.setTextFormat(Qt.RichText) sum_label.setTextInteractionFlags(Qt.TextBrowserInteraction) sum_label.linkActivated.connect(self.open_link) sum_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) sum_label.setToolTip(tooltip) self.sum_table.setCellWidget(row, 0, sum_label) thermo = float(self.thermo_table.cellWidget(row, 1).text()) total = sp_nrg + thermo total_nrg = SmallLineEdit("%.6f" % total) total_nrg.setFrame(False) total_nrg.setReadOnly(True) total_nrg.setToolTip(tooltip) self.sum_table.setCellWidget(row, 1, total_nrg) unit_label = ReadOnlyTableItem() unit_label.setData(Qt.DisplayRole, "E\u2095") unit_label.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) unit_label.setToolTip(tooltip) self.sum_table.setItem(row, 2, unit_label) self.sum_table.resizeRowToContents(row) self.sum_table.resizeColumnToContents(0) self.sum_table.resizeColumnToContents(1) self.sum_table.resizeColumnToContents(2) def display_help(self): """Show the help for this tool in the help viewer.""" from chimerax.core.commands import run run(self.session, 'open %s' % self.help if self.help is not None else "") def delete(self): #overload because closing a tool window doesn't destroy any widgets on it self.settings.ref_col_1 = self.ref_group.tree.columnWidth(0) self.settings.ref_col_2 = self.ref_group.tree.columnWidth(1) self.settings.other_col_1 = self.other_group.tree.columnWidth(0) self.settings.other_col_2 = self.other_group.tree.columnWidth(1) self.sp_selector.deleteLater() self.thermo_selector.deleteLater() self.ref_group.deleteLater() self.other_group.deleteLater() return super().delete() def close(self): #overload because closing a tool window doesn't destroy any widgets on it self.settings.ref_col_1 = self.ref_group.tree.columnWidth(0) self.settings.ref_col_2 = self.ref_group.tree.columnWidth(1) self.settings.other_col_1 = self.other_group.tree.columnWidth(0) self.settings.other_col_2 = self.other_group.tree.columnWidth(1) self.sp_selector.deleteLater() self.thermo_selector.deleteLater() self.ref_group.deleteLater() self.other_group.deleteLater() return super().close()
class IndexedStringsWidget(QWidget): """ Holds the list of indexed string attributes """ def __init__(self, data, parent=None): QWidget.__init__(self, parent) self.data = data vbox = QVBoxLayout() self.setLayout(vbox) title = QLabel('Indexed Strings') vbox.addWidget(title) self.frame = QFrame() vbox.addWidget(self.frame) self.vbox = QVBoxLayout() self.frame.setLayout(self.vbox) self.frame.setFrameShape(QFrame.Panel) self.frame.setFrameShadow(QFrame.Sunken) self.table = QTableWidget() self.table.horizontalHeader().hide() self.vbox.addWidget(self.table) self.table.hide() self.noStringsLabel = QLabel('<i>No indexed strings</i>') self.vbox.addWidget(self.noStringsLabel) self.widgets = [] self.populate() self.data.attributeAdded.connect(self.attributeAddedSlot) self.data.dataReset.connect(self.dataResetSlot) self.data.dirtied.connect(self.dataDirtiedSlot) def dataDirtiedSlot(self, dirty): """ SLOT when the particle data is dirtied or cleaned.""" if not dirty: for widget in self.widgets: widget.drawBorder(False) def dataResetSlot(self): """ SLOT when particle data is reconstructed """ self.populate() def attributeAddedSlot(self, name): #pylint:disable=W0613 """ SLOT when an attribute is added to the particle set """ attr = self.data.attributeInfo(name) if attr.type == partio.INDEXEDSTR: self.populate() def populate(self): """ Populates the table of indexed strings """ self.widgets = [] # If no widgets, just drop that in attrs = [] for anum in range(self.data.numAttributes()): attr = self.data.attributeInfo(anum) if attr.type == partio.INDEXEDSTR: attrs.append(attr) if not attrs: self.table.hide() self.noStringsLabel.show() return self.table.show() self.noStringsLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(len(attrs)) for row, attr in enumerate(attrs): item = QTableWidgetItem(attr.name) self.table.setVerticalHeaderItem(row, item) strings = self.data.indexedStrs(attr) table = QTableWidget() table.setColumnCount(1) table.setRowCount(len(strings)) table.horizontalHeader().hide() table.setVerticalHeaderLabels( [str(i) for i in range(len(strings))]) for i, string in enumerate(strings): widget = QLabel(string) table.setCellWidget(i, 0, widget) self.widgets.append(widget) self.table.setCellWidget(row, 0, table) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections( QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections( QHeaderView.ResizeToContents)
class FixedAttributesWidget(QWidget): """ A widget for viewing/editing fixed attributes (non-varying) """ def __init__(self, data, parent=None): QWidget.__init__(self, parent) self.data = data vbox = QVBoxLayout() self.setLayout(vbox) title = QLabel('Fixed Attributes') vbox.addWidget(title) self.frame = QFrame() vbox.addWidget(self.frame) self.vbox = QVBoxLayout() self.frame.setLayout(self.vbox) self.frame.setFrameShape(QFrame.Panel) self.frame.setFrameShadow(QFrame.Sunken) self.table = QTableWidget() self.table.horizontalHeader().hide() self.vbox.addWidget(self.table) self.table.hide() self.noAttrLabel = QLabel('<i>No fixed attributes</i>') self.vbox.addWidget(self.noAttrLabel) self.widgets = [] self.populate() self.data.fixedAttributeAdded.connect(self.fixedAttributeAddedSlot) self.data.dataReset.connect(self.dataResetSlot) self.data.dirtied.connect(self.dataDirtiedSlot) def dataDirtiedSlot(self, dirty): """ SLOT when the particle data is dirtied or cleaned.""" if not dirty: for widget in self.widgets: widget.drawBorder(False) def dataResetSlot(self): """ SLOT when particle data is reconstructed """ self.populate() def fixedAttributeAddedSlot(self, name): #pylint:disable=W0613 """ SLOT when a fixed attribute is added to the particle set """ self.populate() def populate(self): """ Populates the table of fixed attributes """ self.widgets = [] # If no widgets, just drop that in numAttrs = self.data.numFixedAttributes() if not numAttrs: self.table.hide() self.noAttrLabel.show() return self.table.show() self.noAttrLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(numAttrs) self.attrs = getAttrs(self.data.numFixedAttributes, self.data.fixedAttributeInfo, True) for row, (_, attr) in enumerate(self.attrs): item = QTableWidgetItem(attr.name) tooltip = '<p><tt> Name: {}<br> Type: {}<br>Count: {}</tt></p>'.\ format(attr.name, partio.TypeName(attr.type), attr.count) item.setToolTip(tooltip) self.table.setVerticalHeaderItem(row, item) value = self.data.getFixed(attr) widget = getWidget(value, self.data, attr) self.table.setCellWidget(row, 0, widget) self.widgets.append(widget) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections( QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections( QHeaderView.ResizeToContents)
class TestRunner(ToolInstance): def __init__(self, session, name): super().__init__(session, name) self.tool_window = MainToolWindow(self) self._build_ui() def _build_ui(self): """ ui should have: * table with a list of available tests and show results after they are done * way to filter tests * button to run tests """ layout = QFormLayout() # table to list test classes and the results self.table = QTableWidget() self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(["test", "result"]) self.table.horizontalHeader().setSectionResizeMode( 0, self.table.horizontalHeader().Interactive) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.horizontalHeader().setSectionResizeMode( 1, self.table.horizontalHeader().Stretch) self.table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.table.setSelectionBehavior(self.table.SelectRows) layout.addRow(self.table) self.filter = QLineEdit() self.filter.setPlaceholderText("filter test names") self.filter.setClearButtonEnabled(True) self.filter.textChanged.connect(self.apply_filter) layout.addRow(self.filter) self.profile = QCheckBox() self.profile.setToolTip("profile functions called during testing " "using cProfile") layout.addRow("profile calls:", self.profile) self.run_button = QPushButton("run tests") self.run_button.clicked.connect(self.run_tests) self.run_button.setToolTip( "if no tests are selected on the table, run all tests\n" + "otherwise, run selected tests") layout.addRow(self.run_button) self.fill_table() self.table.resizeColumnToContents(0) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def apply_filter(self, text=None): """filter table to only show tests matching text""" if text is None: text = self.filter.text() if text: text = text.replace("(", "\(") text = text.replace(")", "\)") m = QRegularExpression(text) m.setPatternOptions(QRegularExpression.CaseInsensitiveOption) if m.isValid(): m.optimize() filter = lambda row_num: m.match( self.table.item(row_num, 0).text()).hasMatch() else: return else: filter = lambda row: True for i in range(0, self.table.rowCount()): self.table.setRowHidden(i, not filter(i)) def fill_table(self): """adds test names to the table""" mgr = self.session.test_manager for name in mgr.tests.keys(): row = self.table.rowCount() self.table.insertRow(row) test_name = QTableWidgetItem() test_name.setData(Qt.DisplayRole, name) self.table.setItem(row, 0, test_name) def run_tests(self): """run the tests selected on the table and show the results""" from TestManager.commands.test import test test_list = [] use_selected = True for row in self.table.selectionModel().selectedRows(): if self.table.isRowHidden(row.row()): continue test_name = self.table.item(row.row(), 0).text() test_list.append(test_name) if not test_list: use_selected = False for i in range(0, self.table.rowCount()): if self.table.isRowHidden(i): continue test_name = self.table.item(i, 0).text() test_list.append(test_name) if not test_list: test_list = ["all"] results, stats = test(self.session, test_list, profile=self.profile.checkState() == Qt.Checked) cell_widgets = [] for name in results: widget = QWidget() widget_layout = QHBoxLayout(widget) widget_layout.setContentsMargins(0, 0, 0, 0) success_button = get_button("success") fail_button = get_button("fail") skip_button = get_button("skip") error_button = get_button("error") expected_fail_button = get_button("expected fail") unexpected_success_button = get_button("unexpected success") success_count = 0 fail_count = 0 error_count = 0 unexpected_success_count = 0 expected_fail_count = 0 skip_count = 0 success_tooltip = "Successes:\n" fail_tooltip = "Failed tests:\n" error_tooltip = "Errors during test:\n" unexpected_success_tooltip = "Unexpected successes:\n" expected_fail_tooltip = "Expected fails:\n" skip_tooltip = "Skipped tests:\n" for case in results[name]: result, msg = results[name][case] if result == "success": success_count += 1 success_tooltip += "%s.%s: %s\n" % ( case.__class__.__qualname__, case._testMethodName, msg) elif result == "fail": fail_count += 1 fail_tooltip += "%s.%s failed: %s\n" % ( case.__class__.__qualname__, case._testMethodName, msg) elif result == "error": error_count += 1 error_tooltip += "error during %s.%s: %s\n" % ( case.__class__.__qualname__, case._testMethodName, msg) elif result == "expected_failure": expected_fail_count += 1 expected_fail_tooltip += "intended failure during %s.%s: %s\n" % ( case.__class__.__qualname__, case._testMethodName, msg) elif result == "skip": skip_count += 1 skip_tooltip += "%s.%s\n" % (case.__class__.__qualname__, case._testMethodName) elif result == "unexpected_success": unexpected_success_count += 1 unexpected_success_tooltip += "%s.%s should not have worked, but did\n" % ( case.__class__.__qualname__, case._testMethodName) success_tooltip = success_tooltip.strip() fail_tooltip = fail_tooltip.strip() error_tooltip = error_tooltip.strip() expected_fail_tooltip = expected_fail_tooltip.strip() skip_tooltip = skip_tooltip.strip() unexpected_success_tooltip = unexpected_success_tooltip.strip() icon_count = 0 if success_count: success_button.setText("%i" % success_count) success_button.setToolTip(success_tooltip) success_button.clicked.connect( lambda *args, t_name=name, res=success_tooltip: self. tool_window.create_child_window( "successes for %s" % t_name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, success_button, 1) icon_count += 1 if fail_count: fail_button.setText("%i" % fail_count) fail_button.setToolTip(fail_tooltip) fail_button.clicked.connect( lambda *args, res=fail_tooltip: self.tool_window. create_child_window( "failures for %s" % name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, fail_button, 1) icon_count += 1 if error_count: error_button.setText("%i" % error_count) error_button.setToolTip(error_tooltip) error_button.clicked.connect( lambda *args, res=error_tooltip: self.tool_window. create_child_window( "errors for %s" % name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, error_button, 1) icon_count += 1 if unexpected_success_count: unexpected_success_button.setText("%i" % unexpected_success_count) unexpected_success_button.setToolTip( unexpected_success_tooltip) unexpected_success_button.clicked.connect( lambda *args, res=unexpected_success_tooltip: self. tool_window.create_child_window( "unexpected successes for %s" % name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, unexpected_success_button, 1) icon_count += 1 if expected_fail_count: expected_fail_button.setText("%i" % expected_fail_count) expected_fail_button.setToolTip(expected_fail_tooltip) expected_fail_button.clicked.connect( lambda *args, res=expected_fail_tooltip: self.tool_window. create_child_window( "expected failures for %s" % name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, expected_fail_button, 1) icon_count += 1 if skip_count: skip_button.setText("%i" % skip_count) skip_button.setToolTip(skip_tooltip) skip_button.clicked.connect( lambda *args, res=skip_tooltip: self.tool_window. create_child_window( "skipped tests for %s" % name, text=res, window_class=ResultsWindow, )) widget_layout.insertWidget(icon_count, skip_button, 1) cell_widgets.append(widget) widget_count = 0 if use_selected: for row in self.table.selectionModel().selectedRows(): if self.table.isRowHidden(row.row()): continue self.table.setCellWidget(row.row(), 1, cell_widgets[widget_count]) self.table.resizeRowToContents(row.row()) widget_count += 1 else: for i in range(0, self.table.rowCount()): if self.table.isRowHidden(i): continue self.table.setCellWidget(i, 1, cell_widgets[widget_count]) # self.table.resizeRowToContents(i) widget_count += 1 if self.profile.checkState() == Qt.Checked: self.tool_window.create_child_window("stats", text=stats, window_class=ResultsWindow)
class Info(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/File-Info-Tool" def __init__(self, session, name): super().__init__(session, name) self.tool_window = MainToolWindow(self) self.settings = _InfoSettings(self.session, name) self._build_ui() def _build_ui(self): layout = QVBoxLayout() self.file_selector = FilereaderComboBox(self.session) self.file_selector.currentIndexChanged.connect(self.fill_table) layout.insertWidget(0, self.file_selector, 0) tabs = QTabWidget() self.tabs = tabs layout.insertWidget(1, self.tabs, 1) general_info = QWidget() general_layout = QVBoxLayout(general_info) tabs.addTab(general_info, "general") self.table = QTableWidget() self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(['Data', 'Value']) self.table.horizontalHeader().setStretchLastSection(False) self.table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Interactive) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) general_layout.insertWidget(1, self.table, 1) self.filter = QLineEdit() self.filter.setPlaceholderText("filter data") self.filter.textChanged.connect(self.apply_filter) self.filter.setClearButtonEnabled(True) general_layout.insertWidget(2, self.filter, 0) freq_info = QWidget() freq_layout = QVBoxLayout(freq_info) tabs.addTab(freq_info, "harmonic frequencies") self.freq_table = QTableWidget() self.freq_table.setColumnCount(4) self.freq_table.setHorizontalHeaderLabels([ "Frequency (cm\u207b\u00b9)", "symmetry", "IR intensity", "force constant (mDyne/\u212B)", ]) self.freq_table.setSortingEnabled(True) self.freq_table.setEditTriggers(QTableWidget.NoEditTriggers) for i in range(0, 4): self.freq_table.resizeColumnToContents(i) self.freq_table.horizontalHeader().setStretchLastSection(False) self.freq_table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Fixed) self.freq_table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.freq_table.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Fixed) self.freq_table.horizontalHeader().setSectionResizeMode( 3, QHeaderView.Stretch) self.freq_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) freq_layout.insertWidget(1, self.freq_table, 1) anharm_info = QWidget() anharm_layout = QVBoxLayout(anharm_info) tabs.addTab(anharm_info, "anharmonic frequencies") anharm_layout.insertWidget(0, QLabel("fundamentals:"), 0) self.fundamental_table = QTableWidget() self.fundamental_table.setColumnCount(3) self.fundamental_table.setHorizontalHeaderLabels([ "Fundamental (cm\u207b\u00b9)", "Δ\u2090\u2099\u2095 (cm\u207b\u00b9)", "IR intensity", ]) self.fundamental_table.setSortingEnabled(True) self.fundamental_table.setEditTriggers(QTableWidget.NoEditTriggers) for i in range(0, 3): self.fundamental_table.resizeColumnToContents(i) self.fundamental_table.horizontalHeader().setStretchLastSection(False) self.fundamental_table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Fixed) self.fundamental_table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.fundamental_table.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Stretch) self.fundamental_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) anharm_layout.insertWidget(1, self.fundamental_table, 1) # self.overtone_table = QTableWidget() # self.overtone_table.setColumnCount(3) # self.overtone_table.setHorizontalHeaderLabels( # [ # "Fundamental (cm\u207b\u00b9)", # "Overtone (cm\u207b\u00b9)", # "IR intensity", # ] # ) # self.overtone_table.setSortingEnabled(True) # self.overtone_table.setEditTriggers(QTableWidget.NoEditTriggers) # for i in range(0, 3): # self.overtone_table.resizeColumnToContents(i) # # self.overtone_table.horizontalHeader().setStretchLastSection(False) # self.overtone_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) # self.overtone_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Fixed) # self.overtone_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) # # self.overtone_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # anharm_layout.insertWidget(2, self.overtone_table, 1) anharm_layout.insertWidget(2, QLabel("combinations and overtones:"), 0) self.combo_table = QTableWidget() self.combo_table.setColumnCount(4) self.combo_table.setHorizontalHeaderLabels([ "Fundamental (cm\u207b\u00b9)", "Fundamental (cm\u207b\u00b9)", "Combination (cm\u207b\u00b9)", "IR intensity", ]) self.combo_table.setSortingEnabled(True) self.combo_table.setEditTriggers(QTableWidget.NoEditTriggers) for i in range(0, 3): self.combo_table.resizeColumnToContents(i) self.combo_table.horizontalHeader().setStretchLastSection(False) self.combo_table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Fixed) self.combo_table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.combo_table.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Fixed) self.combo_table.horizontalHeader().setSectionResizeMode( 3, QHeaderView.Stretch) self.combo_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) anharm_layout.insertWidget(3, self.combo_table, 1) menu = QMenuBar() export = menu.addMenu("&Export") copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area) copy.triggered.connect(self.copy_csv) shortcut = QKeySequence(Qt.CTRL + Qt.Key_C) copy.setShortcut(shortcut) export.addAction(copy) self.copy = copy save = QAction("&Save CSV...", self.tool_window.ui_area) save.triggered.connect(self.save_csv) export.addAction(save) delimiter = export.addMenu("Delimiter") comma = QAction("comma", self.tool_window.ui_area, checkable=True) comma.setChecked(self.settings.delimiter == "comma") comma.triggered.connect(lambda *args, delim="comma": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(comma) tab = QAction("tab", self.tool_window.ui_area, checkable=True) tab.setChecked(self.settings.delimiter == "tab") tab.triggered.connect(lambda *args, delim="tab": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(tab) # space = QAction("space", self.tool_window.ui_area, checkable=True) # space.setChecked(self.settings.delimiter == "space") # space.triggered.connect(lambda *args, delim="space": self.settings.__setattr__("delimiter", delim)) # delimiter.addAction(space) semicolon = QAction("semicolon", self.tool_window.ui_area, checkable=True) semicolon.setChecked(self.settings.delimiter == "semicolon") semicolon.triggered.connect(lambda *args, delim="semicolon": self. settings.__setattr__("delimiter", delim)) delimiter.addAction(semicolon) add_header = QAction("&Include CSV header", self.tool_window.ui_area, checkable=True) add_header.setChecked(self.settings.include_header) add_header.triggered.connect(self.header_check) export.addAction(add_header) tab.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=tab: action.setChecked(False)) archive = QAction("Include archive if present", self.tool_window.ui_area, checkable=True) archive.triggered.connect( lambda checked: setattr(self.settings, "archive", checked)) archive.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) archive.setChecked(self.settings.archive) export.addAction(archive) unit = menu.addMenu("&Units") energy = unit.addMenu("energy") hartree = QAction("Hartree", self.tool_window.ui_area, checkable=True) hartree.setChecked(self.settings.energy == "Hartree") kcal = QAction("kcal/mol", self.tool_window.ui_area, checkable=True) kcal.setChecked(self.settings.energy == "kcal/mol") kjoule = QAction("kJ/mol", self.tool_window.ui_area, checkable=True) kjoule.setChecked(self.settings.energy == "kJ/mol") energy.addAction(hartree) energy.addAction(kcal) energy.addAction(kjoule) hartree.triggered.connect( lambda *args, val="Hartree": setattr(self.settings, "energy", val)) hartree.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) hartree.triggered.connect( lambda *args, action=kcal: action.setChecked(False)) hartree.triggered.connect( lambda *args, action=kjoule: action.setChecked(False)) kcal.triggered.connect(lambda *args, val="kcal/mol": setattr( self.settings, "energy", val)) kcal.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) kcal.triggered.connect( lambda *args, action=hartree: action.setChecked(False)) kcal.triggered.connect( lambda *args, action=kjoule: action.setChecked(False)) kjoule.triggered.connect( lambda *args, val="kJ/mol": setattr(self.settings, "energy", val)) kjoule.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) kjoule.triggered.connect( lambda *args, action=hartree: action.setChecked(False)) kjoule.triggered.connect( lambda *args, action=kcal: action.setChecked(False)) mass = unit.addMenu("mass") kg = QAction("kg", self.tool_window.ui_area, checkable=True) kg.setChecked(self.settings.mass == "kg") amu = QAction("Da", self.tool_window.ui_area, checkable=True) amu.setChecked(self.settings.mass == "Da") mass.addAction(kg) mass.addAction(amu) kg.triggered.connect( lambda *args, val="kg": setattr(self.settings, "mass", val)) kg.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) kg.triggered.connect( lambda *args, action=amu: action.setChecked(False)) amu.triggered.connect( lambda *args, val="Da": setattr(self.settings, "mass", val)) amu.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) amu.triggered.connect( lambda *args, action=kg: action.setChecked(False)) rot_const = unit.addMenu("rotational constants") temperature = QAction("K", self.tool_window.ui_area, checkable=True) temperature.setChecked(self.settings.rot_const == "K") hertz = QAction("GHz", self.tool_window.ui_area, checkable=True) hertz.setChecked(self.settings.rot_const == "GHz") rot_const.addAction(temperature) rot_const.addAction(hertz) temperature.triggered.connect( lambda *args, val="K": setattr(self.settings, "rot_const", val)) temperature.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) temperature.triggered.connect( lambda *args, action=hertz: action.setChecked(False)) hertz.triggered.connect( lambda *args, val="GHz": setattr(self.settings, "rot_const", val)) hertz.triggered.connect( lambda *args: self.fill_table(self.file_selector.count() - 1)) hertz.triggered.connect( lambda *args, action=temperature: action.setChecked(False)) menu.setNativeMenuBar(False) self._menu = menu layout.setMenuBar(menu) menu.setVisible(True) if len(self.session.filereader_manager.list()) > 0: self.fill_table(0) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def header_check(self, state): """user has [un]checked the 'include header' option on the menu""" if state: self.settings.include_header = True else: self.settings.include_header = False def get_csv(self): if self.settings.delimiter == "comma": delim = "," elif self.settings.delimiter == "space": delim = " " elif self.settings.delimiter == "tab": delim = "\t" elif self.settings.delimiter == "semicolon": delim = ";" if self.tabs.currentIndex() == 0: if self.settings.include_header: s = delim.join(["Data", "Value"]) s += "\n" else: s = "" for i in range(0, self.table.rowCount()): if self.table.isRowHidden(i): continue s += delim.join([ item.text().replace("<sub>", "").replace("</sub>", "") for item in [ self.table.item(i, j) if self.table.item(i, j) is not None else self.table.cellWidget(i, j) for j in range(0, 2) ] ]) s += "\n" elif self.tabs.currentIndex() == 1: if self.settings.include_header: s = delim.join([ "Frequency (cm\u207b\u00b9)", "symmetry", "IR intensity", "force constant" ]) s += "\n" else: s = "" for i in range(0, self.freq_table.rowCount()): if self.freq_table.isRowHidden(i): continue s += delim.join([ item.text().replace("<sub>", "").replace("</sub>", "") for item in [ self.freq_table.item(i, j) if self.freq_table. item(i, j) is not None else self.freq_table. cellWidget(i, j) for j in range(0, 4) ] ]) s += "\n" else: if self.settings.include_header: s = delim.join( ["Fundamental (cm\u207b\u00b9)", "Δanh", "IR intensity"]) s += "\n" else: s = "" for i in range(0, self.fundamental_table.rowCount()): if self.fundamental_table.isRowHidden(i): continue s += delim.join([ item.text().replace("<sub>", "").replace("</sub>", "") for item in [ self.fundamental_table.item(i, j) if self. fundamental_table.item(i, j) is not None else self. fundamental_table.cellWidget(i, j) for j in range(0, 3) ] ]) s += "\n" if self.settings.include_header: s += delim.join([ "Fundamental (cm\u207b\u00b9)", "Fundamental (cm\u207b\u00b9)", "Combination (cm\u207b\u00b9)", "IR intensity" ]) s += "\n" else: s += "\n" for i in range(0, self.combo_table.rowCount()): if self.combo_table.isRowHidden(i): continue s += delim.join([ item.text().replace("<sub>", "").replace("</sub>", "") for item in [ self.combo_table.item(i, j) if self.combo_table. item(i, j) is not None else self.combo_table. cellWidget(i, j) for j in range(0, 4) ] ]) s += "\n" return s def copy_csv(self): app = QApplication.instance() clipboard = app.clipboard() csv = self.get_csv() clipboard.setText(csv) self.session.logger.status("copied to clipboard") def save_csv(self): """save data on current tab to CSV file""" filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)") if filename: s = self.get_csv() with open(filename, 'w') as f: f.write(s.strip()) self.session.logger.status("saved to %s" % filename) def fill_table(self, ndx): self.table.setRowCount(0) self.freq_table.setRowCount(0) self.fundamental_table.setRowCount(0) # self.overtone_table.setRowCount(0) self.combo_table.setRowCount(0) if ndx < 0: self.fundamental_table.setVisible(False) self.combo_table.setVisible(False) return fr = self.file_selector.currentData() item = QTableWidgetItem() item.setData(Qt.DisplayRole, "name") val = QTableWidgetItem() val.setData(Qt.DisplayRole, fr.name) self.table.insertRow(0) self.table.setItem(0, 0, item) self.table.setItem(0, 1, val) for info in fr.other.keys(): if info == "archive" and not self.settings.archive: continue if any( isinstance(fr.other[info], obj) for obj in [str, float, int]): row = self.table.rowCount() self.table.insertRow(row) item = QTableWidgetItem() info_name = info.replace("_", " ") val = fr.other[info] if info == "mass": info_name += " (%s)" % self.settings.mass if self.settings.mass == "Da": val /= UNIT.AMU_TO_KG elif info == "temperature": info_name += " (K)" elif (any(info == s for s in nrg_infos) or info.lower().endswith("energy") or info.startswith("E(")): if self.settings.energy == "Hartree": info_name += " (E<sub>h</sub>)" else: info_name += " (%s)" % self.settings.energy info_name = info_name.replace("orrelation", "orr.") info_name = info_name.replace("Same-spin", "SS") info_name = info_name.replace("Opposite-spin", "OS") if self.settings.energy == "kcal/mol": val *= UNIT.HART_TO_KCAL elif self.settings.energy == "kJ/mol": val *= 4.184 * UNIT.HART_TO_KCAL val = "%.6f" % val elif info.startswith("optical rotation"): info_name += " (°)" elif any(info == x for x in pg_infos): info_name = info.replace("_", " ") if re.search("\d", val): val = re.sub(r"(\d+)", r"<sub>\1</sub>", val) # gaussian uses * for infinity val = val.replace("*", "<sub>∞</sub>") # psi4 uses _inf_ val = val.replace("_inf_", "<sub>∞</sub>") if any(val.endswith(char) for char in "vhdsiVHDSI"): val = val[:-1] + "<sub>" + val[-1].lower() + "</sub>" if "<sub>" in info_name: self.table.setCellWidget(row, 0, QLabel(info_name)) else: item = QTableWidgetItem() item.setData(Qt.DisplayRole, info_name) self.table.setItem(row, 0, item) value = QTableWidgetItem() val = str(val) if "<sub>" in val: self.table.setCellWidget(row, 1, QLabel(val)) else: value.setData(Qt.DisplayRole, val) self.table.setItem(row, 1, value) elif isinstance(fr.other[info], Theory): theory = fr.other[info] if theory.method is not None: row = self.table.rowCount() self.table.insertRow(row) item = QTableWidgetItem() item.setData(Qt.DisplayRole, "method") self.table.setItem(row, 0, item) value = QTableWidgetItem() value.setData(Qt.DisplayRole, theory.method.name) self.table.setItem(row, 1, value) if theory.basis is not None: if theory.basis.basis: for basis in theory.basis.basis: row = self.table.rowCount() self.table.insertRow(row) item = QTableWidgetItem() if not basis.elements: item.setData(Qt.DisplayRole, "basis set") else: item.setData( Qt.DisplayRole, "basis for %s" % ", ".join(basis.elements)) self.table.setItem(row, 0, item) value = QTableWidgetItem() value.setData(Qt.DisplayRole, basis.name) self.table.setItem(row, 1, value) if theory.basis.ecp: for ecp in theory.basis.ecp: row = self.table.rowCount() self.table.insertRow(row) item = QTableWidgetItem() if ecp.elements is None: item.setData(Qt.DisplayRole, "ECP") else: item.setData(Qt.DisplayRole, "ECP %s" % " ".join(ecp.elements)) self.table.setItem(row, 0, item) value = QTableWidgetItem() value.setData(Qt.DisplayRole, ecp.name) self.table.setItem(row, 1, value) elif (hasattr(fr.other[info], "__iter__") and all(isinstance(x, float) for x in fr.other[info]) and len(fr.other[info]) > 1): row = self.table.rowCount() self.table.insertRow(row) item = QTableWidgetItem() info_name = info.replace("_", " ") vals = fr.other[info] if "rotational_temperature" in info: info_name = info_name.replace( "temperature", "constants (%s)" % self.settings.rot_const) if self.settings.rot_const == "GHz": vals = [ x * PHYSICAL.KB / (PHYSICAL.PLANCK * (10**9)) for x in vals ] item.setData(Qt.DisplayRole, info_name) self.table.setItem(row, 0, item) value = QTableWidgetItem() value.setData(Qt.DisplayRole, ", ".join(["%.4f" % x for x in vals])) self.table.setItem(row, 1, value) if "frequency" in fr.other: self.tabs.setTabEnabled(1, True) freq_data = fr.other['frequency'].data for i, mode in enumerate(freq_data): row = self.freq_table.rowCount() self.freq_table.insertRow(row) freq = FreqTableWidgetItem() freq.setData( Qt.DisplayRole, "%.2f%s" % (abs(mode.frequency), "i" if mode.frequency < 0 else "")) freq.setData(Qt.UserRole, i) self.freq_table.setItem(row, 0, freq) if mode.symmetry: text = mode.symmetry if re.search("\d", text): text = re.sub(r"(\d+)", r"<sub>\1</sub>", text) if text.startswith("SG"): text = "Σ" + text[2:] elif text.startswith("PI"): text = "Π" + text[2:] elif text.startswith("DLT"): text = "Δ" + text[3:] if any(text.endswith(char) for char in "vhdugVHDUG"): text = text[:-1] + "<sub>" + text[-1].lower( ) + "</sub>" label = QLabel(text) label.setAlignment(Qt.AlignCenter) self.freq_table.setCellWidget(row, 1, label) intensity = QTableWidgetItem() if mode.intensity is not None: intensity.setData(Qt.DisplayRole, round(mode.intensity, 2)) self.freq_table.setItem(row, 2, intensity) forcek = QTableWidgetItem() if mode.forcek is not None: forcek.setData(Qt.DisplayRole, round(mode.forcek, 2)) self.freq_table.setItem(row, 3, forcek) if fr.other["frequency"].anharm_data: self.fundamental_table.setVisible(True) self.combo_table.setVisible(True) freq = fr.other["frequency"] self.tabs.setTabEnabled(2, True) anharm_data = sorted( freq.anharm_data, key=lambda x: x.harmonic_frequency, ) for i, mode in enumerate(anharm_data): row = self.fundamental_table.rowCount() self.fundamental_table.insertRow(row) fund = FreqTableWidgetItem() fund.setData( Qt.DisplayRole, "%.2f%s" % (abs(mode.frequency), "i" if mode.frequency < 0 else "")) fund.setData(Qt.UserRole, i) self.fundamental_table.setItem(row, 0, fund) delta_anh = QTableWidgetItem() delta_anh.setData(Qt.DisplayRole, round(mode.delta_anh, 2)) self.fundamental_table.setItem(row, 1, delta_anh) intensity = QTableWidgetItem() if mode.intensity is not None: intensity.setData(Qt.DisplayRole, round(mode.intensity, 2)) self.fundamental_table.setItem(row, 2, intensity) for overtone in mode.overtones: row = self.combo_table.rowCount() self.combo_table.insertRow(row) fund = FreqTableWidgetItem() fund.setData( Qt.DisplayRole, "%.2f%s" % (abs(mode.frequency), "i" if mode.frequency < 0 else "")) fund.setData(Qt.UserRole, i) self.combo_table.setItem(row, 0, fund) fund = FreqTableWidgetItem() fund.setData(Qt.UserRole, i) self.combo_table.setItem(row, 1, fund) ot = FreqTableWidgetItem() ot.setData( Qt.DisplayRole, "%.2f%s" % (abs(overtone.frequency), "i" if overtone.frequency < 0 else "")) ot.setData(Qt.UserRole, i) self.combo_table.setItem(row, 2, ot) intensity = QTableWidgetItem() if overtone.intensity is not None: intensity.setData(Qt.DisplayRole, round(overtone.intensity, 2)) self.combo_table.setItem(row, 3, intensity) for key in mode.combinations: for combination in mode.combinations[key]: row = self.combo_table.rowCount() self.combo_table.insertRow(row) fund = FreqTableWidgetItem() fund.setData( Qt.DisplayRole, "%.2f%s" % (abs(mode.frequency), "i" if mode.frequency < 0 else "")) fund.setData(Qt.UserRole, i) self.combo_table.setItem(row, 0, fund) other_freq = freq.anharm_data[key].frequency fund = FreqTableWidgetItem() fund.setData( Qt.DisplayRole, "%.2f%s" % (abs(other_freq), "i" if other_freq < 0 else "")) fund.setData(Qt.UserRole, i + len(freq.anharm_data) * key) self.combo_table.setItem(row, 1, fund) combo = FreqTableWidgetItem() combo.setData( Qt.DisplayRole, "%.2f%s" % (abs(combination.frequency), "i" if combination.frequency < 0 else "")) combo.setData(Qt.UserRole, i) self.combo_table.setItem(row, 2, combo) intensity = QTableWidgetItem() if combination.intensity is not None: intensity.setData( Qt.DisplayRole, round(combination.intensity, 2)) self.combo_table.setItem(row, 3, intensity) else: self.fundamental_table.setVisible(False) self.combo_table.setVisible(False) self.tabs.setTabEnabled(2, False) else: self.fundamental_table.setVisible(False) self.combo_table.setVisible(False) self.tabs.setTabEnabled(1, False) self.tabs.setTabEnabled(2, False) self.table.resizeColumnToContents(0) self.table.resizeColumnToContents(1) self.freq_table.resizeColumnToContents(0) self.freq_table.resizeColumnToContents(1) self.freq_table.resizeColumnToContents(2) self.apply_filter() def apply_filter(self, text=None): if text is None: text = self.filter.text() if text: text = text.replace("(", "\(") text = text.replace(")", "\)") m = QRegularExpression(text) m.setPatternOptions(QRegularExpression.CaseInsensitiveOption) if m.isValid(): m.optimize() filter = lambda row_num: m.match( self.table.item(row_num, 0).text() if self.table.item(row_num, 0) is not None else self.table. cellWidget(row_num, 0).text().replace("<sub>", "").replace( "</sub>", "")).hasMatch() else: return else: filter = lambda row: True for i in range(0, self.table.rowCount()): self.table.setRowHidden(i, not filter(i)) def delete(self): self.file_selector.deleteLater() return super().delete() def close(self): self.file_selector.deleteLater() return super().close()
class IndexedStringsWidget(QWidget): """ Holds the list of indexed string attributes """ def __init__(self, data, parent=None): QWidget.__init__(self, parent) self.data = data vbox = QVBoxLayout() self.setLayout(vbox) title = QLabel('Indexed Strings') vbox.addWidget(title) self.frame = QFrame() vbox.addWidget(self.frame) self.vbox = QVBoxLayout() self.frame.setLayout(self.vbox) self.frame.setFrameShape(QFrame.Panel) self.frame.setFrameShadow(QFrame.Sunken) self.table = QTableWidget() self.table.horizontalHeader().hide() self.vbox.addWidget(self.table) self.table.hide() self.noStringsLabel = QLabel('<i>No indexed strings</i>') self.vbox.addWidget(self.noStringsLabel) self.widgets = [] self.populate() self.data.attributeAdded.connect(self.attributeAddedSlot) self.data.dataReset.connect(self.dataResetSlot) self.data.dirtied.connect(self.dataDirtiedSlot) def dataDirtiedSlot(self, dirty): """ SLOT when the particle data is dirtied or cleaned.""" if not dirty: for widget in self.widgets: widget.drawBorder(False) def dataResetSlot(self): """ SLOT when particle data is reconstructed """ self.populate() def attributeAddedSlot(self, name): #pylint:disable=W0613 """ SLOT when an attribute is added to the particle set """ attr = self.data.attributeInfo(name) if attr.type == partio.INDEXEDSTR: self.populate() def populate(self): """ Populates the table of indexed strings """ self.widgets = [] # If no widgets, just drop that in attrs = [] for anum in range(self.data.numAttributes()): attr = self.data.attributeInfo(anum) if attr.type == partio.INDEXEDSTR: attrs.append(attr) if not attrs: self.table.hide() self.noStringsLabel.show() return self.table.show() self.noStringsLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(len(attrs)) for row, attr in enumerate(attrs): item = QTableWidgetItem(attr.name) self.table.setVerticalHeaderItem(row, item) strings = self.data.indexedStrs(attr) table = QTableWidget() table.setColumnCount(1) table.setRowCount(len(strings)) table.horizontalHeader().hide() table.setVerticalHeaderLabels([str(i) for i in range(len(strings))]) for i, string in enumerate(strings): widget = QLabel(string) table.setCellWidget(i, 0, widget) self.widgets.append(widget) self.table.setCellWidget(row, 0, table) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections(QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections(QHeaderView.ResizeToContents)
class FixedAttributesWidget(QWidget): """ A widget for viewing/editing fixed attributes (non-varying) """ def __init__(self, data, parent=None): QWidget.__init__(self, parent) self.data = data vbox = QVBoxLayout() self.setLayout(vbox) title = QLabel('Fixed Attributes') vbox.addWidget(title) self.frame = QFrame() vbox.addWidget(self.frame) self.vbox = QVBoxLayout() self.frame.setLayout(self.vbox) self.frame.setFrameShape(QFrame.Panel) self.frame.setFrameShadow(QFrame.Sunken) self.table = QTableWidget() self.table.horizontalHeader().hide() self.vbox.addWidget(self.table) self.table.hide() self.noAttrLabel = QLabel('<i>No fixed attributes</i>') self.vbox.addWidget(self.noAttrLabel) self.widgets = [] self.populate() self.data.fixedAttributeAdded.connect(self.fixedAttributeAddedSlot) self.data.dataReset.connect(self.dataResetSlot) self.data.dirtied.connect(self.dataDirtiedSlot) def dataDirtiedSlot(self, dirty): """ SLOT when the particle data is dirtied or cleaned.""" if not dirty: for widget in self.widgets: widget.drawBorder(False) def dataResetSlot(self): """ SLOT when particle data is reconstructed """ self.populate() def fixedAttributeAddedSlot(self, name): #pylint:disable=W0613 """ SLOT when a fixed attribute is added to the particle set """ self.populate() def populate(self): """ Populates the table of fixed attributes """ self.widgets = [] # If no widgets, just drop that in numAttrs = self.data.numFixedAttributes() if not numAttrs: self.table.hide() self.noAttrLabel.show() return self.table.show() self.noAttrLabel.hide() self.table.setColumnCount(1) self.table.setRowCount(numAttrs) self.attrs = getAttrs(self.data.numFixedAttributes, self.data.fixedAttributeInfo, True) for row, (_, attr) in enumerate(self.attrs): item = QTableWidgetItem(attr.name) tooltip = '<p><tt> Name: {}<br> Type: {}<br>Count: {}</tt></p>'.\ format(attr.name, partio.TypeName(attr.type), attr.count) item.setToolTip(tooltip) self.table.setVerticalHeaderItem(row, item) value = self.data.getFixed(attr) widget = getWidget(value, self.data, attr) self.table.setCellWidget(row, 0, widget) self.widgets.append(widget) self.table.horizontalHeader().setStretchLastSection(False) self.table.setTabKeyNavigation(True) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().resizeSections(QHeaderView.ResizeToContents) self.table.verticalHeader().resizeSections(QHeaderView.ResizeToContents)
class Linter(ToolInstance): """ tool to run python linters on files the UI has a list of files, an option to choose the linter, and a button to run the linter results are printed to the log """ def __init__(self, session, name): super().__init__(session, name) self.tool_window = MainToolWindow(self) self.settings = _LinterSettings(self.session, name) self._build_ui() def _build_ui(self): layout = QFormLayout() self.table = QTableWidget() self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(["file", "remove"]) self.table.horizontalHeader().setStretchLastSection(False) self.table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.table.cellClicked.connect(self.table_clicked) layout.addRow(self.table) self.linters = QComboBox() self.linters.addItems([ "pyflakes", "flake8", "mypy", "pydocstyle", "pylint", ]) ndx = self.linters.findText(self.settings.linter, Qt.MatchExactly) self.linters.setCurrentIndex(ndx) layout.addRow(self.linters) lint = QPushButton("run linter") lint.clicked.connect(self.run_linter) layout.addRow(lint) self.add_files(loads(self.settings.files)) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def table_clicked(self, row, column): """ if the last row is clicked, open a file browser and add the files to the list otherwise, if the 2nd column is clicked (the trash can), remove that row """ if row == self.table.rowCount() - 1 or self.table.rowCount() == 1: filenames = QFileDialog.getOpenFileNames( filter="Python Files (*.py)") if filenames[0]: self.table.setRowCount(self.table.rowCount() - 1) self.add_files(filenames[0]) elif column == 1: self.table.removeRow(row) def add_files(self, filenames): """add filenames (list(str)) to the table""" for f in filenames: row = self.table.rowCount() self.table.insertRow(row) file_item = QTableWidgetItem() file_item.setData(Qt.DisplayRole, f) self.table.setItem(row, 0, file_item) widget_that_lets_me_horizontally_align_an_icon = QWidget() widget_layout = QHBoxLayout( widget_that_lets_me_horizontally_align_an_icon) section_remove = QLabel() dim = int(1.5 * section_remove.fontMetrics().boundingRect("Z").height()) section_remove.setPixmap( QIcon(section_remove.style().standardIcon( QStyle.SP_DialogDiscardButton)).pixmap(dim, dim)) widget_layout.addWidget(section_remove, 0, Qt.AlignHCenter) widget_layout.setContentsMargins(0, 0, 0, 0) self.table.setCellWidget( row, 1, widget_that_lets_me_horizontally_align_an_icon) self.add_last_row() def add_last_row(self): """add the "add files" button to the bottom of the table""" row = self.table.rowCount() self.table.insertRow(row) add_row = QTableWidgetItem() add_row.setData(Qt.DisplayRole, "add files") self.table.setItem(row, 0, add_row) def run_linter(self): """execute linter""" previous_files = [] linter = self.linters.currentText() for row in range(0, self.table.rowCount() - 1): fname = self.table.item(row, 0).text() previous_files.append(fname) run(self.session, "linter \"%s\" linter %s" % (fname, linter)) self.settings.files = dumps(previous_files) self.settings.linter = linter