diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index b2692162..fa9a22fc 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -18,7 +18,7 @@ import gemmi.cif import requests # type: ignore -from PyQt5 import QtCore, QtGui, QtWebEngineWidgets +from PyQt5 import QtCore, QtGui, QtWebEngineWidgets, QtWidgets from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QMainWindow, QShortcut, QCheckBox, QListWidgetItem, QApplication, \ QPlainTextEdit, QFileDialog, QMessageBox, QScrollBar @@ -1136,12 +1136,20 @@ def make_report_tables(self) -> None: make_multi_tables(cif=self.cif, output_filename=str(multi_table_document)) else: print('Report with templates') - t = TemplatedReport(format=ReportFormat.RICHTEXT, options=self.options, cif=self.cif) - ok = t.make_templated_docx_report(output_filename=str(report_filename), - picfile=picfile, - template_path=Path(self.get_checked_templates_list_text())) - # t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif) - # t.make_templated_html_report(options=self.options, picfile=picfile) + template_path = Path(self.get_checked_templates_list_text()) + ok = False + if template_path.suffix in ('.docx',): + t = TemplatedReport(format=ReportFormat.RICHTEXT, options=self.options, cif=self.cif) + ok = t.make_templated_docx_report(output_filename=str(report_filename), + picfile=picfile, + template_path=Path(self.get_checked_templates_list_text())) + elif template_path.suffix in ('.html', '.tmpl'): + t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif) + report_filename = report_filename.with_suffix('.html') + ok = t.make_templated_html_report(output_filename=str(report_filename), + picfile=picfile, + template_path=template_path.parent, + template_file=template_path.name) if not ok: return None except FileNotFoundError as e: @@ -1514,6 +1522,7 @@ def _load_block(self, index: int, load_changes: bool = True) -> None: raise # unable_to_open_message(Path(self.cif.filename), not_ok) self.load_recent_cifs_list() + self.set_vertical_header_width() if self.options.track_changes and load_changes: changes_exist = False if self.changes_cif_has_zero_size(): @@ -1558,6 +1567,16 @@ def _load_block(self, index: int, load_changes: bool = True) -> None: t = QtCore.QTimer(self) t.singleShot(1000, self.check_cecksums) + def set_vertical_header_width(self): + """ + Sets the width of the vertical header to the length of the longest string in the list. + """ + vheader: QtWidgets.QHeaderView = self.ui.cif_main_table.verticalHeader() + fm = QtGui.QFontMetrics(vheader.font(), self) + # noinspection PyTypeChecker + longest_string: str = max(self.ui.cif_main_table.vheaderitems, key=len) + vheader.setMaximumWidth(fm.width(longest_string + 'O')) + def changes_cif_has_values(self) -> bool: try: return not self.get_changes_cif(self.finalcif_changes_filename).is_empty() diff --git a/finalcif/cif/cif_file_io.py b/finalcif/cif/cif_file_io.py index 62cd0780..1d6682ab 100644 --- a/finalcif/cif/cif_file_io.py +++ b/finalcif/cif/cif_file_io.py @@ -94,7 +94,7 @@ def finalcif_file_prefixed(self, prefix: str, suffix: str = '-finalcif.cif', for The suffix needs '-finalcif' in order to contain the finalcif ending. "foo/bar/baz-finalcif.cif" - :param forece_strip: Forces to strip the filename also after the '-finalcif' string. + :param force_strip: Forces to strip the filename also after the '-finalcif' string. """ file_witout_finalcif = strip_finalcif_of_name(Path(self.filename).stem, till_name_ends=force_strip) filename = self.path_base.joinpath(Path(prefix + file_witout_finalcif + suffix)) diff --git a/finalcif/cif/cif_order.py b/finalcif/cif/cif_order.py index 9f86b46a..7f4cfa56 100644 --- a/finalcif/cif/cif_order.py +++ b/finalcif/cif/cif_order.py @@ -17,6 +17,13 @@ ] order = [ + '_audit_update_record', + '_audit_contact_author', + '_audit_contact_author_name', + '_audit_contact_author_phone', + '_audit_contact_author_email', + '_audit_contact_author_fax', + '_audit_contact_author_address', '_audit_author_id', '_audit_author_id_audit', '_audit_author_name', @@ -128,7 +135,6 @@ '_publ_contact_author_id_orcid', '_publ_contact_author_footnote', - '_shelx_SHELXL_version_number', '_chemical_name_systematic', '_chemical_name_common', @@ -341,4 +347,17 @@ '_geom_details', '_geom_special_details', + '_bruker_diffrn_runs_wavelength', + '_bruker_diffrn_runs_temperature', + '_bruker_diffrn_runs_current', + '_bruker_diffrn_runs_voltage', + '_bruker_diffrn_runs_images', + '_bruker_diffrn_runs_time', + '_bruker_diffrn_runs_width', + '_bruker_diffrn_runs_axis', + '_bruker_diffrn_runs_chi', + '_bruker_diffrn_runs_phi', + '_bruker_diffrn_runs_omega', + '_bruker_diffrn_runs_theta', + '_bruker_diffrn_runs_distance', ] diff --git a/finalcif/equip_property/equipment.py b/finalcif/equip_property/equipment.py index c34e9c89..c6902371 100644 --- a/finalcif/equip_property/equipment.py +++ b/finalcif/equip_property/equipment.py @@ -4,10 +4,12 @@ from typing import List, Dict from typing import TYPE_CHECKING +import gemmi from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QListWidgetItem from gemmi import cif +from finalcif.app_path import application_path from finalcif.cif.cif_file_io import CifContainer from finalcif.cif.text import retranslate_delimiter, string_to_utf8 from finalcif.equip_property.tools import read_document_from_cif_file @@ -123,11 +125,34 @@ def load_default_equipment(self): self.store_predefined_templates() self.show_equipment() + def load_equipment_files(self): + template_path = Path(application_path).joinpath('template/machines').resolve() + files = template_path.glob('*.cif') + equipment_templates = [] + for file in files: + doc = read_document_from_cif_file(file.__str__()) + block = doc.sole_block() + entry = {'name': f'{file.stem}', 'items': []} + pairs = [] + for item in block: + if item.pair is not None: + key, value = item.pair + pairs.append([key, gemmi.cif.as_string(value)]) + entry['items'] = pairs + equipment_templates.append(entry) + return equipment_templates + def store_predefined_templates(self): + try: + for item in self.load_equipment_files(): + self.store_equipment_item(item) + except Exception as e: + print(f'DBG> Could not load equipment templates: {e.__str__()}') + + def store_equipment_item(self, item: list[dict[str:str | str:list[list[str]]]]): equipment_list = self.settings.get_equipment_list() or [] - for item in misc.predefined_equipment_templates: - if item['name'] not in equipment_list: - self.settings.save_settings_list('equipment', item['name'], item['items']) + if item['name'] not in equipment_list: + self.settings.save_settings_list('equipment', item['name'], item['items']) def edit_equipment_template(self) -> None: """Gets called when 'edit equipment' button was clicked.""" @@ -183,9 +208,10 @@ def save_equipment_template(self) -> None: for key, _ in table_data: if key not in cif_all_dict.keys(): if not key.startswith('_'): - show_general_warning(self.app, '"{}" is not a valid keyword! ' - '\nChange the name in order to save.\n' - 'Keys must start with an underscore.'.format(key)) + show_general_warning(self.app, + f'"{key}" is not a valid keyword! \n' + f'Change the name in order to save.\n' + f'Keys must start with an underscore.') return show_general_warning(self.app, '"{}" is not an official CIF keyword!'.format(key)) self.settings.save_settings_list('equipment', self.selected_template_name(), table_data) @@ -199,7 +225,7 @@ def import_equipment_from_file(self, filename: Path | str = '') -> None: if isinstance(filename, Path): filename = str(filename) if not filename: - filename = cif_file_open_dialog(filter="CIF file (*.cif *.cif_od *.cfx)") + filename = cif_file_open_dialog(filter="CIF file (*.pcf *.cif *.cif_od *.cfx)") if not filename: print('No file given') return @@ -220,6 +246,8 @@ def _import_block(self, block: cif.Block, filename: str) -> None: table_data.append([key, retranslate_delimiter(cif.as_string(value).strip('\n\r ;'))]) if filename.endswith('.cif_od'): name = Path(filename).stem + elif filename.endswith('.pcf'): + name = Path(filename).stem else: name = block.name.replace('__', ' ') self.undelete_equipment(equipment_name=name) diff --git a/finalcif/gui/loop_creator_ui.py b/finalcif/gui/loop_creator_ui.py index 0ade1ce8..f1484103 100644 --- a/finalcif/gui/loop_creator_ui.py +++ b/finalcif/gui/loop_creator_ui.py @@ -31,9 +31,6 @@ def setupUi(self, LoopCreator): self.horizontalLayout.addWidget(self.searchLineEdit) self.verticalLayout_2.addLayout(self.horizontalLayout) self.availableKeysListWidget = QtWidgets.QListWidget(LoopCreator) - font = QtGui.QFont() - font.setPointSize(13) - self.availableKeysListWidget.setFont(font) self.availableKeysListWidget.setObjectName("availableKeysListWidget") self.verticalLayout_2.addWidget(self.availableKeysListWidget) self.horizontalLayout_2.addLayout(self.verticalLayout_2) @@ -60,9 +57,6 @@ def setupUi(self, LoopCreator): self.label_4.setObjectName("label_4") self.verticalLayout_3.addWidget(self.label_4) self.newLoopKeysListWidget = QtWidgets.QListWidget(LoopCreator) - font = QtGui.QFont() - font.setPointSize(13) - self.newLoopKeysListWidget.setFont(font) self.newLoopKeysListWidget.setObjectName("newLoopKeysListWidget") self.verticalLayout_3.addWidget(self.newLoopKeysListWidget) self.saveLoopPushButton = QtWidgets.QPushButton(LoopCreator) diff --git a/finalcif/gui/loop_creator_ui.ui b/finalcif/gui/loop_creator_ui.ui index 6eb8e1b2..d9eff6da 100644 --- a/finalcif/gui/loop_creator_ui.ui +++ b/finalcif/gui/loop_creator_ui.ui @@ -38,13 +38,7 @@ - - - - 13 - - - + @@ -112,13 +106,7 @@ - - - - 13 - - - + diff --git a/finalcif/gui/plaintextedit.py b/finalcif/gui/plaintextedit.py index 8db823ef..420811c4 100644 --- a/finalcif/gui/plaintextedit.py +++ b/finalcif/gui/plaintextedit.py @@ -1,5 +1,6 @@ +import dataclasses from enum import IntEnum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from PyQt5 import QtCore, QtGui from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent, QSize @@ -8,6 +9,7 @@ from finalcif.gui.edit_button import FloatingButtonWidget from finalcif.gui.new_key_dialog import NewKey +from finalcif.gui.validators import validators if TYPE_CHECKING: from finalcif.gui.custom_classes import MyCifTable @@ -19,15 +21,6 @@ class Column(IntEnum): EDIT = 2 -def correct_length(text): - return len(text) > 5 - - -validators = { - '_chemical_melting_point': correct_length, -} - - class MyQPlainTextEdit(QPlainTextEdit): """ A special plaintextedit with convenient methods to set the background color and other things. @@ -42,6 +35,7 @@ def __init__(self, parent=None, *args): Plaintext edit field for most of the table cells. """ super().__init__(*args, parent) + self.color = None self.cif_key = '' self.edit_button = None font = QFont() @@ -110,10 +104,6 @@ def setBackground(self, color: QColor) -> None: Set background color of the text field. """ self.setStyleSheet("background-color: {};".format(str(color.name()))) - # No idea why this does not work - # pal = self.palette() - # pal.setColor(QPalette.Base, color) - # self.setPalette(pal) def setUneditable(self): self.setReadOnly(True) @@ -166,13 +156,15 @@ def keyPressEvent(self, event: QtGui.QKeyEvent): def validate_text(self, text: str): validator = validators.get(self.cif_key, None) - if validator and validator(text): - self.setBackground(QColor(240, 88, 70)) + if validator and not validator.valid(text): + self.setBackground(QColor(254, 191, 189)) + self.setToolTip(validator.help_text) else: + self.setToolTip('') if self.color: self.setBackground(self.color) else: - self.setBackground(QColor(255, 255, 255)) + self.setStyleSheet("") def leaveEvent(self, a0: QEvent) -> None: super().leaveEvent(a0) diff --git a/finalcif/gui/text_templates_ui.py b/finalcif/gui/text_templates_ui.py index 1501be8b..63f241ff 100644 --- a/finalcif/gui/text_templates_ui.py +++ b/finalcif/gui/text_templates_ui.py @@ -30,7 +30,6 @@ def setupUi(self, TextTemplatesWidget): sizePolicy.setHeightForWidth(self.cifKeyLineEdit.sizePolicy().hasHeightForWidth()) self.cifKeyLineEdit.setSizePolicy(sizePolicy) font = QtGui.QFont() - font.setPointSize(12) font.setBold(True) font.setWeight(75) self.cifKeyLineEdit.setFont(font) @@ -88,9 +87,6 @@ def setupUi(self, TextTemplatesWidget): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth()) self.plainTextEdit.setSizePolicy(sizePolicy) - font = QtGui.QFont() - font.setPointSize(10) - self.plainTextEdit.setFont(font) self.plainTextEdit.setFrameShadow(QtWidgets.QFrame.Plain) self.plainTextEdit.setLineWidth(0) self.plainTextEdit.setBackgroundVisible(False) diff --git a/finalcif/gui/text_templates_ui.ui b/finalcif/gui/text_templates_ui.ui index 2566df8e..e5ff95a6 100644 --- a/finalcif/gui/text_templates_ui.ui +++ b/finalcif/gui/text_templates_ui.ui @@ -51,7 +51,6 @@ - 12 75 true @@ -174,11 +173,6 @@ 0 - - - 10 - - QFrame::Plain diff --git a/finalcif/gui/text_value_editor.py b/finalcif/gui/text_value_editor.py index 08ee561d..e54af902 100644 --- a/finalcif/gui/text_value_editor.py +++ b/finalcif/gui/text_value_editor.py @@ -1,6 +1,7 @@ import sys from typing import Tuple, List, Union +from PyQt5 import QtGui from PyQt5.QtCore import QSize, Qt, pyqtSignal from PyQt5.QtWidgets import QWidget, QHBoxLayout, QCheckBox, QListWidgetItem, QVBoxLayout, QLabel @@ -24,9 +25,7 @@ def __init__(self, parent: QWidget): self.number_label = QLabel() self.vlayout.addWidget(self.number_label) self.textfield = SpellTextEdit(self) - font = self.textfield.font() - font.setPixelSize(12) - self.textfield.setFont(font) + self.set_font_size() self.vlayout.addWidget(self.checkbox) layout.addLayout(self.vlayout) layout.addWidget(self.textfield) @@ -34,6 +33,11 @@ def __init__(self, parent: QWidget): self.setAutoFillBackground(False) self.checkbox.clicked.connect(self.on_checkbox_clicked) + def set_font_size(self): + font: QtGui.QFont = self.textfield.font() + font.setPointSize(font.pointSize() + 2) + self.textfield.setFont(font) + @property def text(self) -> str: return self.textfield.toPlainText() @@ -65,6 +69,15 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.ui.cancelTextPushButton.clicked.connect(self._on_backbutton_clicked) if not self.ui.templatesListWidget.count(): self.add_more_fiels() + self.set_font_size() + + def set_font_size(self): + textedit_font: QtGui.QFont = self.ui.plainTextEdit.font() + textedit_font.setPointSize(textedit_font.pointSize() + 3) + self.ui.plainTextEdit.setFont(textedit_font) + cif_key_font = self.ui.cifKeyLineEdit.font() + cif_key_font.setPointSize(textedit_font.pointSize()) + self.ui.cifKeyLineEdit.setFont(cif_key_font) def _on_backbutton_clicked(self) -> None: self.ui.templatesListWidget.clear() diff --git a/finalcif/gui/validators.py b/finalcif/gui/validators.py new file mode 100644 index 00000000..5b407d3d --- /dev/null +++ b/finalcif/gui/validators.py @@ -0,0 +1,201 @@ +from math import inf +from typing import Union, Type + + +class BaseLimits: + upper: Union[int, float, str] + lower: Union[int, float, str] + valid: callable + value_type: Type[Union[int, float, str]] + help_text: str + + def __init__(self, lower, upper, help_text: str = ''): + self.lower = lower + self.upper = upper + self.help_text = help_text + self.valid = self.validate_cif_key + + def __repr__(self): + return (f"<{self.__class__.__name__}(lower bound: {self.lower}, upper bound: {self.upper}, " + f"type: {self.value_type})> )>") + + def validate_cif_key(self, value: str): + if '(' in value and ')' in value: + value, esd = value.split('(') + value = value.strip() + after_esd = esd.split(')')[1] + if after_esd: + return False + valid = False + value = value.split('(')[0].strip() + if value in ('', '?', '.'): + return True + try: + value = self.value_type(value) + except Exception: + return False + if self.lower <= value <= self.upper: + valid = True + return valid + + +class Integerlimits(BaseLimits): + value_type = int + + def __init__(self, lower: int, upper: int, help_text: str = None): + if not help_text: + help_text = (f'Must be a {"negative" if lower < 0 else "positive"} ' + f'integer number between {lower} and {upper}.') + super().__init__(lower, upper, help_text) + + def validate_cif_key(self, value: str): + valid = super().validate_cif_key(value) + value = value.split('(')[0].strip() + if not value.replace('-', '').isdigit() and value not in ('', '?', '.'): + valid = False + return valid + + +class Floatlimits(BaseLimits): + value_type = float + + def __init__(self, lower: float, upper: float, help_text: str = None): + if not help_text: + help_text = (f'Must be a {"negative" if lower < 0 else "positive"} ' + f'decimal number between {lower} and {upper}.') + super().__init__(lower, upper, help_text) + + +class Textlimits: + def __init__(self, options: list[str], help_text: str = None): + self.valid = self.validate_cif_key + self.options = options + self.help_text = f'Must be one of: {", ".join(options)}.' + if help_text: + self.help_text = help_text + + def validate_cif_key(self, value: str): + if value.lower() in ('', '?', '.') or value.lower() in self.options: + return True + return False + + +validators: dict[str, BaseLimits] = { + '_database_code_depnum_ccdc_archive' : Integerlimits(lower=0, upper=inf), + '_cell_measurement_reflns_used' : Integerlimits(lower=0, upper=inf), + '_cell_measurement_theta_min' : Floatlimits(lower=0.0, upper=90.0), + '_cell_measurement_theta_max' : Floatlimits(lower=0.0, upper=90.0), + '_chemical_melting_point' : Floatlimits(lower=0.0, upper=5000.0), + '_diffrn_reflns_number' : Integerlimits(lower=0, upper=inf), + '_exptl_crystal_density_meas' : Floatlimits(lower=0, upper=25.0), + '_exptl_crystal_density_diffrn' : Floatlimits(lower=0, upper=25.0), + '_diffrn_ambient_temperature' : Floatlimits(lower=0, upper=inf), + '_diffrn_source_current' : Floatlimits(lower=0.0, upper=inf), + '_diffrn_source_voltage' : Floatlimits(lower=0.0, upper=inf), + '_exptl_absorpt_correction_T_max' : Floatlimits(lower=0.0, upper=1.0), + '_exptl_absorpt_correction_T_min' : Floatlimits(lower=0.0, upper=1.0), + '_exptl_transmission_factor_max' : Floatlimits(lower=0.0, upper=1.0), + '_exptl_transmission_factor_min' : Floatlimits(lower=0.0, upper=1.0), + '_cell_angle_alpha' : Floatlimits(lower=0.0, upper=180.0), + '_cell_angle_beta' : Floatlimits(lower=0.0, upper=180.0), + '_cell_angle_gamma' : Floatlimits(lower=0.0, upper=180.0), + '_cell_formula_units_Z' : Integerlimits(lower=1, upper=inf), + '_cell_length_a' : Floatlimits(lower=0.0, upper=inf), + '_cell_length_b' : Floatlimits(lower=0.0, upper=inf), + '_cell_length_c' : Floatlimits(lower=0.0, upper=inf), + '_cell_measurement_temperature' : Floatlimits(lower=0.0, upper=inf), + '_cell_volume' : Floatlimits(lower=0.0, upper=inf), + '_chemical_formula_weight' : Floatlimits(lower=1.0, upper=inf), + '_diffrn_measured_fraction_theta_full': Floatlimits(lower=0.0, upper=1.0), + '_diffrn_measured_fraction_theta_max' : Floatlimits(lower=0.0, upper=1.0), + '_diffrn_radiation_wavelength' : Floatlimits(lower=0.0, upper=1.0 * inf), + '_diffrn_reflns_Laue_' + 'measured_fraction_full' : Floatlimits(lower=0.95, + upper=1.0, + help_text="This number should not be between zero and 0.95,\n" + "since it represents the fraction of reflections\n" + "measured in the part of the diffraction pattern\n" + "that is essentially complete."), + '_diffrn_reflns_point_group_' + 'measured_fraction_full' : Floatlimits(lower=0.95, + upper=1.0, + help_text="This number should not be between zero and 0.95,\n" + "since it represents the fraction of reflections\n" + "measured in the part of the diffraction pattern\n" + "that is essentially complete."), + '_diffrn_reflns_Laue_' + 'measured_fraction_max' : Floatlimits(lower=0.0, upper=1.0), + '_diffrn_reflns_point_' + 'group_measured_fraction_max' : Floatlimits(lower=0.0, upper=1.0), + '_diffrn_reflns_theta_full' : Floatlimits(lower=0.0, upper=90.0), + '_diffrn_reflns_theta_max' : Floatlimits(lower=0.0, upper=90.0), + '_diffrn_reflns_theta_min' : Floatlimits(lower=0.0, upper=90.0), + '_diffrn_reflns_av_R_equivalents' : Floatlimits(lower=0.0, upper=inf), + '_diffrn_reflns_av_unetI/netI' : Floatlimits(lower=0.0, upper=inf), + '_diffrn_reflns_limit_h_max' : Integerlimits(lower=0, upper=inf), + '_diffrn_reflns_limit_k_max' : Integerlimits(lower=0, upper=inf), + '_diffrn_reflns_limit_l_max' : Integerlimits(lower=0, upper=inf), + '_diffrn_reflns_limit_h_min' : Integerlimits(lower=-inf, upper=0), + '_diffrn_reflns_limit_k_min' : Integerlimits(lower=-inf, upper=0), + '_diffrn_reflns_limit_l_min' : Integerlimits(lower=-inf, upper=0), + '_exptl_absorpt_coefficient_mu' : Floatlimits(lower=0.0, upper=inf), + '_exptl_crystal_size_max' : Floatlimits(lower=0.0, upper=inf), + '_exptl_crystal_size_min' : Floatlimits(lower=0.0, upper=inf), + '_exptl_crystal_size_mid' : Floatlimits(lower=0.0, upper=inf), + '_refine_diff_density_max' : Floatlimits(lower=0.0, upper=inf), + '_refine_diff_density_min' : Floatlimits(lower=-inf, upper=0.0), + '_refine_diff_density_rms' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_R_factor_all' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_R_factor_gt' : Floatlimits(lower=0.0, upper=inf), + # In fact from 0.0-1.0, but some software does other: + '_refine_ls_abs_structure_Flack' : Floatlimits(lower=-inf, upper=inf), + '_refine_ls_goodness_of_fit_ref' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_number_parameters' : Integerlimits(lower=0, upper=inf), + '_refine_ls_number_reflns' : Integerlimits(lower=0, upper=inf), + '_refine_ls_number_restraints' : Integerlimits(lower=0, upper=inf), + '_refine_ls_restrained_S_all' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_shift/su_max' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_shift/su_mean' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_wR_factor_gt' : Floatlimits(lower=0.0, upper=inf), + '_refine_ls_wR_factor_ref' : Floatlimits(lower=0.0, upper=inf), + '_reflns_Friedel_coverage' : Floatlimits(lower=0.0, upper=1.0), + '_reflns_Friedel_fraction_full' : Floatlimits(lower=0.0, upper=1.0), + '_reflns_Friedel_fraction_max' : Floatlimits(lower=0.0, upper=1.0), + '_reflns_number_gt' : Integerlimits(lower=0.0, upper=inf), + '_reflns_number_total' : Integerlimits(lower=0.0, upper=inf), + '_shelx_estimated_absorpt_T_max' : Floatlimits(lower=0.0, upper=1.0), + '_shelx_estimated_absorpt_T_min' : Floatlimits(lower=0.0, upper=1.0), + '_shelx_hkl_checksum' : Integerlimits(lower=0, upper=inf), + '_shelx_res_checksum' : Integerlimits(lower=0, upper=inf), + '_shelx_fcf_checksum' : Integerlimits(lower=0, upper=inf), + '_space_group_IT_number' : Integerlimits(lower=1, upper=230), + '_space_group_crystal_system' : Textlimits(options=['triclinic', + 'monoclinic', + 'orthorhombic', + 'tetragonal', + 'trigonal', + 'hexagonal', + 'cubic', ]), + +} + +if __name__ == '__main__': + limits = validators['_chemical_melting_point'] + print(limits.valid('5')) + print(limits.valid('-5')) + + min_ = validators['_diffrn_reflns_limit_h_min'] + print(min_.valid('-5')) + print(min_.help_text) + + min_ = validators['_diffrn_reflns_Laue_measured_fraction_max'] + print(min_.valid('-5')) + print(min_.help_text) + + min_ = validators['_refine_ls_number_restraints'] + print(min_.valid('-5')) + print(min_.help_text) + + min_ = validators['_space_group_crystal_system'] + print(min_.valid('Triclinic')) + print(min_.help_text) diff --git a/finalcif/report/tables.py b/finalcif/report/tables.py index 681fa59d..1fe289d9 100644 --- a/finalcif/report/tables.py +++ b/finalcif/report/tables.py @@ -70,8 +70,6 @@ def make_multi_tables(cif: CifContainer, output_filename: str = 'multitable.docx def make_report_from(options: Options, cif: CifContainer, output_filename: str = None, picfile: Path = None) -> str: """ Creates a tabular cif report. - :param file_obj: Input cif file. - :param output_filename: the table is saved to this file. """ document = create_document() references: Union[ReferenceList, None] = None diff --git a/finalcif/template/machines/D8_VENTURE.cif b/finalcif/template/machines/D8_VENTURE.cif new file mode 100644 index 00000000..d73e2693 --- /dev/null +++ b/finalcif/template/machines/D8_VENTURE.cif @@ -0,0 +1,16 @@ +data_D8__VENTURE +_diffrn_ambient_environment N~2~ +_diffrn_radiation_monochromator 'mirror optics' +_diffrn_measurement_ambient_temperature_device_make 'Oxford Cryostream 800' +_diffrn_radiation_probe x-ray +_diffrn_source 'microfocus sealed X-ray tube' +_diffrn_source_type 'Incoatec I\ms' +_diffrn_source_voltage 50 +_diffrn_source_current 1.40 +_diffrn_detector CPAD +_diffrn_detector_type 'Bruker PHOTON III' +_diffrn_detector_area_resol_mean 7.41 +_diffrn_measurement_device 'three-circle diffractometer' +_diffrn_measurement_device_type 'Bruker D8 VENTURE dual wavelength Mo/Cu' +_diffrn_measurement_method '\w and \f scans' +_diffrn_measurement_specimen_support 'MiTeGen micromount' \ No newline at end of file diff --git a/finalcif/template/templates.py b/finalcif/template/templates.py index 7041ea6b..c5d35319 100644 --- a/finalcif/template/templates.py +++ b/finalcif/template/templates.py @@ -1,17 +1,16 @@ +from __future__ import annotations from contextlib import suppress from pathlib import Path from typing import List +from typing import TYPE_CHECKING from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QFileDialog, QListWidgetItem -from typing import TYPE_CHECKING - if TYPE_CHECKING: from finalcif.appwindow import AppWindow - -from finalcif.tools.settings import FinalCifSettings + from finalcif.tools.settings import FinalCifSettings class ReportTemplates: @@ -19,7 +18,7 @@ class ReportTemplates: Displays the list of report templates in the options menu. """ - def __init__(self, app: 'AppWindow', settings: FinalCifSettings): + def __init__(self, app: AppWindow, settings: FinalCifSettings): self.app = app self.settings = settings self.lw = self.app.ui.docxTemplatesListWidget @@ -33,16 +32,17 @@ def __init__(self, app: 'AppWindow', settings: FinalCifSettings): def add_new_template(self, templ_path: str = '') -> None: if not templ_path: - templ_path, _ = QFileDialog.getOpenFileName(filter="DOCX file (*.docx)", initialFilter="DOCX file (*.docx)", - caption='Open a Report Template File') + templ_path, _ = QFileDialog.getOpenFileName(filter="DOCX file (*.docx);; html file (*.html *.tmpl)", + initialFilter="DOCX file (*.docx)", + caption='Open a Report Template File', parent=self.app) itemslist = self.get_templates_list_from_widget() self.app.status_bar.show_message('') if templ_path in itemslist: self.app.status_bar.show_message('This templates is already in the list.', 10) print('This templates is already in the list.') return - if not Path(templ_path).exists() or not Path(templ_path).is_file() \ - or not Path(templ_path).name.endswith('.docx'): + if (not Path(templ_path).exists() or not Path(templ_path).is_file() + or not Path(templ_path).suffix in ('.docx', '.html', '.tmpl')): self.app.status_bar.show_message('This template does not exist or is unreadable.', 10) print('This template does not exist or is unreadable.', Path(templ_path).resolve()) return diff --git a/finalcif/tools/misc.py b/finalcif/tools/misc.py index 7ce2b9ef..2ca829d8 100644 --- a/finalcif/tools/misc.py +++ b/finalcif/tools/misc.py @@ -260,6 +260,8 @@ def make_numbered(items): # '_space_group_centring_type', # seems to be used nowere # '_exptl_absorpt_special_details', # This is not official?!? + +# These keywords will end up in the CIF in any case: essential_keys = ( # '_atom_sites_solution_secondary' # '_diffrn_measurement_specimen_adhesive' @@ -298,10 +300,10 @@ def make_numbered(items): '_computing_data_collection', '_computing_data_reduction', '_computing_molecular_graphics', - '_computing_publication_material', + #'_computing_publication_material', '_computing_structure_refinement', '_computing_structure_solution', - '_diffrn_ambient_environment', + #'_diffrn_ambient_environment', '_diffrn_ambient_temperature', '_diffrn_detector', '_diffrn_detector_area_resol_mean', diff --git a/finalcif/tools/options.py b/finalcif/tools/options.py index 7d271873..a2c890d4 100644 --- a/finalcif/tools/options.py +++ b/finalcif/tools/options.py @@ -9,7 +9,6 @@ def __init__(self, ui: Ui_FinalCifWindow = None, settings: FinalCifSettings = No """ :param ui: UI of FinalCif :param settings: Settings of FinalCif - :param debug: If turned on, this object can set and get attributes without using the settings. """ self.ui = ui self.settings = settings diff --git a/tests/test_main_table.py b/tests/test_main_table.py index 44b6f9f7..20fd477b 100644 --- a/tests/test_main_table.py +++ b/tests/test_main_table.py @@ -56,9 +56,6 @@ def get_background_color(self, key: str, col: int) -> QColor: ###### - def test_rowcounts(self): - self.assertEqual(139, self.myapp.ui.cif_main_table.rowCount()) - def test_delete_row(self): self.myapp.ui.cif_main_table.delete_row(self.key_row('_audit_update_record')) self.assertEqual(138, self.myapp.ui.cif_main_table.rowCount()) @@ -77,9 +74,6 @@ def test_get_text_by_key(self): self.assertEqual('', self.cell_text('_atom_sites_solution_hydrogens', Column.DATA)) self.assertEqual('', self.cell_text('_atom_sites_solution_hydrogens', Column.EDIT)) - def test_Crystallographer_in_equipment_list(self): - self.assertEqual('Crystallographer Details', self.myapp.ui.EquipmentTemplatesListWidget.item(1).text()) - def test_load_equipment(self): self.myapp.equipment.import_equipment_from_file(str(data.parent / 'test-data/Crystallographer_Details.cif')) # make sure contact author is selected @@ -99,23 +93,10 @@ def test_field_types(self): self.assertEqual("", str(self.myapp.ui.cif_main_table.widget_from_key('_atom_sites_solution_hydrogens', Column.CIF).__class__)) - - def test_combobox_field(self): self.assertEqual("", str(self.myapp.ui.cif_main_table.widget_from_key('_atom_sites_solution_hydrogens', Column.EDIT).__class__)) - def test_plaintextedit_field(self): - self.assertEqual("", - str(self.myapp.ui.cif_main_table.widget_from_key('_audit_contact_author_address', - Column.CIF).__class__)) - self.assertEqual("", - str(self.myapp.ui.cif_main_table.widget_from_key('_audit_contact_author_address', - Column.DATA).__class__)) - self.assertEqual("", - str(self.myapp.ui.cif_main_table.widget_from_key('_audit_contact_author_address', - Column.EDIT).__class__)) - def test_multicif(self): self.assertEqual(False, self.myapp.cif.is_multi_cif) self.assertEqual(1, len(self.myapp.cif.doc)) diff --git a/tests/test_workfolder.py b/tests/test_workfolder.py index 8de88802..dd90a20a 100644 --- a/tests/test_workfolder.py +++ b/tests/test_workfolder.py @@ -18,6 +18,7 @@ data = Path('tests') + class TestFileIsOpened(unittest.TestCase): """A CIF fle in a complete work folder""" @@ -129,10 +130,6 @@ def testDataColumn(self): self.cell_text('_publ_section_references', Column.DATA)) self.assertEqual('geom', self.cell_text('_atom_sites_solution_hydrogens', 0)) self.assertEqual('', self.cell_text('_atom_sites_solution_hydrogens', Column.DATA)) - self.assertEqual( - """FinalCif V{} by Daniel Kratzert, Freiburg {}, https://dkratzert.de/finalcif.html""".format(VERSION, - datetime.now().year), - self.cell_text('_audit_creation_method', Column.DATA)) def test_abs_configuration_combo(self): self.assertEqual(6, self.key_row('_chemical_absolute_configuration')) @@ -168,11 +165,6 @@ def test_combo_items_radiation(self): self.assertEqual(['', 'Mo Kα', 'Cu Kα', 'Ag Kα', 'In Kα', 'Ga Kα', 'Fe Kα', 'W Kα'], self.get_combobox_items(row, Column.EDIT)) - def test_ambient_conditions_combo(self): - # Test if N~~2~ is correctly translated to N_2 - row = self.key_row('_diffrn_ambient_environment') - self.assertEqual('N₂', self.get_combobox_items(row, Column.EDIT)[1]) - def test_combo_items_exptl_crystal_description(self): row = self.key_row('_exptl_crystal_description') self.assertEqual(['', 'block', 'needle', 'plate', 'prism', 'sphere'], self.get_combobox_items(row, Column.EDIT)) @@ -201,7 +193,6 @@ def test_background_color_data(self): self.myapp.ui.cif_main_table.widget_from_key('_computing_data_reduction', Column.DATA).styleSheet()) - def test_background_color_theta_max(self): self.assertEqual('', self.myapp.ui.cif_main_table.widget_from_key('_cell_measurement_theta_max', Column.CIF).styleSheet()) self.assertEqual('background-color: #d9ffc9;', @@ -210,18 +201,9 @@ def test_background_color_theta_max(self): self.assertEqual('', self.myapp.ui.cif_main_table.widget_from_key('_cell_measurement_theta_max', Column.EDIT).styleSheet()) - def test_color(self): self.assertEqual('', self.myapp.ui.cif_main_table.widget_from_key('_computing_molecular_graphics', Column.DATA).styleSheet()) - def test_chemical_formula_moiety(self): - self.assertEqual('?', - self.cell_text('_chemical_formula_moiety', Column.CIF)) - self.assertEqual('', - self.cell_text('_chemical_formula_moiety', Column.DATA)) - self.assertEqual('', - self.cell_text('_chemical_formula_moiety', Column.EDIT)) - def test_exptl_crystal_size(self): self.assertEqual('0.220', self.cell_text('_exptl_crystal_size_max', Column.DATA)) self.assertEqual('0.100', self.cell_text('_exptl_crystal_size_mid', Column.DATA)) @@ -249,54 +231,11 @@ def allrows_test_key(self, key: str = '', results: list = None): self.assertEqual(r, self.cell_text(key, n)) def test_equipment_click_machine(self): - self.equipment_click('APEX2 QUAZAR') + self.equipment_click('D8_VENTURE') self.allrows_test_key('_diffrn_measurement_method', ['?', 'ω and ϕ scans', 'ω and ϕ scans']) self.allrows_test_key('_diffrn_measurement_specimen_support', ['?', 'MiTeGen micromount', 'MiTeGen micromount']) - # unittest.SkipTest('') - def test_equipment_click_machine_oxford_0(self): - self.equipment_click('APEX2 QUAZAR') - # We have a value which is new. So a row at start is created and only the CIF column is populated - self.assertEqual('?', self.cell_text('_diffrn_measurement_ambient_temperature_device_make', Column.CIF)) - - def test_equipment_click_machine_oxford_1(self): - self.equipment_click('APEX2 QUAZAR') - self.assertEqual('Oxford Cryostream 800', - self.cell_text('_diffrn_measurement_ambient_temperature_device_make', Column.DATA)) - self.assertEqual('Oxford Cryostream 800', - self.cell_text('_diffrn_measurement_ambient_temperature_device_make', Column.EDIT)) - - def test_equipment_click_author_address_0(self): - # Check if click on author adds the address to second and third column: - self.equipment_click('Crystallographer Details') - self.assertEqual('?', self.cell_text('_audit_contact_author_address', Column.CIF)) - self.assertEqual(unify_line_endings(addr), self.cell_text('_audit_contact_author_address', Column.DATA)) - self.assertEqual(unify_line_endings(addr), self.cell_text('_audit_contact_author_address', Column.EDIT)) - - def test_contact_author_name_0(self): - self.equipment_click('Crystallographer Details') - self.assertEqual('?', self.cell_text('_audit_contact_author_name', Column.CIF)) - self.assertEqual('Dr. Daniel Kratzert', self.cell_text('_audit_contact_author_name', Column.DATA)) - self.assertEqual('Dr. Daniel Kratzert', self.cell_text('_audit_contact_author_name', Column.EDIT)) - - def test_contact_author_cellwidget_bevore_click(self): - self.assertEqual('_cell_measurement_theta_min', self.myapp.ui.cif_main_table.vheaderitems[5]) - self.assertEqual('2.547', self.myapp.ui.cif_main_table.getText(5, Column.DATA)) - - def test_contact_author_cellwidget_after(self): - self.equipment_click('Crystallographer Details') - self.assertEqual(self.myapp.ui.cif_main_table.vheaderitems[5], '_audit_contact_author_name') - self.assertEqual('Dr. Daniel Kratzert', self.myapp.ui.cif_main_table.getText(5, Column.DATA)) - self.assertEqual("", self.cell_widget_class(5, Column.CIF)) - self.assertEqual("", - self.cell_widget_class(5, Column.DATA)) - self.assertEqual("", - self.cell_widget_class(5, Column.EDIT)) - - def test_addr(self): - self.assertNotEqual(unify_line_endings(addr), self.cell_text('_audit_contact_author_address', Column.EDIT)) - def test_addr_after_author_click_0(self): self.equipment_click('Crystallographer Details') self.assertEqual(unify_line_endings(addr), self.cell_text('_audit_contact_author_address', Column.EDIT)) @@ -394,8 +333,6 @@ def testDataColumn(self): "G.M. (2015). Acta Cryst. C71, 3-8.", self.cell_text('_publ_section_references', Column.DATA)) self.assertEqual('geom', self.cell_text('_atom_sites_solution_hydrogens', 0)) self.assertEqual('', self.cell_text('_atom_sites_solution_hydrogens', Column.DATA)) - self.assertEqual(f"FinalCif V{VERSION} by Daniel Kratzert, Freiburg {datetime.now().year}, " - f"https://dkratzert.de/finalcif.html", self.cell_text('_audit_creation_method', Column.DATA)) if __name__ == '__main__':