MoleditPy 2.3.1__py3-none-any.whl → 2.3.3__py3-none-any.whl

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.
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QFont, QColor
16
16
  from rdkit import Chem
17
17
 
18
18
  #Version
19
- VERSION = '2.3.1'
19
+ VERSION = '2.3.3'
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -641,6 +641,10 @@ class MainWindowEditActions(object):
641
641
  # アプリケーションのイベントループを強制的に処理し、画面の再描画を確実に行う
642
642
  QApplication.processEvents()
643
643
 
644
+ # Call plugin document reset handlers
645
+ if hasattr(self, 'plugin_manager') and self.plugin_manager:
646
+ self.plugin_manager.invoke_document_reset_handlers()
647
+
644
648
  self.statusBar().showMessage("Cleared all data.")
645
649
 
646
650
 
@@ -1452,18 +1452,22 @@ class MainWindowMainInit(object):
1452
1452
  # 1. Custom Plugin Openers
1453
1453
  # 1. Custom Plugin Openers
1454
1454
  if ext_with_dot in self.plugin_manager.file_openers:
1455
- opener = self.plugin_manager.file_openers[ext_with_dot]
1456
- try:
1457
- opener['callback'](file_path)
1458
- self.current_file_path = file_path
1459
- self.update_window_title()
1460
- return
1461
- except Exception as e:
1462
- print(f"Plugin opener failed: {e}")
1463
- QMessageBox.warning(self, "Plugin Error", f"Error opening file with plugin '{opener.get('plugin', 'Unknown')}':\n{e}")
1464
- # Fallback to standard logic if plugin fails? Or stop?
1465
- # Generally if a plugin claims it, we stop. But here we let it fall through if it errors?
1466
- # Let's simple check next.
1455
+ openers = self.plugin_manager.file_openers[ext_with_dot]
1456
+ # Iterate through openers (already sorted by priority)
1457
+ for opener_info in openers:
1458
+ try:
1459
+ callback = opener_info['callback']
1460
+ # Try to call the opener
1461
+ callback(file_path)
1462
+
1463
+ self.current_file_path = file_path
1464
+ self.update_window_title()
1465
+ return # Success
1466
+ except Exception as e:
1467
+ print(f"Plugin opener failed for '{opener_info.get('plugin', 'Unknown')}': {e}")
1468
+ # If this opener fails, try the next one or fall through to default
1469
+ continue
1470
+
1467
1471
 
1468
1472
  if file_ext in ['mol', 'sdf']:
1469
1473
  self.load_mol_file_for_3d_viewing(file_path)
@@ -1847,6 +1851,11 @@ class MainWindowMainInit(object):
1847
1851
  action_text = text if text else parts[-1]
1848
1852
  action = QAction(action_text, self)
1849
1853
  action.triggered.connect(callback)
1854
+
1855
+ # Apply shortcut if provided
1856
+ if action_def.get('shortcut'):
1857
+ action.setShortcut(QKeySequence(action_def['shortcut']))
1858
+
1850
1859
  action.setData(PLUGIN_ACTION_TAG) # TAG THE ACTION
1851
1860
  current_menu.addAction(action)
1852
1861
 
@@ -1969,7 +1978,6 @@ class MainWindowMainInit(object):
1969
1978
  a.setData(PLUGIN_ACTION_TAG)
1970
1979
  menu.addAction(a)
1971
1980
 
1972
- # 5. Integrate File Openers into Import Menu
1973
1981
  # 5. Integrate File Openers into Import Menu
1974
1982
  if hasattr(self, 'import_menu') and self.plugin_manager.file_openers:
1975
1983
  # Add separator
@@ -1978,16 +1986,32 @@ class MainWindowMainInit(object):
1978
1986
 
1979
1987
  # Group by Plugin Name
1980
1988
  plugin_map = {}
1981
- for ext, info in self.plugin_manager.file_openers.items():
1982
- p_name = info.get('plugin', 'Plugin')
1983
- if p_name not in plugin_map:
1984
- plugin_map[p_name] = {}
1985
- plugin_map[p_name][ext] = info['callback']
1989
+ for ext, openers_list in self.plugin_manager.file_openers.items():
1990
+ # Handles potential multiple openers for same extension
1991
+ for info in openers_list:
1992
+ p_name = info.get('plugin', 'Plugin')
1993
+ if p_name not in plugin_map:
1994
+ plugin_map[p_name] = {}
1995
+ # We can only register one callback per plugin per extension in the menu for now.
1996
+ # Since we process them, let's just take the one present (if a plugin registers multiple openers for same ext - weird but ok)
1997
+ plugin_map[p_name][ext] = info['callback']
1986
1998
 
1987
- for p_name, ext_map in plugin_map.items():
1999
+ for p_name, ext_map in sorted(plugin_map.items()):
1988
2000
  # Create combined label: "Import .ext1/.ext2 (PluginName)..."
1989
2001
  extensions = sorted(ext_map.keys())
1990
2002
  ext_str = "/".join(extensions)
2003
+
2004
+ # TRUNCATION LOGIC
2005
+ MAX_EXT_LEN = 30
2006
+ if len(ext_str) > MAX_EXT_LEN:
2007
+ # Find last slash within limit
2008
+ cutoff = ext_str.rfind('/', 0, MAX_EXT_LEN)
2009
+ if cutoff != -1:
2010
+ ext_str = ext_str[:cutoff] + "/..."
2011
+ else:
2012
+ # Fallback if first extension is super long (unlikely but safe)
2013
+ ext_str = ext_str[:MAX_EXT_LEN] + "..."
2014
+
1991
2015
  label = f"Import {ext_str} ({p_name})..."
1992
2016
 
1993
2017
  # Create combined filter: "PluginName Files (*.ext1 *.ext2)"
@@ -313,12 +313,8 @@ class MainWindowUiManager(object):
313
313
  event.acceptProposedAction()
314
314
  return
315
315
 
316
- # Plugin-registered file openers
317
- if self.plugin_manager and hasattr(self.plugin_manager, 'file_openers'):
318
- for ext in self.plugin_manager.file_openers.keys():
319
- if file_lower.endswith(ext):
320
- event.acceptProposedAction()
321
- return
316
+ # 2. Plugin drop handlers (Drop専用ハンドラ)
317
+ # プラグインが「Dropを受け入れる」と明示している場合のみ許可
322
318
 
323
319
  # Plugin drop handlers (accept more liberally for custom logic)
324
320
  # A plugin drop handler might handle it, so accept
@@ -104,7 +104,7 @@ class PluginContext:
104
104
  """
105
105
  self._manager.register_optimization_method(self._plugin_name, method_name, callback)
106
106
 
107
- def register_file_opener(self, extension: str, callback: Callable[[str], None]):
107
+ def register_file_opener(self, extension: str, callback: Callable[[str], None], priority: int = 0):
108
108
  """
109
109
  Register a handler for opening a specific file extension.
110
110
 
@@ -112,19 +112,11 @@ class PluginContext:
112
112
  extension: File extension including dot, e.g. ".xyz".
113
113
  callback: Function taking (file_path) -> None.
114
114
  Should load the file into the main window.
115
+ priority: Higher priority handlers are tried first (default 0).
115
116
  """
116
- self._manager.register_file_opener(self._plugin_name, extension, callback)
117
+ self._manager.register_file_opener(self._plugin_name, extension, callback, priority)
118
+
117
119
 
118
- def register_file_opener(self, extension: str, callback: Callable[[str], None]):
119
- """
120
- Register a handler for opening a specific file extension.
121
-
122
- Args:
123
- extension: File extension including dot, e.g. ".xyz".
124
- callback: Function taking (file_path) -> None.
125
- Should load the file into the main window.
126
- """
127
- self._manager.register_file_opener(self._plugin_name, extension, callback)
128
120
 
129
121
  def add_analysis_tool(self, label: str, callback: Callable):
130
122
  """
@@ -176,6 +168,16 @@ class PluginContext:
176
168
  """
177
169
  self._manager.register_3d_style(self._plugin_name, style_name, callback)
178
170
 
171
+ def register_document_reset_handler(self, callback: Callable[[], None]):
172
+ """
173
+ Register a callback to be called when a new document is created (File→New).
174
+
175
+ Args:
176
+ callback: Function with no arguments that resets plugin state.
177
+ """
178
+ self._manager.register_document_reset_handler(self._plugin_name, callback)
179
+
180
+
179
181
 
180
182
 
181
183
 
@@ -46,11 +46,12 @@ class PluginManager:
46
46
  # Extended Registries (Added to prevent lazy initialization "monkey patching")
47
47
  self.export_actions = []
48
48
  self.optimization_methods = {}
49
- self.file_openers = {}
49
+ self.file_openers = {} # ext -> list of {'plugin':..., 'callback':..., 'priority':...}
50
50
  self.analysis_tools = []
51
51
  self.save_handlers = {}
52
52
  self.load_handlers = {}
53
53
  self.custom_3d_styles = {} # style_name -> {'plugin': name, 'callback': func}
54
+ self.document_reset_handlers = [] # List of callbacks to call on new document
54
55
 
55
56
  def get_main_window(self):
56
57
  return self.main_window
@@ -182,6 +183,7 @@ class PluginManager:
182
183
  self.save_handlers = {}
183
184
  self.load_handlers = {}
184
185
  self.custom_3d_styles = {}
186
+ self.document_reset_handlers = []
185
187
 
186
188
  if not os.path.exists(self.plugin_dir):
187
189
  return []
@@ -345,14 +347,23 @@ class PluginManager:
345
347
  'plugin': plugin_name, 'callback': callback, 'label': method_name
346
348
  }
347
349
 
348
- def register_file_opener(self, plugin_name, extension, callback):
350
+ def register_file_opener(self, plugin_name, extension, callback, priority=0):
349
351
  # Normalize extension to lowercase
350
352
  ext = extension.lower()
351
353
  if not ext.startswith('.'):
352
354
  ext = '.' + ext
353
- self.file_openers[ext] = {
354
- 'plugin': plugin_name, 'callback': callback
355
- }
355
+
356
+ if ext not in self.file_openers:
357
+ self.file_openers[ext] = []
358
+
359
+ self.file_openers[ext].append({
360
+ 'plugin': plugin_name,
361
+ 'callback': callback,
362
+ 'priority': priority
363
+ })
364
+
365
+ # Sort by priority descending
366
+ self.file_openers[ext].sort(key=lambda x: x['priority'], reverse=True)
356
367
 
357
368
  # Analysis Tools registration
358
369
  def register_analysis_tool(self, plugin_name, label, callback):
@@ -369,6 +380,21 @@ class PluginManager:
369
380
  self.custom_3d_styles[style_name] = {
370
381
  'plugin': plugin_name, 'callback': callback
371
382
  }
383
+
384
+ def register_document_reset_handler(self, plugin_name, callback):
385
+ """Register callback to be invoked when a new document is created."""
386
+ self.document_reset_handlers.append({
387
+ 'plugin': plugin_name,
388
+ 'callback': callback
389
+ })
390
+
391
+ def invoke_document_reset_handlers(self):
392
+ """Call all registered document reset handlers."""
393
+ for handler in self.document_reset_handlers:
394
+ try:
395
+ handler['callback']()
396
+ except Exception as e:
397
+ print(f"Error in document reset handler for {handler['plugin']}: {e}")
372
398
 
373
399
  def get_plugin_info_safe(self, file_path):
374
400
  """Extracts plugin metadata using AST parsing (safe, no execution)."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 2.3.1
3
+ Version: 2.3.3
4
4
  Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -12,7 +12,7 @@ moleditpy/modules/bond_item.py,sha256=eVkEeKvM4igYI67DYxpey3FllqDyt_iWDo4VPYMhaP
12
12
  moleditpy/modules/bond_length_dialog.py,sha256=6bFPGssnqlgINuqpxLv-OhjMH3_hspnaH8QtorAyu2M,14782
13
13
  moleditpy/modules/calculation_worker.py,sha256=KiGQY7i-QCQofEoE0r65KoQgpEGFcbhmxWv6egfkUdc,42324
14
14
  moleditpy/modules/color_settings_dialog.py,sha256=Ow44BhCOLo0AFb6klO001k6B4drOgKX9DeNBQhZLp5o,15474
15
- moleditpy/modules/constants.py,sha256=oCixX94SBFCkjSZCVrrfBfieq1LgawOd8UMH_pPyKSY,4702
15
+ moleditpy/modules/constants.py,sha256=ehyCNblUR4uHs1xVpBAzY2-tQuuF05_6vGY7OYFTfbE,4702
16
16
  moleditpy/modules/constrained_optimization_dialog.py,sha256=REsk4ePsqNmAGPMTS_jckeM7jexrU3krwun8sKqKUCs,30062
17
17
  moleditpy/modules/custom_interactor_style.py,sha256=LDNODMJoNHGe1AUSrvqv6PdeJm-hpPmSpWINppnJLt0,38942
18
18
  moleditpy/modules/custom_qt_interactor.py,sha256=vCZsDfRO-FtphD5cTP7Ps-5rpHZMIGloaoe6EaKzrsw,4139
@@ -23,13 +23,13 @@ moleditpy/modules/main_window_app_state.py,sha256=8YDcGNCSpLTO1NGL9tEvNkXpUcS7JW
23
23
  moleditpy/modules/main_window_compute.py,sha256=ipIkhH_DONXDnPzh7xeym9X-Yfx8EhsvXYOdyxsAj4c,53347
24
24
  moleditpy/modules/main_window_dialog_manager.py,sha256=QR96LqHAPSOShXbc9cK-Ffq8a16JrXAoMKB0pHjESrQ,20072
25
25
  moleditpy/modules/main_window_edit_3d.py,sha256=CUArB5wcsgq1C7LygAEC6URlbnn4RhRYDa5n-Y-etWI,19731
26
- moleditpy/modules/main_window_edit_actions.py,sha256=yEc0Nw-VpN0P4e4neUu7pDuUHPGEcu6eFmwWFrSBIQ8,64815
26
+ moleditpy/modules/main_window_edit_actions.py,sha256=MtCFmbKVAIMrm6hp0rUUNCQ37xYzE8MerVR7d1Coutk,65007
27
27
  moleditpy/modules/main_window_export.py,sha256=dSVfylsybDDboDuXU9Inotf6YkrKJwgBTqGYSfq1lRE,38241
28
- moleditpy/modules/main_window_main_init.py,sha256=BChZuyXgWfOTFqAM1OUukFgtL5KLusW4t29GnfCN1QE,92018
28
+ moleditpy/modules/main_window_main_init.py,sha256=stZT2Swcd_yry3C_a7R2bC9-kew8yifRqy8Q3H3a3kk,93220
29
29
  moleditpy/modules/main_window_molecular_parsers.py,sha256=KR6vzuqc3nutOcorpYr0QOyX3MFBcxTwDhZX96VgJ9Q,48291
30
30
  moleditpy/modules/main_window_project_io.py,sha256=TWwtuKDuvgcvPZ9IGmW8r1EJJOrgxrIJRnxe_f4C1oM,17149
31
31
  moleditpy/modules/main_window_string_importers.py,sha256=v47wOd4RtjKYcF-aLP-mogGGdYTpTEo3dDyAu79_5MM,10782
32
- moleditpy/modules/main_window_ui_manager.py,sha256=HofI6T9EvcSSzPbsdPqkYEEDoB6Hui1Uj2Ll-wwczGA,24016
32
+ moleditpy/modules/main_window_ui_manager.py,sha256=A-MBoZOq5xHD_Dg9F2BSbBgeWZBY9wDq_7zHacJjKRA,23796
33
33
  moleditpy/modules/main_window_view_3d.py,sha256=CxZxyJHl2isF7KtyVWSI9f8LVbvdZM5H9Gnhm_8ovBM,74227
34
34
  moleditpy/modules/main_window_view_loaders.py,sha256=gklTMo27QnyJ8Gd0ampPdbm9d0Gi-oHWkIqQuGADHmI,14352
35
35
  moleditpy/modules/mirror_dialog.py,sha256=c3v4qY6R4FAljzk4EPaDjL9ZdZMjLQSFLqDMXz2fBUk,4696
@@ -38,8 +38,8 @@ moleditpy/modules/molecule_scene.py,sha256=khdt7h9Mk_D1cMbYeHGtq7P9aFXo0xG-hcShU
38
38
  moleditpy/modules/move_group_dialog.py,sha256=Fyuy3Uq1KsFsk9qR96r_FxPbAM_-zSfW2dsMQGv7btc,27276
39
39
  moleditpy/modules/periodic_table_dialog.py,sha256=ItEZUts1XCietz9paY-spvbzxh6SXak3GnikwqkHZCw,4006
40
40
  moleditpy/modules/planarize_dialog.py,sha256=eaqI1MpF35e-VUMpJATt-EtGG5FhcSUlbAenUaFGabY,8593
41
- moleditpy/modules/plugin_interface.py,sha256=srzPZ3a_aRTx28NAvWNKRVUDYQNfQOTcjzx-5YW2Pb4,8164
42
- moleditpy/modules/plugin_manager.py,sha256=J-BeUGvOFa4v0djYln_Olurr6JVWDAETcYMwOlZ0VkI,19998
41
+ moleditpy/modules/plugin_interface.py,sha256=JfobFdAOLp5OnyJp3BNsWnpmZNJYypWQHNVNT0fP1N8,8171
42
+ moleditpy/modules/plugin_manager.py,sha256=ZFQz8VlCy1_IHX7DnMro7iRbouB_rxZQowfrKuZ76G8,21133
43
43
  moleditpy/modules/plugin_manager_window.py,sha256=b4kEv0DaWHZG76ZaFTOxn6CVtA62_0MpPYYr10ehCtA,12544
44
44
  moleditpy/modules/settings_dialog.py,sha256=Nr7yE8UmYRi3VObWvRlrnv0DnjSjmYXbvqryZ02O12k,65348
45
45
  moleditpy/modules/template_preview_item.py,sha256=djdq3tz73d_fJGOvai3E-V9Hk9q9ZW7skx7BV59mooA,6556
@@ -51,9 +51,9 @@ moleditpy/modules/assets/file_icon.ico,sha256=yyVj084A7HuMNbV073cE_Ag3Ne405qgOP3
51
51
  moleditpy/modules/assets/icon.icns,sha256=wD5R6-Vw7K662tVKhu2E1ImN0oUuyAP4youesEQsn9c,139863
52
52
  moleditpy/modules/assets/icon.ico,sha256=RfgFcx7-dHY_2STdsOQCQziY5SNhDr3gPnjO6jzEDPI,147975
53
53
  moleditpy/modules/assets/icon.png,sha256=kCFN1WacYIdy0GN6SFEbNA00ef39pCczBnFdkkBI8Bs,147110
54
- moleditpy-2.3.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
- moleditpy-2.3.1.dist-info/METADATA,sha256=7uof_qhynKs1RVqwxoQjfE6oOUH-zs53C2Plo15-hZk,60629
56
- moleditpy-2.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- moleditpy-2.3.1.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
- moleditpy-2.3.1.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
- moleditpy-2.3.1.dist-info/RECORD,,
54
+ moleditpy-2.3.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
+ moleditpy-2.3.3.dist-info/METADATA,sha256=VbBIVoZs0NPTK88hUAwg8s9MVwt6aVqsIqs1NYcM0po,60629
56
+ moleditpy-2.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ moleditpy-2.3.3.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
+ moleditpy-2.3.3.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
+ moleditpy-2.3.3.dist-info/RECORD,,