MoleditPy 2.1.1__py3-none-any.whl → 2.2.0a0__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.1.1'
19
+ VERSION = '2.2.0a0'
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -600,6 +600,14 @@ class MainWindow(QMainWindow):
600
600
  # --- MOVED TO main_window_view_3d.py ---
601
601
  return self.main_window_view_3d.fit_to_view()
602
602
 
603
+ def draw_standard_3d_style(self, mol, style_override=None):
604
+ # --- MOVED TO main_window_view_3d.py ---
605
+ return self.main_window_view_3d.draw_standard_3d_style(mol, style_override)
606
+
607
+ def clear_measurement_selection(self):
608
+ # --- MOVED TO main_window_view_3d.py ---
609
+ return self.main_window_view_3d.clear_measurement_selection()
610
+
603
611
  def toggle_3d_edit_mode(self, checked):
604
612
  # --- MOVED TO main_window_ui_manager.py ---
605
613
  return self.main_window_ui_manager.toggle_3d_edit_mode(checked)
@@ -595,6 +595,20 @@ class MainWindowAppState(object):
595
595
  except Exception:
596
596
  json_data["last_successful_optimization_method"] = None
597
597
 
598
+ # Plugin State Persistence (Phase 3)
599
+ if self.plugin_manager and self.plugin_manager.save_handlers:
600
+ plugin_data = {}
601
+ for name, callback in self.plugin_manager.save_handlers.items():
602
+ try:
603
+ p_state = callback()
604
+ # Ensure serializable? Use primitive types ideally.
605
+ plugin_data[name] = p_state
606
+ except Exception as e:
607
+ print(f"Error saving state for plugin {name}: {e}")
608
+
609
+ if plugin_data:
610
+ json_data['plugins'] = plugin_data
611
+
598
612
  return json_data
599
613
 
600
614
 
@@ -614,6 +628,16 @@ class MainWindowAppState(object):
614
628
  except Exception:
615
629
  self.last_successful_optimization_method = None
616
630
 
631
+ # Plugin State Restoration (Phase 3)
632
+ if "plugins" in json_data and self.plugin_manager and self.plugin_manager.load_handlers:
633
+ plugin_data = json_data["plugins"]
634
+ for name, p_state in plugin_data.items():
635
+ if name in self.plugin_manager.load_handlers:
636
+ try:
637
+ self.plugin_manager.load_handlers[name](p_state)
638
+ except Exception as e:
639
+ print(f"Error loading state for plugin {name}: {e}")
640
+
617
641
 
618
642
  # 2D構造データの復元
619
643
  if "2d_structure" in json_data:
@@ -145,6 +145,11 @@ class MainWindowCompute(object):
145
145
  """右クリックで表示する一時的な3D変換メニュー。
146
146
  選択したモードは一時フラグとして保持され、その後の変換で使用されます(永続化しません)。
147
147
  """
148
+ # If button is disabled (during calculation), do not show menu
149
+ if not self.convert_button.isEnabled():
150
+ return
151
+
152
+
148
153
  try:
149
154
  menu = QMenu(self)
150
155
  conv_options = [
@@ -203,6 +208,16 @@ class MainWindowCompute(object):
203
208
  a.triggered.connect(lambda checked=False, k=key: self._trigger_optimize_with_temp_method(k))
204
209
  menu.addAction(a)
205
210
 
211
+ # Add Plugin Optimization Methods
212
+ if hasattr(self.mw, 'plugin_manager') and self.mw.plugin_manager.optimization_methods:
213
+ methods = self.mw.plugin_manager.optimization_methods
214
+ if methods:
215
+ menu.addSeparator()
216
+ for method_name, info in methods.items():
217
+ a = QAction(info.get('label', method_name), self)
218
+ a.triggered.connect(lambda checked=False, k=method_name: self._trigger_optimize_with_temp_method(k))
219
+ menu.addAction(a)
220
+
206
221
  menu.exec_(self.optimize_3d_button.mapToGlobal(pos))
207
222
  except Exception as e:
208
223
  print(f"Error showing optimize menu: {e}")
@@ -765,6 +780,18 @@ class MainWindowCompute(object):
765
780
  except Exception as e:
766
781
  self.statusBar().showMessage(f"UFF (RDKit) optimization error: {e}")
767
782
  return
783
+ # Plugin method dispatch
784
+ elif hasattr(self.mw, 'plugin_manager') and hasattr(self.mw.plugin_manager, 'optimization_methods') and method in self.mw.plugin_manager.optimization_methods:
785
+ info = self.mw.plugin_manager.optimization_methods[method]
786
+ callback = info['callback']
787
+ try:
788
+ success = callback(self.current_mol)
789
+ if not success:
790
+ self.statusBar().showMessage(f"Optimization method '{method}' returned failure.")
791
+ return
792
+ except Exception as e:
793
+ self.statusBar().showMessage(f"Plugin optimization error ({method}): {e}")
794
+ return
768
795
  else:
769
796
  self.statusBar().showMessage("Selected optimization method is not available. Use MMFF94 (RDKit) or UFF (RDKit).")
770
797
  return
@@ -328,7 +328,7 @@ class MainWindowMainInit(object):
328
328
  else:
329
329
  print(f"警告: アイコンファイルが見つかりません: {icon_path}")
330
330
 
331
- self.init_menu_bar()
331
+
332
332
 
333
333
  self.splitter = QSplitter(Qt.Orientation.Horizontal)
334
334
  # スプリッターハンドルを太くして視認性を向上
@@ -395,10 +395,11 @@ class MainWindowMainInit(object):
395
395
  self.plotter.setSizePolicy(
396
396
  QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
397
397
  )
398
+ self.plotter.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
399
+
398
400
  # 2. 垂直レイアウトに3Dビューを追加
399
401
  right_layout.addWidget(self.plotter, 1)
400
402
  #self.plotter.installEventFilter(self)
401
-
402
403
  # 3. ボタンをまとめるための「水平」レイアウトを作成
403
404
  right_buttons_layout = QHBoxLayout()
404
405
 
@@ -414,6 +415,7 @@ class MainWindowMainInit(object):
414
415
  self.optimize_3d_button.customContextMenuRequested.connect(self.show_optimize_menu)
415
416
  except Exception:
416
417
  pass
418
+ pass
417
419
  right_buttons_layout.addWidget(self.optimize_3d_button)
418
420
 
419
421
  # エクスポートボタン (メニュー付き)
@@ -467,6 +469,9 @@ class MainWindowMainInit(object):
467
469
  # Keep a reference to the main toolbar for later updates
468
470
  self.toolbar = toolbar
469
471
 
472
+ # Now that toolbar exists, initialize menu bar (which might add toolbar actions from plugins)
473
+ # self.init_menu_bar() - Moved down
474
+
470
475
  # Templates toolbar: place it directly below the main toolbar (second row at the top)
471
476
  # Use addToolBarBreak to ensure this toolbar appears on the next row under the main toolbar.
472
477
  # Some older PyQt/PySide versions may not have addToolBarBreak; fall back silently in that case.
@@ -480,6 +485,20 @@ class MainWindowMainInit(object):
480
485
  toolbar_bottom = QToolBar("Templates Toolbar")
481
486
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar_bottom)
482
487
  self.toolbar_bottom = toolbar_bottom
488
+
489
+ # Plugin Toolbar (Third Row)
490
+ try:
491
+ self.addToolBarBreak(Qt.ToolBarArea.TopToolBarArea)
492
+ except Exception:
493
+ pass
494
+
495
+ self.plugin_toolbar = QToolBar("Plugin Toolbar")
496
+ self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.plugin_toolbar)
497
+ self.plugin_toolbar.hide()
498
+
499
+ # Initialize menu bar (and populate toolbars) AFTER all toolbars are created
500
+ self.init_menu_bar()
501
+
483
502
  self.tool_group = QActionGroup(self)
484
503
  self.tool_group.setExclusive(True)
485
504
 
@@ -853,30 +872,31 @@ class MainWindowMainInit(object):
853
872
 
854
873
  file_menu.addSeparator()
855
874
 
875
+
856
876
  # === インポート ===
857
- import_menu = file_menu.addMenu("Import")
877
+ self.import_menu = file_menu.addMenu("Import")
858
878
 
859
879
  load_mol_action = QAction("MOL/SDF File...", self)
860
880
  load_mol_action.triggered.connect(self.load_mol_file)
861
- import_menu.addAction(load_mol_action)
881
+ self.import_menu.addAction(load_mol_action)
862
882
 
863
883
  import_smiles_action = QAction("SMILES...", self)
864
884
  import_smiles_action.triggered.connect(self.import_smiles_dialog)
865
- import_menu.addAction(import_smiles_action)
885
+ self.import_menu.addAction(import_smiles_action)
866
886
 
867
887
  import_inchi_action = QAction("InChI...", self)
868
888
  import_inchi_action.triggered.connect(self.import_inchi_dialog)
869
- import_menu.addAction(import_inchi_action)
889
+ self.import_menu.addAction(import_inchi_action)
870
890
 
871
- import_menu.addSeparator()
891
+ self.import_menu.addSeparator()
872
892
 
873
893
  load_3d_mol_action = QAction("3D MOL/SDF (3D View Only)...", self)
874
894
  load_3d_mol_action.triggered.connect(self.load_mol_file_for_3d_viewing)
875
- import_menu.addAction(load_3d_mol_action)
895
+ self.import_menu.addAction(load_3d_mol_action)
876
896
 
877
897
  load_3d_xyz_action = QAction("3D XYZ (3D View Only)...", self)
878
898
  load_3d_xyz_action.triggered.connect(self.load_xyz_for_3d_viewing)
879
- import_menu.addAction(load_3d_xyz_action)
899
+ self.import_menu.addAction(load_3d_xyz_action)
880
900
 
881
901
  # === エクスポート ===
882
902
  export_menu = file_menu.addMenu("Export")
@@ -1204,16 +1224,16 @@ class MainWindowMainInit(object):
1204
1224
  # Plugin menu
1205
1225
  plugin_menu = menu_bar.addMenu("&Plugin")
1206
1226
 
1207
- open_plugin_dir_action = QAction("Open Plugin Directory", self)
1208
- if self.plugin_manager:
1209
- open_plugin_dir_action.triggered.connect(self.plugin_manager.open_plugin_folder)
1210
- else:
1211
- open_plugin_dir_action.setEnabled(False)
1212
- plugin_menu.addAction(open_plugin_dir_action)
1213
-
1214
- reload_plugins_action = QAction("Reload Plugins", self)
1215
- reload_plugins_action.triggered.connect(lambda: self.update_plugin_menu(plugin_menu))
1216
- plugin_menu.addAction(reload_plugins_action)
1227
+ # Only keep the Manager action, moving others to the Manager Window
1228
+ manage_plugins_action = QAction("Plugin Manager...", self)
1229
+ def show_plugin_manager():
1230
+ from .plugin_manager_window import PluginManagerWindow
1231
+ dlg = PluginManagerWindow(self.plugin_manager, self)
1232
+ dlg.exec()
1233
+ self.update_plugin_menu(plugin_menu) # Refresh after closing
1234
+ manage_plugins_action.triggered.connect(show_plugin_manager)
1235
+ plugin_menu.addAction(manage_plugins_action)
1236
+
1217
1237
 
1218
1238
 
1219
1239
 
@@ -1386,6 +1406,8 @@ class MainWindowMainInit(object):
1386
1406
  )
1387
1407
  help_menu.addAction(manual_action)
1388
1408
 
1409
+
1410
+
1389
1411
  # 3D関連機能の初期状態を統一的に設定
1390
1412
  self._enable_3d_features(False)
1391
1413
 
@@ -1418,7 +1440,27 @@ class MainWindowMainInit(object):
1418
1440
  if not file_path or not os.path.exists(file_path):
1419
1441
  return
1420
1442
 
1421
- file_ext = file_path.lower().split('.')[-1]
1443
+ # Helper for extension
1444
+ _, ext_with_dot = os.path.splitext(file_path)
1445
+ ext_with_dot = ext_with_dot.lower()
1446
+ # Legacy variable name (no dot)
1447
+ file_ext = ext_with_dot.lstrip('.')
1448
+
1449
+ # 1. Custom Plugin Openers
1450
+ # 1. Custom Plugin Openers
1451
+ if ext_with_dot in self.plugin_manager.file_openers:
1452
+ opener = self.plugin_manager.file_openers[ext_with_dot]
1453
+ try:
1454
+ opener['callback'](file_path)
1455
+ self.current_file_path = file_path
1456
+ self.update_window_title()
1457
+ return
1458
+ except Exception as e:
1459
+ print(f"Plugin opener failed: {e}")
1460
+ QMessageBox.warning(self, "Plugin Error", f"Error opening file with plugin '{opener.get('plugin', 'Unknown')}':\n{e}")
1461
+ # Fallback to standard logic if plugin fails? Or stop?
1462
+ # Generally if a plugin claims it, we stop. But here we let it fall through if it errors?
1463
+ # Let's simple check next.
1422
1464
 
1423
1465
  if file_ext in ['mol', 'sdf']:
1424
1466
  self.load_mol_file_for_3d_viewing(file_path)
@@ -1717,26 +1759,88 @@ class MainWindowMainInit(object):
1717
1759
  # Clear existing plugin actions
1718
1760
  plugin_menu.clear()
1719
1761
 
1720
- # Re-add static actions
1721
- open_plugin_dir_action = QAction("Open Plugin Directory", self)
1722
- open_plugin_dir_action.triggered.connect(self.plugin_manager.open_plugin_folder)
1723
- plugin_menu.addAction(open_plugin_dir_action)
1724
-
1725
- reload_plugins_action = QAction("Reload Plugins", self)
1726
- reload_plugins_action.triggered.connect(lambda: self.update_plugin_menu(plugin_menu))
1727
- plugin_menu.addAction(reload_plugins_action)
1728
-
1729
- explore_plugins_action = QAction("Explore Plugins", self)
1730
- explore_plugins_action.triggered.connect(
1731
- lambda: QDesktopServices.openUrl(QUrl("https://hiroyokoyama.github.io/moleditpy-plugins/explorer/"))
1732
- )
1733
- plugin_menu.addAction(explore_plugins_action)
1762
+ # Only keep the Manager action
1763
+ manage_plugins_action = QAction("Plugin Manager...", self)
1764
+ def show_plugin_manager():
1765
+ from .plugin_manager_window import PluginManagerWindow
1766
+ dlg = PluginManagerWindow(self.plugin_manager, self)
1767
+ dlg.exec()
1768
+ self.update_plugin_menu(plugin_menu) # Refresh after closing
1769
+ manage_plugins_action.triggered.connect(show_plugin_manager)
1770
+ plugin_menu.addAction(manage_plugins_action)
1734
1771
 
1735
1772
  plugin_menu.addSeparator()
1736
1773
 
1737
- # Add dynamic plugin actions
1774
+ # Add dynamic plugin actions (Legacy + New Registration)
1738
1775
  plugins = self.plugin_manager.discover_plugins(self)
1739
1776
 
1777
+ # 1. Add Registered Menu Actions (New System)
1778
+ if self.plugin_manager.menu_actions:
1779
+ for action_def in self.plugin_manager.menu_actions:
1780
+ path = action_def['path']
1781
+ callback = action_def['callback']
1782
+ text = action_def['text']
1783
+ # Create/Find menu path
1784
+ current_menu = self.menuBar() # Or find specific top-level
1785
+
1786
+ # Handling top-level menus vs nested
1787
+ parts = path.split('/')
1788
+
1789
+ # If path starts with existing top-level (File, Edit, etc), grab it
1790
+ # Otherwise create new top-level
1791
+ top_level_title = parts[0]
1792
+ found_top = False
1793
+ for act in self.menuBar().actions():
1794
+ if act.menu() and act.text().replace('&', '') == top_level_title:
1795
+ current_menu = act.menu()
1796
+ found_top = True
1797
+ break
1798
+
1799
+ if not found_top:
1800
+ current_menu = self.menuBar().addMenu(top_level_title)
1801
+
1802
+ # Traverse rest
1803
+ for part in parts[1:]:
1804
+ found_sub = False
1805
+ for act in current_menu.actions():
1806
+ if act.menu() and act.text().replace('&', '') == part:
1807
+ current_menu = act.menu()
1808
+ found_sub = True
1809
+ break
1810
+ if not found_sub:
1811
+ current_menu = current_menu.addMenu(part)
1812
+
1813
+ # Add action
1814
+ action_text = text if text else parts[-1]
1815
+ action = QAction(action_text, self)
1816
+ action.triggered.connect(callback)
1817
+ current_menu.addAction(action)
1818
+
1819
+ # 2. Add Toolbar Buttons (New System)
1820
+ # 2. Add Toolbar Buttons (New System)
1821
+ # Use dedicated plugin toolbar
1822
+ if hasattr(self, 'plugin_toolbar'):
1823
+ self.plugin_toolbar.clear()
1824
+
1825
+ if self.plugin_manager.toolbar_actions:
1826
+ self.plugin_toolbar.show()
1827
+ for action_def in self.plugin_manager.toolbar_actions:
1828
+ text = action_def['text']
1829
+ callback = action_def['callback']
1830
+
1831
+ action = QAction(text, self)
1832
+ action.triggered.connect(callback)
1833
+ if action_def['icon']:
1834
+ if os.path.exists(action_def['icon']):
1835
+ action.setIcon(QIcon(action_def['icon']))
1836
+ if action_def['tooltip']:
1837
+ action.setToolTip(action_def['tooltip'])
1838
+ self.plugin_toolbar.addAction(action)
1839
+ else:
1840
+ self.plugin_toolbar.hide()
1841
+
1842
+ # 3. Legacy Menu Building (Folder based)
1843
+
1740
1844
  if not plugins:
1741
1845
  no_plugin_action = QAction("(No plugins found)", self)
1742
1846
  no_plugin_action.setEnabled(False)
@@ -1750,28 +1854,142 @@ class MainWindowMainInit(object):
1750
1854
  menus = { "": plugin_menu }
1751
1855
 
1752
1856
  for p in plugins:
1753
- rel_folder = p.get('rel_folder', "")
1857
+ # Only add legacy plugins (with 'run' function) to the generic Plugins menu.
1858
+ # New plugins (with 'initialize') should register their own menu actions if needed.
1859
+ if hasattr(p['module'], 'run'):
1860
+ rel_folder = p.get('rel_folder', '')
1861
+ # Get or create the parent menu for this plugin
1862
+ parent_menu = menus.get("") # Start at root
1863
+
1864
+ if rel_folder:
1865
+ # Split path and traverse/create submenus
1866
+ parts = rel_folder.split(os.sep)
1867
+ current_path = ""
1868
+ for part in parts:
1869
+ new_path = os.path.join(current_path, part) if current_path else part
1870
+
1871
+ if new_path not in menus:
1872
+ # Create new submenu
1873
+ sub_menu = parent_menu.addMenu(part)
1874
+ menus[new_path] = sub_menu
1875
+
1876
+ parent_menu = menus[new_path]
1877
+ current_path = new_path
1878
+
1879
+ # Add action to the resolved parent_menu
1880
+ action = QAction(p['name'], self)
1881
+ action.triggered.connect(lambda checked, mod=p['module']: self.plugin_manager.run_plugin(mod, self.mw if hasattr(self, 'mw') else self))
1882
+ parent_menu.addAction(action)
1883
+
1884
+ # 4. Integrate Export Actions into Export Button and Menu
1885
+ if self.plugin_manager.export_actions:
1886
+ # Add separator if we have custom exports (and haven't added it yet for this session/update)
1887
+ # Since update_plugin_menu clears the dynamic plugin actions but NOT the export menu (which is on a button),
1888
+ # we need to be careful not to duplicate.
1889
+ # Ideally, we should clear custom actions from the export menu first.
1890
+ # For simplicity, we'll just check existence.
1891
+
1892
+ if hasattr(self, 'export_button') and self.export_button.menu():
1893
+ # Naive approach: check if separator/actions exist.
1894
+ # Better approach: Add them to a specific section or manage them explicitly.
1895
+ # Here we just append if not present.
1754
1896
 
1897
+ # Check if we need a separator (if we have built-in actions)
1898
+ if self.export_button.menu().actions():
1899
+ has_sep = False
1900
+ for a in self.export_button.menu().actions():
1901
+ if a.isSeparator():
1902
+ has_sep = True
1755
1903
  # Get or create the parent menu for this plugin
1756
- parent_menu = menus.get("") # Start at root
1904
+ # For folder based hierarchy only?
1905
+ # ... (Refer to existing code for menu building, skipped here for brevity)
1906
+ pass
1907
+
1908
+ # 5. Integrate File Openers into Import Menu
1909
+ if hasattr(self, 'import_menu') and self.plugin_manager.file_openers:
1910
+ # Add a separator if plugins are present
1911
+ has_plugins = len(self.plugin_manager.file_openers) > 0
1912
+ if has_plugins:
1913
+ self.import_menu.addSeparator()
1914
+
1915
+ for ext, info in self.plugin_manager.file_openers.items():
1916
+ # ext e.g. .xyz
1917
+ # info = {'plugin': name, 'callback': cb}
1918
+ label = f"Import {ext} ({info.get('plugin', 'Plugin')})..."
1919
+
1920
+ # duplicate check
1921
+ exists = False
1922
+ for act in self.import_menu.actions():
1923
+ if act.text() == label:
1924
+ exists = True
1925
+ break
1926
+
1927
+ if not exists:
1928
+ def make_cb(callback):
1929
+ def _cb():
1930
+ # Standard file dialog to pick file, then callback
1931
+ fpath, _ = QFileDialog.getOpenFileName(
1932
+ self, f"Import {ext}", "",
1933
+ f"{info.get('plugin', 'Plugin')} File (*{ext});;All Files (*)"
1934
+ )
1935
+ if fpath:
1936
+ callback(fpath)
1937
+ self.current_file_path = fpath # Update current file path?
1938
+ self.update_window_title()
1939
+ return _cb
1940
+
1941
+ a = QAction(label, self)
1942
+ a.triggered.connect(make_cb(info['callback']))
1943
+ self.import_menu.addAction(a)
1944
+
1945
+ # 6. Integrate Analysis Tools into Analysis Menu
1946
+ if hasattr(self, 'analysis_action') and self.plugin_manager.analysis_tools:
1947
+ # Determine parent menu (Analysis)
1948
+ # self.analysis_action is just an action, we need the menu it belongs to?
1949
+ # Or did we stash the analysis_menu?
1950
+ # Looking at init, analysis_menu wasn't stored as self.analysis_menu, but we can find it via menuBar.
1951
+
1952
+ # Let's find "Analysis" menu
1953
+ analysis_menu = None
1954
+ for action in self.menuBar().actions():
1955
+ if action.text().replace('&', '') == 'Analysis':
1956
+ analysis_menu = action.menu()
1957
+ break
1958
+
1959
+ if analysis_menu:
1960
+ # Add separator if we have plugins
1961
+ if self.plugin_manager.analysis_tools:
1962
+ analysis_menu.addSeparator()
1963
+
1964
+ for tool in self.plugin_manager.analysis_tools:
1965
+ label = f"{tool['label']} ({tool.get('plugin', 'Plugin')})"
1966
+ # duplicate check
1967
+ exists = False
1968
+ for act in analysis_menu.actions():
1969
+ if act.text() == label:
1970
+ exists = True
1971
+ break
1972
+ if not exists:
1973
+ a = QAction(label, self)
1974
+ a.triggered.connect(tool['callback'])
1975
+ analysis_menu.addAction(a)
1976
+
1977
+ # 7. Integrate Export Actions (Continued)
1978
+ if self.plugin_manager.export_actions:
1979
+ for exp in self.plugin_manager.export_actions:
1980
+ label = exp['label']
1981
+ callback = exp['callback']
1982
+
1983
+ exists = False
1984
+ for act in self.export_button.menu().actions():
1985
+ if act.text() == label:
1986
+ exists = True
1987
+ break
1757
1988
 
1758
- if rel_folder:
1759
- # Split path and traverse/create submenus
1760
- parts = rel_folder.split(os.sep)
1761
- current_path = ""
1762
- for part in parts:
1763
- new_path = os.path.join(current_path, part) if current_path else part
1764
-
1765
- if new_path not in menus:
1766
- # Create new submenu
1767
- sub_menu = parent_menu.addMenu(part)
1768
- menus[new_path] = sub_menu
1769
-
1770
- parent_menu = menus[new_path]
1771
- current_path = new_path
1772
-
1773
- # Add action to the resolved parent_menu
1774
- action = QAction(p['name'], self)
1775
- action.triggered.connect(lambda checked, mod=p['module']: self.plugin_manager.run_plugin(mod, self.mw if hasattr(self, 'mw') else self))
1776
- parent_menu.addAction(action)
1989
+ if not exists:
1990
+ a = QAction(label, self)
1991
+ a.triggered.connect(callback)
1992
+ self.export_button.menu().addAction(a)
1993
+
1994
+ # 5. Integrate File Openers (Implicitly handled during file load) uses PluginManager directly
1777
1995
 
@@ -83,7 +83,7 @@ class MainWindowUiManager(object):
83
83
 
84
84
  def __init__(self, main_window):
85
85
  """ クラスの初期化 """
86
- self.mw = main_window
86
+ self = main_window
87
87
 
88
88
 
89
89
  def update_status_bar(self, message):
@@ -330,6 +330,17 @@ class MainWindowUiManager(object):
330
330
  continue
331
331
 
332
332
  if file_path:
333
+ # 1. Custom Plugin Handlers
334
+ if self.plugin_manager and hasattr(self.plugin_manager, 'drop_handlers'):
335
+ for handler_def in self.plugin_manager.drop_handlers:
336
+ try:
337
+ callback = handler_def['callback']
338
+ handled = callback(file_path)
339
+ if handled:
340
+ event.acceptProposedAction()
341
+ return
342
+ except Exception as e:
343
+ print(f"Error in plugin drop handler: {e}")
333
344
  # ドロップ位置を取得
334
345
  drop_pos = event.position().toPoint()
335
346
  # 拡張子に応じて適切な読み込みメソッドを呼び出す