finalcif 169__tar.gz → 170__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. {finalcif-169 → finalcif-170}/Finalcif_installer_win.spec +1 -1
  2. {finalcif-169 → finalcif-170}/PKG-INFO +1 -1
  3. finalcif-170/finalcif/__init__.py +3 -0
  4. {finalcif-169 → finalcif-170}/finalcif/appwindow.py +57 -20
  5. {finalcif-169 → finalcif-170}/finalcif/cif/cif_file_io.py +18 -13
  6. {finalcif-169 → finalcif-170}/finalcif/equip_property/author_loop_templates.py +2 -1
  7. {finalcif-169 → finalcif-170}/finalcif/equip_property/equipment.py +2 -1
  8. {finalcif-169 → finalcif-170}/finalcif/equip_property/properties.py +3 -2
  9. {finalcif-169 → finalcif-170}/finalcif/finalcif_start.py +3 -0
  10. {finalcif-169 → finalcif-170}/finalcif/gui/cif_table_view.py +2 -1
  11. finalcif-170/finalcif/gui/dialogs.py +447 -0
  12. finalcif-170/finalcif/gui/file_editor.py +355 -0
  13. {finalcif-169 → finalcif-170}/finalcif/gui/import_selector.py +48 -29
  14. {finalcif-169 → finalcif-170}/finalcif/gui/import_selector_ui.py +13 -3
  15. {finalcif-169 → finalcif-170}/finalcif/gui/import_selector_ui.ui +10 -0
  16. {finalcif-169 → finalcif-170}/finalcif/gui/loops.py +17 -0
  17. {finalcif-169 → finalcif-170}/finalcif/gui/spell_check_edit.py +3 -1
  18. {finalcif-169 → finalcif-170}/finalcif/gui/squeeze_dialog.py +11 -1
  19. {finalcif-169 → finalcif-170}/finalcif/gui/syntax_highlighter.py +34 -14
  20. finalcif-170/finalcif/gui/timers.py +21 -0
  21. {finalcif-169 → finalcif-170}/finalcif/gui/vzs_viewer.py +9 -3
  22. {finalcif-169 → finalcif-170}/finalcif/tools/misc.py +9 -0
  23. finalcif-170/finalcif/tools/selfupdate.py +348 -0
  24. {finalcif-169 → finalcif-170}/finalcif/tools/settings.py +50 -14
  25. {finalcif-169 → finalcif-170}/uv.lock +42 -42
  26. finalcif-169/finalcif/__init__.py +0 -3
  27. finalcif-169/finalcif/gui/dialogs.py +0 -268
  28. finalcif-169/finalcif/gui/file_editor.py +0 -123
  29. finalcif-169/update.exe +0 -0
  30. {finalcif-169 → finalcif-170}/.gitattributes +0 -0
  31. {finalcif-169 → finalcif-170}/.githooks/pre-push +0 -0
  32. {finalcif-169 → finalcif-170}/.gitignore +0 -0
  33. {finalcif-169 → finalcif-170}/.python-version +0 -0
  34. {finalcif-169 → finalcif-170}/.ruff.toml +0 -0
  35. {finalcif-169 → finalcif-170}/.run/Make Documentation MAC.run.xml +0 -0
  36. {finalcif-169 → finalcif-170}/.run/compile_ui_files.run.xml +0 -0
  37. {finalcif-169 → finalcif-170}/.run/make documentation Windows.run.xml +0 -0
  38. {finalcif-169 → finalcif-170}/AGENTS.md +0 -0
  39. {finalcif-169 → finalcif-170}/CODE_OF_CONDUCT.md +0 -0
  40. {finalcif-169 → finalcif-170}/CONTRIBUTING.md +0 -0
  41. {finalcif-169 → finalcif-170}/Finalcif_linux_onefile.spec +0 -0
  42. {finalcif-169 → finalcif-170}/Finalcif_mac.spec +0 -0
  43. {finalcif-169 → finalcif-170}/LICENSE +0 -0
  44. {finalcif-169 → finalcif-170}/README.md +0 -0
  45. {finalcif-169 → finalcif-170}/finalcif/app_path.py +0 -0
  46. {finalcif-169 → finalcif-170}/finalcif/cif/__init__.py +0 -0
  47. {finalcif-169 → finalcif-170}/finalcif/cif/all_cif_dicts.py +0 -0
  48. {finalcif-169 → finalcif-170}/finalcif/cif/atoms.py +0 -0
  49. {finalcif-169 → finalcif-170}/finalcif/cif/checkcif/__init__.py +0 -0
  50. {finalcif-169 → finalcif-170}/finalcif/cif/checkcif/checkcif.py +0 -0
  51. {finalcif-169 → finalcif-170}/finalcif/cif/cif_dict_foo.py +0 -0
  52. {finalcif-169 → finalcif-170}/finalcif/cif/cif_keyword_mapping.py +0 -0
  53. {finalcif-169 → finalcif-170}/finalcif/cif/cif_order.py +0 -0
  54. {finalcif-169 → finalcif-170}/finalcif/cif/cod/__init__.py +0 -0
  55. {finalcif-169 → finalcif-170}/finalcif/cif/cod/deposit.py +0 -0
  56. {finalcif-169 → finalcif-170}/finalcif/cif/cod/deposit_check.py +0 -0
  57. {finalcif-169 → finalcif-170}/finalcif/cif/cod/deposition_list.py +0 -0
  58. {finalcif-169 → finalcif-170}/finalcif/cif/cod/doi.py +0 -0
  59. {finalcif-169 → finalcif-170}/finalcif/cif/cod/upload.py +0 -0
  60. {finalcif-169 → finalcif-170}/finalcif/cif/cod/website_parser.py +0 -0
  61. {finalcif-169 → finalcif-170}/finalcif/cif/core_dict.py +0 -0
  62. {finalcif-169 → finalcif-170}/finalcif/cif/hkl.py +0 -0
  63. {finalcif-169 → finalcif-170}/finalcif/cif/modulation_dict.py +0 -0
  64. {finalcif-169 → finalcif-170}/finalcif/cif/powder_dict.py +0 -0
  65. {finalcif-169 → finalcif-170}/finalcif/cif/reference.py +0 -0
  66. {finalcif-169 → finalcif-170}/finalcif/cif/restraints_dict.py +0 -0
  67. {finalcif-169 → finalcif-170}/finalcif/cif/text.py +0 -0
  68. {finalcif-169 → finalcif-170}/finalcif/cif/twin_dict.py +0 -0
  69. {finalcif-169 → finalcif-170}/finalcif/cif/vrf_entry.py +0 -0
  70. {finalcif-169 → finalcif-170}/finalcif/ciforder/__init__.py +0 -0
  71. {finalcif-169 → finalcif-170}/finalcif/ciforder/order.py +0 -0
  72. {finalcif-169 → finalcif-170}/finalcif/ciforder/order_ui.py +0 -0
  73. {finalcif-169 → finalcif-170}/finalcif/ciforder/order_ui.ui +0 -0
  74. {finalcif-169 → finalcif-170}/finalcif/datafiles/__init__.py +0 -0
  75. {finalcif-169 → finalcif-170}/finalcif/datafiles/bruker_data.py +0 -0
  76. {finalcif-169 → finalcif-170}/finalcif/datafiles/bruker_frame.py +0 -0
  77. {finalcif-169 → finalcif-170}/finalcif/datafiles/ccdc_mail.py +0 -0
  78. {finalcif-169 → finalcif-170}/finalcif/datafiles/data.py +0 -0
  79. {finalcif-169 → finalcif-170}/finalcif/datafiles/p4p_reader.py +0 -0
  80. {finalcif-169 → finalcif-170}/finalcif/datafiles/sadabs.py +0 -0
  81. {finalcif-169 → finalcif-170}/finalcif/datafiles/saint.py +0 -0
  82. {finalcif-169 → finalcif-170}/finalcif/datafiles/shelx_lst.py +0 -0
  83. {finalcif-169 → finalcif-170}/finalcif/datafiles/utils.py +0 -0
  84. {finalcif-169 → finalcif-170}/finalcif/displaymol/__init__.py +0 -0
  85. {finalcif-169 → finalcif-170}/finalcif/displaymol/vtk_molecule.py +0 -0
  86. {finalcif-169 → finalcif-170}/finalcif/equip_property/__init__.py +0 -0
  87. {finalcif-169 → finalcif-170}/finalcif/equip_property/tools.py +0 -0
  88. {finalcif-169 → finalcif-170}/finalcif/gui/__init__.py +0 -0
  89. {finalcif-169 → finalcif-170}/finalcif/gui/block_combobox.py +0 -0
  90. {finalcif-169 → finalcif-170}/finalcif/gui/checkcif_browser.py +0 -0
  91. {finalcif-169 → finalcif-170}/finalcif/gui/cif_table_model.py +0 -0
  92. {finalcif-169 → finalcif-170}/finalcif/gui/combobox.py +0 -0
  93. {finalcif-169 → finalcif-170}/finalcif/gui/custom_classes.py +0 -0
  94. {finalcif-169 → finalcif-170}/finalcif/gui/edit_button.py +0 -0
  95. {finalcif-169 → finalcif-170}/finalcif/gui/equipmenttable.py +0 -0
  96. {finalcif-169 → finalcif-170}/finalcif/gui/finalcif_gui_ui.py +0 -0
  97. {finalcif-169 → finalcif-170}/finalcif/gui/finalcif_gui_ui.ui +0 -0
  98. {finalcif-169 → finalcif-170}/finalcif/gui/loop_creator.py +0 -0
  99. {finalcif-169 → finalcif-170}/finalcif/gui/loop_creator_ui.py +0 -0
  100. {finalcif-169 → finalcif-170}/finalcif/gui/loop_creator_ui.ui +0 -0
  101. {finalcif-169 → finalcif-170}/finalcif/gui/mainstackwidget.py +0 -0
  102. {finalcif-169 → finalcif-170}/finalcif/gui/mixins.py +0 -0
  103. {finalcif-169 → finalcif-170}/finalcif/gui/new_key_dialog.py +0 -0
  104. {finalcif-169 → finalcif-170}/finalcif/gui/new_key_dialog_ui.py +0 -0
  105. {finalcif-169 → finalcif-170}/finalcif/gui/new_key_dialog_ui.ui +0 -0
  106. {finalcif-169 → finalcif-170}/finalcif/gui/plaintextedit.py +0 -0
  107. {finalcif-169 → finalcif-170}/finalcif/gui/playground.py +0 -0
  108. {finalcif-169 → finalcif-170}/finalcif/gui/propertytable.py +0 -0
  109. {finalcif-169 → finalcif-170}/finalcif/gui/shelx_navigation.py +0 -0
  110. {finalcif-169 → finalcif-170}/finalcif/gui/single_line_textedit.py +0 -0
  111. {finalcif-169 → finalcif-170}/finalcif/gui/text_templates_ui.py +0 -0
  112. {finalcif-169 → finalcif-170}/finalcif/gui/text_templates_ui.ui +0 -0
  113. {finalcif-169 → finalcif-170}/finalcif/gui/text_value_editor.py +0 -0
  114. {finalcif-169 → finalcif-170}/finalcif/gui/validators.py +0 -0
  115. {finalcif-169 → finalcif-170}/finalcif/gui/vrf_classes.py +0 -0
  116. {finalcif-169 → finalcif-170}/finalcif/icon/__init__.py +0 -0
  117. {finalcif-169 → finalcif-170}/finalcif/icon/finalcif.png +0 -0
  118. {finalcif-169 → finalcif-170}/finalcif/icon/finalcif2.ico +0 -0
  119. {finalcif-169 → finalcif-170}/finalcif/icon/finalcif2.png +0 -0
  120. {finalcif-169 → finalcif-170}/finalcif/icon/multitable.ico +0 -0
  121. {finalcif-169 → finalcif-170}/finalcif/icon/multitable.png +0 -0
  122. {finalcif-169 → finalcif-170}/finalcif/icon/multitable.xcf +0 -0
  123. {finalcif-169 → finalcif-170}/finalcif/report/__init__.py +0 -0
  124. {finalcif-169 → finalcif-170}/finalcif/report/archive_report.py +0 -0
  125. {finalcif-169 → finalcif-170}/finalcif/report/gui/__init__.py +0 -0
  126. {finalcif-169 → finalcif-170}/finalcif/report/gui/mainwindow.py +0 -0
  127. {finalcif-169 → finalcif-170}/finalcif/report/gui/mainwindow.ui +0 -0
  128. {finalcif-169 → finalcif-170}/finalcif/report/mtools.py +0 -0
  129. {finalcif-169 → finalcif-170}/finalcif/report/references.py +0 -0
  130. {finalcif-169 → finalcif-170}/finalcif/report/report_text.py +0 -0
  131. {finalcif-169 → finalcif-170}/finalcif/report/symm.py +0 -0
  132. {finalcif-169 → finalcif-170}/finalcif/report/tables.py +0 -0
  133. {finalcif-169 → finalcif-170}/finalcif/report/templated_report.py +0 -0
  134. {finalcif-169 → finalcif-170}/finalcif/template/__init__.py +0 -0
  135. {finalcif-169 → finalcif-170}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  136. {finalcif-169 → finalcif-170}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  137. {finalcif-169 → finalcif-170}/finalcif/template/mathml2omml.xsl +0 -0
  138. {finalcif-169 → finalcif-170}/finalcif/template/report.tex +0 -0
  139. {finalcif-169 → finalcif-170}/finalcif/template/report.tmpl +0 -0
  140. {finalcif-169 → finalcif-170}/finalcif/template/report2.tex +0 -0
  141. {finalcif-169 → finalcif-170}/finalcif/template/report_default.docx +0 -0
  142. {finalcif-169 → finalcif-170}/finalcif/template/template1.docx +0 -0
  143. {finalcif-169 → finalcif-170}/finalcif/template/template_for_multitable.docx +0 -0
  144. {finalcif-169 → finalcif-170}/finalcif/template/template_text.docx +0 -0
  145. {finalcif-169 → finalcif-170}/finalcif/template/template_without_text.docx +0 -0
  146. {finalcif-169 → finalcif-170}/finalcif/template/templates.py +0 -0
  147. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/FakePlasTeX/FakeContext.py +0 -0
  148. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/FakePlasTeX/FakeTokenizer.py +0 -0
  149. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/FakePlasTeX/README.md +0 -0
  150. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/FakePlasTeX/TokenizerPassThru.py +0 -0
  151. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/LICENSE +0 -0
  152. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/__init__.py +0 -0
  153. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/u2l.py +0 -0
  154. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/unicode-math-table.tex +0 -0
  155. {finalcif-169 → finalcif-170}/finalcif/template/unicode2latex/unicode-math-xetex.sty +0 -0
  156. {finalcif-169 → finalcif-170}/finalcif/template/xsl/convert.py +0 -0
  157. {finalcif-169 → finalcif-170}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  158. {finalcif-169 → finalcif-170}/finalcif/template/xsl/xmltolatex.xsl +0 -0
  159. {finalcif-169 → finalcif-170}/finalcif/tools/__init__.py +0 -0
  160. {finalcif-169 → finalcif-170}/finalcif/tools/chemparse.py +0 -0
  161. {finalcif-169 → finalcif-170}/finalcif/tools/download.py +0 -0
  162. {finalcif-169 → finalcif-170}/finalcif/tools/dsrmath.py +0 -0
  163. {finalcif-169 → finalcif-170}/finalcif/tools/formal_charge.py +0 -0
  164. {finalcif-169 → finalcif-170}/finalcif/tools/options.py +0 -0
  165. {finalcif-169 → finalcif-170}/finalcif/tools/platon.py +0 -0
  166. {finalcif-169 → finalcif-170}/finalcif/tools/pupdate.py +0 -0
  167. {finalcif-169 → finalcif-170}/finalcif/tools/shred.py +0 -0
  168. {finalcif-169 → finalcif-170}/finalcif/tools/space_groups.py +0 -0
  169. {finalcif-169 → finalcif-170}/finalcif/tools/spgr_format.py +0 -0
  170. {finalcif-169 → finalcif-170}/finalcif/tools/squeeze.py +0 -0
  171. {finalcif-169 → finalcif-170}/finalcif/tools/statusbar.py +0 -0
  172. {finalcif-169 → finalcif-170}/finalcif/tools/sumformula.py +0 -0
  173. {finalcif-169 → finalcif-170}/finalcif/tools/z_from_packing.py +0 -0
  174. {finalcif-169 → finalcif-170}/finalcif.exe +0 -0
  175. {finalcif-169 → finalcif-170}/platon/README.txt +0 -0
  176. {finalcif-169 → finalcif-170}/platon/compile_platon.bat +0 -0
  177. {finalcif-169 → finalcif-170}/platon/platon_special.exe +0 -0
  178. {finalcif-169 → finalcif-170}/platon/platon_special.f +0 -0
  179. {finalcif-169 → finalcif-170}/pylock.toml +0 -0
  180. {finalcif-169 → finalcif-170}/pyproject.toml +0 -0
  181. {finalcif-169 → finalcif-170}/run_and_capture.py +0 -0
  182. {finalcif-169 → finalcif-170}/run_finalcif +0 -0
  183. {finalcif-169 → finalcif-170}/run_finalcif.bat +0 -0
  184. {finalcif-169 → finalcif-170}/screenshots/finalcif_checkcif.png +0 -0
  185. {finalcif-169 → finalcif-170}/screenshots/finalcif_details.png +0 -0
  186. {finalcif-169 → finalcif-170}/screenshots/finalcif_loops.png +0 -0
  187. {finalcif-169 → finalcif-170}/screenshots/finalcif_loops2.png +0 -0
  188. {finalcif-169 → finalcif-170}/screenshots/finalcif_main.png +0 -0
  189. {finalcif-169 → finalcif-170}/screenshots/finalcif_report.png +0 -0
  190. {finalcif-169 → finalcif-170}/screenshots/finalcif_responses.png +0 -0
  191. {finalcif-169 → finalcif-170}/screenshots/loops_column_buttons.png +0 -0
  192. {finalcif-169 → finalcif-170}/work/compare_z_platon.py +0 -0
  193. {finalcif-169 → finalcif-170}/work/diagnose_1519506.py +0 -0
  194. {finalcif-169 → finalcif-170}/work/diagnose_z.py +0 -0
  195. {finalcif-169 → finalcif-170}/work/investigate_failures.py +0 -0
  196. {finalcif-169 → finalcif-170}/work/validate_cod.py +0 -0
  197. {finalcif-169 → finalcif-170}/work/validate_zprime.py +0 -0
@@ -9,7 +9,7 @@ site_packages = next(p for p in sys.path if 'site-packages' in p)
9
9
 
10
10
  a = Analysis(['finalcif/finalcif_start.py'],
11
11
  pathex=['finalcif'],
12
- binaries=[('update.exe', '.')],
12
+ binaries=[],
13
13
  datas=[('finalcif/gui', 'gui'),
14
14
  (path.join(site_packages, "docx", "templates"), 'finalcif/docx/templates'),
15
15
  ('finalcif/template', 'template'), ('finalcif/icon', 'icon'),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finalcif
3
- Version: 169
3
+ Version: 170
4
4
  Summary: CIF file editor
5
5
  Project-URL: Homepage, https://dkratzert.de/finalcif.html
6
6
  Project-URL: Bug Tracker, https://github.com/dkratzert/FinalCif/issues
@@ -0,0 +1,3 @@
1
+
2
+ VERSION = 170
3
+ __version__ = VERSION
@@ -70,13 +70,15 @@ from finalcif.gui.plaintextedit import MyQPlainTextEdit
70
70
  from finalcif.gui.validators import calculated_z_validator
71
71
  from finalcif.gui.checkcif_browser import CheckCifBrowser
72
72
  from finalcif.gui.text_value_editor import MyTextTemplateEdit, TextEditItem
73
+ from finalcif.gui.timers import single_shot
73
74
  from finalcif.cif.vrf_entry import VRFEntry
74
75
  from finalcif.gui.vrf_classes import MyVRFContainer
75
76
  from finalcif.template.templates import ReportTemplates
76
77
  from finalcif.tools.download import MyDownloader
77
78
  from finalcif.tools.dsrmath import my_isnumeric
78
79
  from finalcif.tools.misc import (next_path, celltxt, to_float, is_database_number,
79
- open_file, strip_finalcif_of_name, file_age_in_days, open_in_text_editor)
80
+ open_file, strip_finalcif_of_name, file_age_in_days, open_in_text_editor,
81
+ running_inside_unit_test as is_unit_test)
80
82
  from finalcif.tools.options import Options
81
83
  from finalcif.tools.platon import PlatonRunner
82
84
  from finalcif.tools.settings import FinalCifSettings
@@ -155,6 +157,7 @@ class _StructureFactorReportThread(QtCore.QThread):
155
157
 
156
158
 
157
159
  class AppWindow(QMainWindow):
160
+ thread_shutdown_timeout_ms = 10_000
158
161
 
159
162
  def __init__(self, file: Path | None = None):
160
163
  super().__init__()
@@ -163,6 +166,7 @@ class AppWindow(QMainWindow):
163
166
  self.thread_version = None
164
167
  self.worker = None
165
168
  self._z_thread: _ZEstimatorThread | None = None
169
+ self.structure_factor_thread: _StructureFactorReportThread | None = None
166
170
  self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
167
171
  # This prevents some things to happen during unit tests:
168
172
  # Open of target dir of shred cif,
@@ -269,9 +273,9 @@ class AppWindow(QMainWindow):
269
273
 
270
274
  @property
271
275
  def running_inside_unit_test(self):
272
- if "RUNNING_TEST" in os.environ:
276
+ if is_unit_test():
273
277
  if DEBUG:
274
- print(f'pytest process running: {os.environ["PYTEST_CURRENT_TEST"]}')
278
+ print(f'pytest process running: {os.environ.get("PYTEST_CURRENT_TEST")}')
275
279
  return True
276
280
  return False
277
281
 
@@ -540,8 +544,8 @@ class AppWindow(QMainWindow):
540
544
 
541
545
  def set_video_image_for_report(self):
542
546
  image_filename = self.cif.finalcif_file_prefixed(prefix='', suffix='_video-finalcif.png')
543
- self.video.save_image(image_filename)
544
- self.options.video_image = image_filename
547
+ if self.video.save_image(image_filename):
548
+ self.options.video_image = image_filename
545
549
 
546
550
  def _select_crystal_video(self, video_file: Path | None = None) -> None:
547
551
  if video_file is None:
@@ -875,8 +879,26 @@ class AppWindow(QMainWindow):
875
879
  with suppress(Exception):
876
880
  self._savesize()
877
881
  self.settings.flush()
882
+ self._stop_background_threads()
878
883
  super().closeEvent(event)
879
884
 
885
+ def _stop_background_threads(self) -> None:
886
+ """Finish all worker threads before Qt destroys them together with this window.
887
+
888
+ Qt aborts the whole process when a running QThread is deleted. findChildren()
889
+ is used instead of the individual attributes because a superseded worker may
890
+ still run after its attribute was cleared.
891
+ """
892
+ for thread in self.findChildren(QtCore.QThread):
893
+ if not thread.isRunning():
894
+ continue
895
+ # Prevent a result from being delivered into the dying window.
896
+ thread.blockSignals(True)
897
+ thread.quit()
898
+ if not thread.wait(self.thread_shutdown_timeout_ms):
899
+ thread.terminate()
900
+ thread.wait()
901
+
880
902
  def show_help(self) -> None:
881
903
  QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://dkratzert.de/files/finalcif/docs/'))
882
904
 
@@ -916,7 +938,27 @@ class AppWindow(QMainWindow):
916
938
  dialog = SqueezeSolventDialog(cif=self.cif, mode=mode, parent=self)
917
939
  if dialog._cancelled:
918
940
  return False
919
- return bool(dialog.exec())
941
+ accepted = bool(dialog.exec())
942
+ if accepted:
943
+ self._refresh_after_squeeze_dialog(dialog)
944
+ return accepted
945
+
946
+ def _refresh_after_squeeze_dialog(self, dialog: SqueezeSolventDialog) -> None:
947
+ """
948
+ Show the values written by the solvent dialog without leaving the current page.
949
+ """
950
+ self._update_squeeze_details_row(dialog.details_key, dialog.details_text)
951
+ if self.ui.MainStackedWidget.on_loops_page():
952
+ self.ui.loops_page.reload()
953
+
954
+ def _update_squeeze_details_row(self, key: str, text: str) -> None:
955
+ if not self.ui.cif_main_table.rowCount():
956
+ return
957
+ if not self.ui.cif_main_table.has_key(key):
958
+ if not text:
959
+ return
960
+ self.add_row(key=key, value=text, at_start=True)
961
+ self.ui.cif_main_table.setText(key=key, column=Column.EDIT, txt=text)
920
962
 
921
963
  def _ccdc_deposit(self) -> None:
922
964
  """
@@ -1727,8 +1769,7 @@ class AppWindow(QMainWindow):
1727
1769
  fixfont.setPointSize(fixfont.pointSize() + 2)
1728
1770
  doc.setDefaultFont(fixfont)
1729
1771
  final_textedit.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
1730
- final_textedit.setPlainText(self.cif.finalcif_file.read_text(encoding='utf-8', errors='ignore'))
1731
- QtCore.QTimer().singleShot(0, lambda: final_textedit.highlighter.setDocument(doc))
1772
+ final_textedit.set_cif_text(self.cif.as_saved_string())
1732
1773
 
1733
1774
  def import_additional_cif(self, filename: str):
1734
1775
  """
@@ -1818,18 +1859,16 @@ class AppWindow(QMainWindow):
1818
1859
  if not self.cif.chars_ok:
1819
1860
  self.warn_about_bad_cif()
1820
1861
  # Do this only when sure we can load the file:
1821
- QtCore.QTimer.singleShot(0, lambda: self.save_current_recent_files_list(filepath))
1862
+ single_shot(self, 0, lambda: self.save_current_recent_files_list(filepath))
1822
1863
  self._load_block(block, load_changes=load_changes)
1823
1864
  self.add_data_names_to_combobox()
1824
1865
  self.ui.datanameComboBox.setCurrentIndex(block)
1825
1866
  self.ui.datanameComboBox.blockSignals(False)
1826
- QtCore.QTimer.singleShot(500, self.ui.cif_main_table.resizeRowsToContents)
1867
+ single_shot(self, 500, self.ui.cif_main_table.resizeRowsToContents)
1827
1868
  if self.cif.is_multi_cif:
1828
1869
  self._flash_block_combobox()
1829
1870
  self.cif.set_order_keys(self.ui.cifOrderWidget.order_keys)
1830
- QtCore.QTimer().singleShot(0, self._find_video)
1831
- # Enable to find widgets without parent:
1832
- # QtCore.QTimer(self).singleShot(1000, self.find_widgets_without_parent)
1871
+ single_shot(self, 0, self._find_video)
1833
1872
 
1834
1873
  def _find_video(self) -> None:
1835
1874
  self.video.reset()
@@ -1853,8 +1892,8 @@ class AppWindow(QMainWindow):
1853
1892
  pal.setColor(QtGui.QPalette.ColorRole.Base, light_blue)
1854
1893
  self.ui.datanameComboBox.setAutoFillBackground(True)
1855
1894
  # short after start, because window size is not finished before:
1856
- QtCore.QTimer(self).singleShot(1500, lambda: self.ui.datanameComboBox.setPalette(pal))
1857
- QtCore.QTimer(self).singleShot(2600, lambda: self.ui.datanameComboBox.setPalette(orig_pal))
1895
+ single_shot(self, 1500, lambda: self.ui.datanameComboBox.setPalette(pal))
1896
+ single_shot(self, 2600, lambda: self.ui.datanameComboBox.setPalette(orig_pal))
1858
1897
 
1859
1898
  def add_data_names_to_combobox(self) -> None:
1860
1899
  self.ui.datanameComboBox.clear()
@@ -1932,8 +1971,7 @@ class AppWindow(QMainWindow):
1932
1971
  self.show_residuals()
1933
1972
  self.redraw_molecule()
1934
1973
  self._update_z_label()
1935
- t = QtCore.QTimer(self)
1936
- t.singleShot(1000, self.check_cecksums)
1974
+ single_shot(self, 1000, self.check_cecksums)
1937
1975
 
1938
1976
  def _delete_current_block(self, index: int) -> None:
1939
1977
  self.cif.delete_block(index)
@@ -2132,8 +2170,7 @@ class AppWindow(QMainWindow):
2132
2170
  self.ui.peakLineEdit.setText("{} / {}".format(peak, self.cif['_refine_diff_density_min']))
2133
2171
  self._show_shelx_file()
2134
2172
  try:
2135
- QtCore.QTimer(self).singleShot(0, lambda: self.view_molecule(reset_view=True))
2136
- # threading.Thread(target=self.view_molecule).start()
2173
+ single_shot(self, 0, lambda: self.view_molecule(reset_view=True))
2137
2174
  except Exception:
2138
2175
  print('Molecule view crashed!')
2139
2176
 
@@ -2577,7 +2614,7 @@ class AppWindow(QMainWindow):
2577
2614
  table.clearSelection()
2578
2615
 
2579
2616
  def check_cecksums(self):
2580
- if "RUNNING_TEST" in os.environ:
2617
+ if self.running_inside_unit_test:
2581
2618
  return
2582
2619
  if not self.cif.test_res_checksum():
2583
2620
  show_res_checksum_warning(parent=self)
@@ -386,6 +386,20 @@ class CifContainer:
386
386
  else:
387
387
  self.block.set_pair(key, quote(txt))
388
388
 
389
+ def write_options(self) -> gemmi.cif.WriteOptions:
390
+ """The gemmi write options used to save and to display the CIF."""
391
+ options = gemmi.cif.WriteOptions()
392
+ options.prefer_pairs = False
393
+ options.compact = False
394
+ options.align_pairs = 33
395
+ options.align_loops = 15
396
+ options.misuse_hash = False
397
+ return options
398
+
399
+ def as_saved_string(self) -> str:
400
+ """The CIF text exactly as :meth:`save` writes it to disk."""
401
+ return self.doc.as_string(options=self.write_options())
402
+
389
403
  def save(self, filename: Path | None = None) -> None:
390
404
  """
391
405
  Saves the current cif file. It uses the '[basename]-finalcif.cif' suffix by default.
@@ -400,16 +414,7 @@ class CifContainer:
400
414
  raise PermissionError(f'Failed to open {filename.resolve()} for writing: Operation not permitted')
401
415
  self.order_cif_keys()
402
416
  print('Saving CIF to', Path(filename).resolve())
403
- if Version(gemmi.__version__) > Version('0.6.3'):
404
- options = gemmi.cif.WriteOptions()
405
- options.prefer_pairs = False
406
- options.compact = False
407
- options.align_pairs = 33
408
- options.align_loops = 15
409
- options.misuse_hash = False
410
- self.doc.write_file(str(filename), options=options)
411
- else:
412
- self.doc.write_file(str(filename), gemmi.cif.Style.Indent35)
417
+ self.doc.write_file(str(filename), options=self.write_options())
413
418
 
414
419
  def is_writable(self, filepath: Path | str) -> bool:
415
420
  filepath = Path(filepath)
@@ -973,9 +978,9 @@ class CifContainer:
973
978
  publ_loop = self.block.find_loop('_geom_angle_publ_flag') or len(label1) * ['?']
974
979
  angle = namedtuple('angle', ('label1', 'label2', 'label3', 'angle_val', 'symm1', 'symm2'))
975
980
  for label1, label2, label3, angle_val, symm1, symm2, publ in (
976
- zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop, strict=False)):
981
+ zip(label1, label2, label3, angle_val, symm1, symm2, publ_loop, strict=False)):
977
982
  if ((without_H and (self.ishydrogen(label1) or self.ishydrogen(label2) or self.ishydrogen(label3))) or
978
- self.yes_not_set(publ)):
983
+ self.yes_not_set(publ)):
979
984
  continue
980
985
  else:
981
986
  yield angle(label1=label1, label2=label2, label3=label3, angle_val=angle_val,
@@ -1049,7 +1054,7 @@ class CifContainer:
1049
1054
  hydr = namedtuple('HydrogenBond', ('label_d', 'label_h', 'label_a', 'dist_dh', 'dist_ha', 'dist_da',
1050
1055
  'angle_dha', 'symm'))
1051
1056
  for label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ in (
1052
- zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop, strict=False)):
1057
+ zip(label_d, label_h, label_a, dist_dh, dist_ha, dist_da, angle_dha, symm, publ_loop, strict=False)):
1053
1058
  if self.yes_not_set(publ):
1054
1059
  continue
1055
1060
  if self.picometer:
@@ -22,6 +22,7 @@ from finalcif.cif.text import utf8_to_str, quote, retranslate_delimiter, string_
22
22
  from finalcif.equip_property.tools import read_document_from_cif_file
23
23
  from finalcif.gui.dialogs import cif_file_save_dialog, show_general_warning, cif_file_open_dialog
24
24
  from finalcif.gui.finalcif_gui_ui import Ui_FinalCifWindow
25
+ from finalcif.gui.timers import single_shot
25
26
  from finalcif.tools.misc import grouper
26
27
  from finalcif.tools.settings import FinalCifSettings
27
28
 
@@ -452,7 +453,7 @@ class AuthorLoops:
452
453
  self._rename_old_name = item.text()
453
454
  # Defer editItem() to the next event-loop tick so that all
454
455
  # menu-close events are processed before Qt starts the editor.
455
- QtCore.QTimer.singleShot(0, lambda: listw.editItem(item))
456
+ single_shot(listw, 0, lambda: listw.editItem(item))
456
457
 
457
458
  menu.triggered.connect(_on_triggered)
458
459
  menu.popup(listw.viewport().mapToGlobal(pos))
@@ -14,6 +14,7 @@ from finalcif.cif.text import retranslate_delimiter, string_to_utf8
14
14
  from finalcif.equip_property.tools import read_document_from_cif_file
15
15
  from finalcif.gui.custom_classes import Column, light_green
16
16
  from finalcif.gui.dialogs import show_general_warning, cif_file_open_dialog, cif_file_save_dialog
17
+ from finalcif.gui.timers import single_shot
17
18
  from finalcif.tools import misc
18
19
  from finalcif.tools.misc import include_equipment_imports
19
20
  from finalcif.tools.settings import FinalCifSettings
@@ -94,7 +95,7 @@ class Equipment:
94
95
  self._rename_old_name = item.text()
95
96
  # Defer editItem() to the next event-loop tick so that all
96
97
  # menu-close events are processed before Qt starts the editor.
97
- QtCore.QTimer.singleShot(0, lambda: listw.editItem(item))
98
+ single_shot(listw, 0, lambda: listw.editItem(item))
98
99
 
99
100
  menu.triggered.connect(_on_triggered)
100
101
  menu.popup(listw.viewport().mapToGlobal(pos))
@@ -14,6 +14,7 @@ from finalcif.cif.text import retranslate_delimiter, utf8_to_str
14
14
  from finalcif.equip_property.tools import read_document_from_cif_file
15
15
  from finalcif.gui.dialogs import cif_file_open_dialog, show_general_warning, cif_file_save_dialog
16
16
  from finalcif.gui.plaintextedit import PlainTextEditTemplate
17
+ from finalcif.gui.timers import single_shot
17
18
  from finalcif.tools import misc
18
19
  from finalcif.tools.settings import FinalCifSettings
19
20
 
@@ -72,7 +73,7 @@ class Properties(QtCore.QObject):
72
73
  self.lb.setText(f'key {key} already exists')
73
74
  self.lb.move(self.app.ui.cifKeywordLineEdit.mapToGlobal(QtCore.QPoint(15, 25)))
74
75
  self.lb.show()
75
- QtCore.QTimer().singleShot(5000, self.hide_label)
76
+ single_shot(self.lb, 5000, self.hide_label)
76
77
  else:
77
78
  self.app.ui.SavePropertiesButton.setEnabled(True)
78
79
  self.lb.hide()
@@ -118,7 +119,7 @@ class Properties(QtCore.QObject):
118
119
  self._rename_old_name = item.text()
119
120
  # Defer editItem() to the next event-loop tick so that all
120
121
  # menu-close events are processed before Qt starts the editor.
121
- QtCore.QTimer.singleShot(0, lambda: listw.editItem(item))
122
+ single_shot(listw, 0, lambda: listw.editItem(item))
122
123
 
123
124
  menu.triggered.connect(_on_triggered)
124
125
  menu.popup(listw.viewport().mapToGlobal(pos))
@@ -17,6 +17,7 @@ from finalcif import VERSION
17
17
  from finalcif.app_path import application_path
18
18
  from finalcif.appwindow import AppWindow, DEBUG, app
19
19
  from finalcif.gui.dialogs import show_bug_found_warning, show_general_warning
20
+ from finalcif.tools.selfupdate import create_running_mutex
20
21
 
21
22
 
22
23
  def my_exception_hook(exctype: type[BaseException], value: BaseException, error_traceback: traceback,
@@ -73,6 +74,8 @@ def my_exception_hook(exctype: type[BaseException], value: BaseException, error_
73
74
  def main():
74
75
  if not DEBUG:
75
76
  sys.excepthook = my_exception_hook
77
+ # The installer (AppMutex) must not overwrite the files of a running FinalCif:
78
+ create_running_mutex()
76
79
  # windows_style = QStyleFactory.create('Fusion')
77
80
  # app.setStyle(windows_style)
78
81
  file = None
@@ -15,6 +15,7 @@ from finalcif.gui.cif_table_model import CifTableModel, Column, blue, CifRowData
15
15
  from finalcif.gui.combobox import MyComboBox
16
16
  from finalcif.gui.dialogs import show_keyword_help
17
17
  from finalcif.gui.plaintextedit import MyQPlainTextEdit
18
+ from finalcif.gui.timers import single_shot
18
19
 
19
20
  DEBUG = False
20
21
 
@@ -381,7 +382,7 @@ class CifTableView(QTableView):
381
382
  return QObject.eventFilter(self, widget, event)
382
383
 
383
384
  def resizeEvent(self, e: QtGui.QResizeEvent) -> None:
384
- QtCore.QTimer(self).singleShot(0, self.resizeRowsToContents)
385
+ single_shot(self, 0, self.resizeRowsToContents)
385
386
  super().resizeEvent(e)
386
387
 
387
388