MoleditPy-linux 3.5.2__tar.gz → 3.6.0__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 (80) hide show
  1. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/PKG-INFO +3 -3
  2. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/README.md +2 -2
  3. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/pyproject.toml +1 -1
  4. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/PKG-INFO +3 -3
  5. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
  6. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/main.py +10 -0
  7. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/custom_interactor_style.py +55 -14
  8. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/dialog_logic.py +61 -13
  9. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/main_window_init.py +8 -0
  10. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/molecular_scene_handler.py +108 -24
  11. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/molecule_scene.py +4 -4
  12. moleditpy_linux-3.6.0/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +640 -0
  13. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +3 -1
  14. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +18 -4
  15. moleditpy_linux-3.6.0/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +108 -0
  16. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/ui_manager.py +1 -0
  17. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/utils/default_settings.py +1 -1
  18. moleditpy_linux-3.5.2/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -70
  19. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/LICENSE +0 -0
  20. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/setup.cfg +0 -0
  21. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  22. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  23. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  24. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  25. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/__init__.py +0 -0
  26. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/__main__.py +0 -0
  27. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  28. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/assets/icon.icns +0 -0
  29. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/assets/icon.ico +0 -0
  30. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/assets/icon.png +0 -0
  31. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/core/__init__.py +0 -0
  32. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/core/mol_geometry.py +0 -0
  33. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/core/molecular_data.py +0 -0
  34. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/plugins/__init__.py +0 -0
  35. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
  36. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
  37. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
  38. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/__init__.py +0 -0
  39. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  40. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  41. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  42. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/analysis_window.py +0 -0
  43. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  44. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/app_state.py +0 -0
  45. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/atom_item.py +0 -0
  46. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  47. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
  48. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/bond_item.py +0 -0
  49. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  50. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
  51. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  52. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/compute_logic.py +0 -0
  53. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
  54. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
  55. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
  56. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  57. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  58. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
  59. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/export_logic.py +0 -0
  60. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  61. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/io_logic.py +0 -0
  62. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/main_window.py +0 -0
  63. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  64. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
  65. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  66. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
  67. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  68. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  69. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  70. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/string_importers.py +0 -0
  71. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
  72. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  73. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
  74. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  75. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
  76. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  77. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/utils/__init__.py +0 -0
  78. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/utils/constants.py +0 -0
  79. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  80. {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.0}/src/moleditpy_linux/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 3.5.2
3
+ Version: 3.6.0
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
@@ -804,7 +804,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
804
804
  After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
805
805
 
806
806
  ```bash
807
- moleditpy-installer
807
+ python -m moleditpy_installer
808
808
  ```
809
809
 
810
810
  #### Running the Application
@@ -937,7 +937,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
937
937
  インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
938
938
 
939
939
  ```bash
940
- moleditpy-installer
940
+ python -m moleditpy_installer
941
941
  ```
942
942
 
943
943
  #### アプリケーションの起動
@@ -101,7 +101,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
101
101
  After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
102
102
 
103
103
  ```bash
104
- moleditpy-installer
104
+ python -m moleditpy_installer
105
105
  ```
106
106
 
107
107
  #### Running the Application
@@ -234,7 +234,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
234
234
  インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
235
235
 
236
236
  ```bash
237
- moleditpy-installer
237
+ python -m moleditpy_installer
238
238
  ```
239
239
 
240
240
  #### アプリケーションの起動
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "3.5.2"
8
+ version = "3.6.0"
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: 3.5.2
3
+ Version: 3.6.0
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
@@ -804,7 +804,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
804
804
  After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
805
805
 
806
806
  ```bash
807
- moleditpy-installer
807
+ python -m moleditpy_installer
808
808
  ```
809
809
 
810
810
  #### Running the Application
@@ -937,7 +937,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
937
937
  インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
938
938
 
939
939
  ```bash
940
- moleditpy-installer
940
+ python -m moleditpy_installer
941
941
  ```
942
942
 
943
943
  #### アプリケーションの起動
@@ -53,6 +53,7 @@ src/moleditpy_linux/ui/mirror_dialog.py
53
53
  src/moleditpy_linux/ui/molecular_scene_handler.py
54
54
  src/moleditpy_linux/ui/molecule_scene.py
55
55
  src/moleditpy_linux/ui/move_group_dialog.py
56
+ src/moleditpy_linux/ui/move_selected_atoms_dialog.py
56
57
  src/moleditpy_linux/ui/periodic_table_dialog.py
57
58
  src/moleditpy_linux/ui/planarize_dialog.py
58
59
  src/moleditpy_linux/ui/settings_dialog.py
@@ -148,4 +148,14 @@ def main() -> None:
148
148
  app = QApplication([sys.argv[0]] + remaining)
149
149
  window = MainWindow(initial_file=args.file, safe_mode=args.safe)
150
150
  window.show()
151
+
152
+ # Force Windows to refresh taskbar/titlebar icon after event loop starts
153
+ if sys.platform == "win32":
154
+ try:
155
+ from PyQt6.QtCore import QTimer
156
+
157
+ QTimer.singleShot(100, lambda: window.setWindowIcon(window.windowIcon()))
158
+ except Exception:
159
+ pass
160
+
151
161
  sys.exit(app.exec())
@@ -22,10 +22,8 @@ from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera #
22
22
 
23
23
 
24
24
  try:
25
- from .move_group_dialog import MoveGroupDialog
26
25
  from .atom_picking import pick_atom_index_from_screen
27
26
  except ImportError:
28
- from moleditpy_linux.ui.move_group_dialog import MoveGroupDialog
29
27
  from moleditpy_linux.ui.atom_picking import pick_atom_index_from_screen
30
28
 
31
29
  from rdkit import Geometry
@@ -107,10 +105,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
107
105
  self._mouse_press_pos = None
108
106
 
109
107
  # Check Move Group dialog
108
+ # Check Move Group or Move Selected Atoms dialog
110
109
  move_group_dialog = None
111
110
  for widget in QApplication.topLevelWidgets():
112
111
  try:
113
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
112
+ if (
113
+ type(widget).__name__
114
+ in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
115
+ and widget.isVisible()
116
+ ):
114
117
  move_group_dialog = widget
115
118
  break
116
119
  except (AttributeError, RuntimeError, TypeError):
@@ -147,6 +150,20 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
147
150
  self._suppress_next_left_button_up = True
148
151
  return # Disable camera rotation
149
152
  else:
153
+ if type(move_group_dialog).__name__ == "MoveSelectedAtomsDialog":
154
+ # For MoveSelectedAtomsDialog, we toggle ONLY the clicked atom, no BFS!
155
+ def _deferred_toggle(
156
+ idx=clicked_atom_idx, dlg=move_group_dialog
157
+ ):
158
+ try:
159
+ dlg.on_atom_picked(idx)
160
+ except (AttributeError, RuntimeError):
161
+ pass
162
+
163
+ QTimer.singleShot(0, _deferred_toggle)
164
+ self._suppress_next_left_button_up = True
165
+ return
166
+
150
167
  # Clicked outside group - Search connected component
151
168
  visited = set()
152
169
  queue = [clicked_atom_idx]
@@ -172,8 +189,14 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
172
189
 
173
190
  # Multi-selection with Ctrl
174
191
  is_ctrl_pressed = bool(
175
- QApplication.keyboardModifiers()
176
- & Qt.KeyboardModifier.ControlModifier
192
+ (
193
+ QApplication.keyboardModifiers()
194
+ & Qt.KeyboardModifier.ControlModifier
195
+ )
196
+ or (
197
+ self.GetInteractor()
198
+ and self.GetInteractor().GetControlKey()
199
+ )
177
200
  )
178
201
 
179
202
  if is_ctrl_pressed:
@@ -208,8 +231,10 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
208
231
  super(CustomInteractorStyle, self).OnLeftButtonDown()
209
232
  return
210
233
 
234
+ interactor = self.GetInteractor()
211
235
  is_temp_mode = bool(
212
- QApplication.keyboardModifiers() & Qt.KeyboardModifier.AltModifier
236
+ (QApplication.keyboardModifiers() & Qt.KeyboardModifier.AltModifier)
237
+ or (interactor and interactor.GetAltKey())
213
238
  )
214
239
  is_edit_active = mw.edit_3d_manager.is_3d_edit_mode or is_temp_mode
215
240
 
@@ -293,11 +318,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
293
318
  """
294
319
  mw = self.main_window
295
320
 
296
- # Check if Move Group dialog is open
321
+ # Check if Move Group dialog or Move Selected Atoms dialog is open
297
322
  move_group_dialog = None
298
323
  try:
299
324
  for widget in QApplication.topLevelWidgets():
300
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
325
+ if (
326
+ type(widget).__name__
327
+ in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
328
+ and widget.isVisible()
329
+ ):
301
330
  move_group_dialog = widget
302
331
  break
303
332
  except (AttributeError, RuntimeError, TypeError) as e:
@@ -350,11 +379,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
350
379
  """
351
380
  mw = self.main_window
352
381
 
353
- # Move Group drag handling
382
+ # Move Group / Selected Atoms drag handling
354
383
  move_group_dialog = None
355
384
  try:
356
385
  for widget in QApplication.topLevelWidgets():
357
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
386
+ if (
387
+ type(widget).__name__
388
+ in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
389
+ and widget.isVisible()
390
+ ):
358
391
  move_group_dialog = widget
359
392
  break
360
393
  except (AttributeError, RuntimeError, TypeError):
@@ -442,11 +475,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
442
475
  """
443
476
  mw = self.main_window
444
477
 
445
- # Finalize Move Group drag
478
+ # Finalize Move Group / Selected Atoms drag
446
479
  move_group_dialog = None
447
480
  try:
448
481
  for widget in QApplication.topLevelWidgets():
449
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
482
+ if (
483
+ type(widget).__name__
484
+ in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
485
+ and widget.isVisible()
486
+ ):
450
487
  move_group_dialog = widget
451
488
  break
452
489
  except (AttributeError, RuntimeError, TypeError):
@@ -749,11 +786,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
749
786
  """
750
787
  mw = self.main_window
751
788
 
752
- # Finalize Move Group rotation
789
+ # Finalize Move Group / Selected Atoms rotation
753
790
  move_group_dialog = None
754
791
  try:
755
792
  for widget in QApplication.topLevelWidgets():
756
- if isinstance(widget, MoveGroupDialog) and widget.isVisible():
793
+ if (
794
+ type(widget).__name__
795
+ in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
796
+ and widget.isVisible()
797
+ ):
757
798
  move_group_dialog = widget
758
799
  break
759
800
  except (AttributeError, RuntimeError, TypeError):
@@ -18,7 +18,8 @@ import json
18
18
  import os
19
19
  from typing import Any, List, Literal, Optional, cast
20
20
 
21
- from PyQt6.QtWidgets import QInputDialog, QMessageBox
21
+ from PyQt6.QtWidgets import QInputDialog, QMessageBox, QDialog
22
+ from PyQt6.QtCore import Qt
22
23
 
23
24
 
24
25
  try:
@@ -33,6 +34,7 @@ try:
33
34
  from .dihedral_dialog import DihedralDialog
34
35
  from .mirror_dialog import MirrorDialog
35
36
  from .move_group_dialog import MoveGroupDialog
37
+ from .move_selected_atoms_dialog import MoveSelectedAtomsDialog
36
38
  from .periodic_table_dialog import PeriodicTableDialog
37
39
  from .planarize_dialog import PlanarizeDialog
38
40
  from .settings_dialog import SettingsDialog
@@ -53,6 +55,7 @@ except ImportError:
53
55
  from moleditpy_linux.ui.dihedral_dialog import DihedralDialog
54
56
  from moleditpy_linux.ui.mirror_dialog import MirrorDialog
55
57
  from moleditpy_linux.ui.move_group_dialog import MoveGroupDialog
58
+ from moleditpy_linux.ui.move_selected_atoms_dialog import MoveSelectedAtomsDialog
56
59
  from moleditpy_linux.ui.periodic_table_dialog import PeriodicTableDialog
57
60
  from moleditpy_linux.ui.planarize_dialog import PlanarizeDialog
58
61
  from moleditpy_linux.ui.settings_dialog import SettingsDialog
@@ -213,6 +216,13 @@ class DialogManager:
213
216
  self.host, "Error", f"Failed to save template: {str(e)}"
214
217
  )
215
218
 
219
+ def _show_modeless_dialog(self, dialog: QDialog) -> None:
220
+ """Show a modeless dialog on top, especially important for macOS."""
221
+ dialog.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
222
+ dialog.show()
223
+ dialog.raise_()
224
+ dialog.activateWindow()
225
+
216
226
  def open_translation_dialog(self) -> None:
217
227
  """Open the translation dialog"""
218
228
  # Get preselected atoms
@@ -229,8 +239,8 @@ class DialogManager:
229
239
  preselected_atoms,
230
240
  parent=self.host,
231
241
  )
232
- self.host.edit_3d_manager.active_3d_dialogs.append(dialog) # Keep reference
233
- dialog.show() # Use show for modeless display
242
+ self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
243
+ self._show_modeless_dialog(dialog)
234
244
  dialog.accepted.connect(
235
245
  lambda: self.host.statusBar().showMessage("Translation applied.")
236
246
  )
@@ -256,7 +266,7 @@ class DialogManager:
256
266
  parent=self.host,
257
267
  )
258
268
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
259
- dialog.show()
269
+ self._show_modeless_dialog(dialog)
260
270
  dialog.accepted.connect(
261
271
  lambda: self.host.statusBar().showMessage("Group transformation applied.")
262
272
  )
@@ -265,6 +275,34 @@ class DialogManager:
265
275
  lambda: self.host.edit_3d_manager.remove_dialog_from_list(dialog)
266
276
  )
267
277
 
278
+ def open_move_selected_atoms_dialog(self) -> None:
279
+ """Open Move Selected Atoms dialog"""
280
+ # Get preselected atoms
281
+ preselected_atoms = self._get_preselected_atoms_3d()
282
+
283
+ # Disable measurement mode
284
+ if self.host.edit_3d_manager.measurement_mode:
285
+ self.host.init_manager.measurement_action.setChecked(False)
286
+ self.host.edit_3d_manager.toggle_measurement_mode(False)
287
+
288
+ dialog = MoveSelectedAtomsDialog(
289
+ self.host.view_3d_manager.current_mol,
290
+ self.host,
291
+ preselected_atoms,
292
+ parent=self.host,
293
+ )
294
+ self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
295
+ self._show_modeless_dialog(dialog)
296
+ dialog.accepted.connect(
297
+ lambda: self.host.statusBar().showMessage(
298
+ "Selected atoms transformation applied."
299
+ )
300
+ )
301
+ dialog.accepted.connect(self.host.edit_actions_manager.push_undo_state)
302
+ dialog.finished.connect(
303
+ lambda: self.host.edit_3d_manager.remove_dialog_from_list(dialog)
304
+ )
305
+
268
306
  def open_align_plane_dialog(self, plane: str) -> None:
269
307
  """Open align dialog"""
270
308
  # Get pre-selected atoms
@@ -283,7 +321,7 @@ class DialogManager:
283
321
  parent=self.host,
284
322
  )
285
323
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
286
- dialog.show()
324
+ self._show_modeless_dialog(dialog)
287
325
  dialog.accepted.connect(
288
326
  lambda: self.host.statusBar().showMessage(
289
327
  f"Atoms aligned to {plane.upper()} plane."
@@ -311,7 +349,7 @@ class DialogManager:
311
349
  parent=self.host,
312
350
  )
313
351
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
314
- dialog.show()
352
+ self._show_modeless_dialog(dialog)
315
353
  dialog.accepted.connect(
316
354
  lambda: self.host.statusBar().showMessage(
317
355
  "Selection planarized to best-fit plane."
@@ -340,7 +378,7 @@ class DialogManager:
340
378
  parent=self.host,
341
379
  )
342
380
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
343
- dialog.show()
381
+ self._show_modeless_dialog(dialog)
344
382
  dialog.accepted.connect(
345
383
  lambda: self.host.statusBar().showMessage(
346
384
  f"Atoms aligned to {axis.upper()}-axis."
@@ -368,7 +406,7 @@ class DialogManager:
368
406
  parent=self.host,
369
407
  )
370
408
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
371
- dialog.show()
409
+ self._show_modeless_dialog(dialog)
372
410
  dialog.accepted.connect(
373
411
  lambda: self.host.statusBar().showMessage("Bond length adjusted.")
374
412
  )
@@ -394,7 +432,7 @@ class DialogManager:
394
432
  parent=self.host,
395
433
  )
396
434
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
397
- dialog.show()
435
+ self._show_modeless_dialog(dialog)
398
436
  dialog.accepted.connect(
399
437
  lambda: self.host.statusBar().showMessage("Angle adjusted.")
400
438
  )
@@ -420,7 +458,7 @@ class DialogManager:
420
458
  parent=self.host,
421
459
  )
422
460
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
423
- dialog.show()
461
+ self._show_modeless_dialog(dialog)
424
462
  dialog.accepted.connect(
425
463
  lambda: self.host.statusBar().showMessage("Dihedral angle adjusted.")
426
464
  )
@@ -440,8 +478,18 @@ class DialogManager:
440
478
  self.host.init_manager.measurement_action.setChecked(False)
441
479
  self.host.edit_3d_manager.toggle_measurement_mode(False)
442
480
 
443
- dialog = MirrorDialog(self.host.view_3d_manager.current_mol, self.host)
444
- dialog.exec()
481
+ dialog = MirrorDialog(
482
+ self.host.view_3d_manager.current_mol, self.host, parent=self.host
483
+ )
484
+ self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
485
+ self._show_modeless_dialog(dialog)
486
+ dialog.accepted.connect(
487
+ lambda: self.host.statusBar().showMessage("Mirror applied.")
488
+ )
489
+ dialog.accepted.connect(self.host.edit_actions_manager.push_undo_state)
490
+ dialog.finished.connect(
491
+ lambda: self.host.edit_3d_manager.remove_dialog_from_list(dialog)
492
+ )
445
493
 
446
494
  def open_settings_dialog(self) -> None:
447
495
  """Open the application settings dialog."""
@@ -468,7 +516,7 @@ class DialogManager:
468
516
  self.host.view_3d_manager.current_mol, self.host, parent=self.host
469
517
  )
470
518
  self.host.edit_3d_manager.active_3d_dialogs.append(dialog)
471
- dialog.show()
519
+ self._show_modeless_dialog(dialog)
472
520
  dialog.finished.connect(
473
521
  lambda: self.host.edit_3d_manager.remove_dialog_from_list(dialog)
474
522
  )
@@ -1866,6 +1866,14 @@ class MainInitManager:
1866
1866
  edit_3d_menu.addAction(translation_action)
1867
1867
  self.host.translation_action = translation_action
1868
1868
 
1869
+ move_selected_atoms_action = QAction("Move Selected Atoms...", self.host)
1870
+ move_selected_atoms_action.triggered.connect(
1871
+ self.host.dialog_manager.open_move_selected_atoms_dialog
1872
+ )
1873
+ move_selected_atoms_action.setEnabled(False)
1874
+ edit_3d_menu.addAction(move_selected_atoms_action)
1875
+ self.host.move_selected_atoms_action = move_selected_atoms_action
1876
+
1869
1877
  move_group_action = QAction("Move Group...", self.host)
1870
1878
  move_group_action.triggered.connect(
1871
1879
  self.host.dialog_manager.open_move_group_dialog