MoleditPy-linux 2.3.1__tar.gz → 2.3.3__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 (64) hide show
  1. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/PKG-INFO +1 -1
  2. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/pyproject.toml +1 -1
  3. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/constants.py +1 -1
  5. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_edit_actions.py +4 -0
  6. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_main_init.py +43 -19
  7. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_ui_manager.py +2 -6
  8. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/plugin_interface.py +14 -12
  9. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/plugin_manager.py +31 -5
  10. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/LICENSE +0 -0
  11. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/README.md +0 -0
  12. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/setup.cfg +0 -0
  13. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  14. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  15. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  16. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  17. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  18. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/__init__.py +0 -0
  19. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/__main__.py +0 -0
  20. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/main.py +0 -0
  21. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/__init__.py +0 -0
  22. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/about_dialog.py +0 -0
  23. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/align_plane_dialog.py +0 -0
  24. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/alignment_dialog.py +0 -0
  25. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/analysis_window.py +0 -0
  26. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/angle_dialog.py +0 -0
  27. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/assets/file_icon.ico +0 -0
  28. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/assets/icon.icns +0 -0
  29. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/assets/icon.ico +0 -0
  30. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/assets/icon.png +0 -0
  31. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/atom_item.py +0 -0
  32. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/bond_item.py +0 -0
  33. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/bond_length_dialog.py +0 -0
  34. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/calculation_worker.py +0 -0
  35. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/color_settings_dialog.py +0 -0
  36. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/constrained_optimization_dialog.py +0 -0
  37. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/custom_interactor_style.py +0 -0
  38. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/custom_qt_interactor.py +0 -0
  39. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/dialog3_d_picking_mixin.py +0 -0
  40. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/dihedral_dialog.py +0 -0
  41. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window.py +0 -0
  42. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_app_state.py +0 -0
  43. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_compute.py +0 -0
  44. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_dialog_manager.py +0 -0
  45. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_edit_3d.py +0 -0
  46. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_export.py +0 -0
  47. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_molecular_parsers.py +0 -0
  48. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_project_io.py +0 -0
  49. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_string_importers.py +0 -0
  50. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_view_3d.py +0 -0
  51. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/main_window_view_loaders.py +0 -0
  52. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/mirror_dialog.py +0 -0
  53. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/molecular_data.py +0 -0
  54. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/molecule_scene.py +0 -0
  55. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/move_group_dialog.py +0 -0
  56. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/periodic_table_dialog.py +0 -0
  57. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/planarize_dialog.py +0 -0
  58. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/plugin_manager_window.py +0 -0
  59. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/settings_dialog.py +0 -0
  60. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/template_preview_item.py +0 -0
  61. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/template_preview_view.py +0 -0
  62. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/translation_dialog.py +0 -0
  63. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/user_template_dialog.py +0 -0
  64. {moleditpy_linux-2.3.1 → moleditpy_linux-2.3.3}/src/moleditpy_linux/modules/zoomable_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "2.3.1"
8
+ version = "2.3.3"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
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
@@ -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)."""
File without changes