diff --git a/src/bundles/alphafold/src/alphafold21_predict_colab.ipynb b/src/bundles/alphafold/src/alphafold21_predict_colab.ipynb index 5529d99ec5..830bb830a9 100644 --- a/src/bundles/alphafold/src/alphafold21_predict_colab.ipynb +++ b/src/bundles/alphafold/src/alphafold21_predict_colab.ipynb @@ -266,8 +266,8 @@ "# We have to use \"--no-warn-conflicts\" because colab already has a lot preinstalled with requirements different to ours\n", "pip install --no-warn-conflicts \"colabfold[alphafold-minus-jax] @ git+https://github.com/sokrypton/ColabFold@dc9fc3d03379d23784e796f4c7fd31d173bafaa2\"\n", "# high risk high gain\n", - "pip uninstall jaxlib -y\n", - "pip install \"jax[cuda11_cudnn805]==0.3.24\" jaxlib==0.3.24+cuda11.cudnn805 -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", + "pip uninstall jax jaxlib -y\n", + "pip install \"jax[cuda]==0.3.25\" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", "touch COLABFOLD_READY\n", "'''\n", " run_shell_commands(cmds, 'install_colabfold.sh', install_log)\n", diff --git a/src/bundles/alphafold/src/alphafold_test_colab.ipynb b/src/bundles/alphafold/src/alphafold_test_colab.ipynb index 5529d99ec5..830bb830a9 100644 --- a/src/bundles/alphafold/src/alphafold_test_colab.ipynb +++ b/src/bundles/alphafold/src/alphafold_test_colab.ipynb @@ -266,8 +266,8 @@ "# We have to use \"--no-warn-conflicts\" because colab already has a lot preinstalled with requirements different to ours\n", "pip install --no-warn-conflicts \"colabfold[alphafold-minus-jax] @ git+https://github.com/sokrypton/ColabFold@dc9fc3d03379d23784e796f4c7fd31d173bafaa2\"\n", "# high risk high gain\n", - "pip uninstall jaxlib -y\n", - "pip install \"jax[cuda11_cudnn805]==0.3.24\" jaxlib==0.3.24+cuda11.cudnn805 -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", + "pip uninstall jax jaxlib -y\n", + "pip install \"jax[cuda]==0.3.25\" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", "touch COLABFOLD_READY\n", "'''\n", " run_shell_commands(cmds, 'install_colabfold.sh', install_log)\n", diff --git a/src/bundles/alphafold/src/colabfold_predict.ipynb b/src/bundles/alphafold/src/colabfold_predict.ipynb index 5529d99ec5..830bb830a9 100644 --- a/src/bundles/alphafold/src/colabfold_predict.ipynb +++ b/src/bundles/alphafold/src/colabfold_predict.ipynb @@ -266,8 +266,8 @@ "# We have to use \"--no-warn-conflicts\" because colab already has a lot preinstalled with requirements different to ours\n", "pip install --no-warn-conflicts \"colabfold[alphafold-minus-jax] @ git+https://github.com/sokrypton/ColabFold@dc9fc3d03379d23784e796f4c7fd31d173bafaa2\"\n", "# high risk high gain\n", - "pip uninstall jaxlib -y\n", - "pip install \"jax[cuda11_cudnn805]==0.3.24\" jaxlib==0.3.24+cuda11.cudnn805 -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", + "pip uninstall jax jaxlib -y\n", + "pip install \"jax[cuda]==0.3.25\" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\n", "touch COLABFOLD_READY\n", "'''\n", " run_shell_commands(cmds, 'install_colabfold.sh', install_log)\n", diff --git a/src/bundles/alphafold/src/colabfold_predict.py b/src/bundles/alphafold/src/colabfold_predict.py index bd794ea1c2..55c15f5686 100644 --- a/src/bundles/alphafold/src/colabfold_predict.py +++ b/src/bundles/alphafold/src/colabfold_predict.py @@ -239,8 +239,8 @@ def install(use_amber = False, use_templates = False, install_log = 'install_log # We have to use "--no-warn-conflicts" because colab already has a lot preinstalled with requirements different to ours pip install --no-warn-conflicts "colabfold[alphafold-minus-jax] @ git+https://github.com/sokrypton/ColabFold@dc9fc3d03379d23784e796f4c7fd31d173bafaa2" # high risk high gain -pip uninstall jaxlib -y -pip install "jax[cuda11_cudnn805]==0.3.24" jaxlib==0.3.24+cuda11.cudnn805 -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html +pip uninstall jax jaxlib -y +pip install "jax[cuda]==0.3.25" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html touch COLABFOLD_READY ''' run_shell_commands(cmds, 'install_colabfold.sh', install_log) diff --git a/src/bundles/atom_search/bundle_info.xml b/src/bundles/atom_search/bundle_info.xml index 778abba4a6..d198b9bb4d 100644 --- a/src/bundles/atom_search/bundle_info.xml +++ b/src/bundles/atom_search/bundle_info.xml @@ -26,7 +26,7 @@ - + diff --git a/src/bundles/atomic/bundle_info.xml b/src/bundles/atomic/bundle_info.xml index 8475d9ed23..3a28d8cc18 100644 --- a/src/bundles/atomic/bundle_info.xml +++ b/src/bundles/atomic/bundle_info.xml @@ -62,7 +62,7 @@ - + diff --git a/src/bundles/atomic/bundle_info.xml.in b/src/bundles/atomic/bundle_info.xml.in index eee9639d5e..397b132e69 100644 --- a/src/bundles/atomic/bundle_info.xml.in +++ b/src/bundles/atomic/bundle_info.xml.in @@ -62,7 +62,7 @@ - + diff --git a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.cpp b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.cpp index 067f75d36d..3034636531 100644 --- a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.cpp +++ b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.cpp @@ -294,15 +294,17 @@ AtomicStructure::normalize_ss_ids() } void -AtomicStructure::make_chains() const +AtomicStructure::_make_chains() const { - if (_chains != nullptr) { - for (auto c: *_chains) - delete c; - delete _chains; + std::set pre_existing; + if (_chains == nullptr) { + _chains = new Chains(); + } else { + for (auto chain: *_chains) + pre_existing.insert(chain->chain_id()); } + _chains_made = true; // prevent resursion - _chains = new Chains(); auto polys = polymers(); // In an ideal world there would be a one-to-one correspondence between @@ -321,6 +323,8 @@ AtomicStructure::make_chains() const for (auto key_polys: id_to_polys) { auto id_type = key_polys.first; auto chain_id = id_type.first; + if (pre_existing.find(chain_id) != pre_existing.end()) + continue; auto pt_type = id_type.second; auto& chain_polys = key_polys.second; std::vector res_list; diff --git a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.h b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.h index 5b50dd281c..dbbbf581aa 100644 --- a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.h +++ b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/AtomicStructure.h @@ -33,19 +33,19 @@ namespace atomstruct { class ATOMSTRUCT_IMEX AtomicStructure: public Structure { friend class Atom; // for IDATM stuff and structure categories - friend class Bond; // for checking if make_chains() has been run yet, struct categories + friend class Bond; // struct categories friend class Residue; // for _polymers_computed friend class StructureSeq; // for remove_chain() private: void _compute_atom_types(); void _compute_structure_cats() const; + void _make_chains() const; public: AtomicStructure(PyObject* logger = nullptr) : Structure(logger) {} void compute_secondary_structure(float energy_cutoff = -0.5, int min_helix_length = 3, int min_strand_length = 3, bool = false, CompSSInfo* = nullptr); AtomicStructure* copy() const; - void make_chains() const; void normalize_ss_ids(); std::vector> polymers( PolymerMissingStructure missing_structure_treatment = PMS_ALWAYS_CONNECTS, diff --git a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.cpp b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.cpp index 202cd53bde..64535ef23e 100644 --- a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.cpp +++ b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.cpp @@ -883,10 +883,10 @@ Structure::_delete_atoms(const std::set& atoms, bool verify) void Structure::_form_chain_check(Atom* a1, Atom* a2, Bond* b) { - // If initial construction is over (i.e. Python instance exists) and make_chains() - // has been called (i.e. _chains is not null), then need to check if new bond + // If initial construction is over (i.e. Python instance exists) and _make_chains() + // has been called (i.e. _chain_made is true), then need to check if new bond // or missing-structure pseudobond creates a chain or coalesces chain fragments - if (_chains == nullptr) + if (!_chains_made) return; auto inst = py_instance(false); Py_DECREF(inst); @@ -1134,16 +1134,11 @@ Structure::find_residue(const ChainID& chain_id, int num, char insert, ResName& } void -Structure::make_chains() const +Structure::_make_chains() const { - // since Graphs don't have sequences, they don't have chains - if (_chains != nullptr) { - for (auto c: *_chains) - delete c; - delete _chains; - } - + // since non-atomic Structures don't have sequences, they don't have chains _chains = new Chains(); + _chains_made = true; } Atom * diff --git a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.h b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.h index 6b634ccd9b..943c2d7e5c 100644 --- a/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.h +++ b/src/bundles/atomic_lib/atomic_cpp/atomstruct_cpp/Structure.h @@ -146,6 +146,7 @@ class ATOMSTRUCT_IMEX Structure: public GraphicsChanges, float _ball_scale = 0.25; Bonds _bonds; mutable Chains* _chains; + mutable bool _chains_made = false; ChangeTracker* _change_tracker; CoordSets _coord_sets; bool _display = true; @@ -202,6 +203,7 @@ class ATOMSTRUCT_IMEX Structure: public GraphicsChanges, std::set& left_missing_structure_atoms, std::set& right_missing_structure_atoms, const std::set* deleted_atoms = nullptr) const; + virtual void _make_chains() const; Bond* _new_bond(Atom* a1, Atom* a2, bool bond_only); Chain* _new_chain(const ChainID& chain_id, PolymerType pt = PT_NONE) const { auto chain = new Chain(chain_id, const_cast(this), pt); @@ -243,7 +245,7 @@ class ATOMSTRUCT_IMEX Structure: public GraphicsChanges, void bonded_groups(std::vector>* groups, bool consider_missing_structure) const; const Bonds& bonds() const { return _bonds; } - const Chains& chains() const { if (_chains == nullptr) make_chains(); return *_chains; } + const Chains& chains() const { if (!_chains_made) _make_chains(); return *_chains; } void change_chain_ids(const std::vector, const std::vector, bool /*non-polymeric*/=true); ChangeTracker* change_tracker() { return _change_tracker; } @@ -276,7 +278,6 @@ class ATOMSTRUCT_IMEX Structure: public GraphicsChanges, bool is_traj; PyObject* logger() const { return _logger; } bool lower_case_chains; - virtual void make_chains() const; std::map> metadata; Atom* new_atom(const char* name, const Element& e); Bond* new_bond(Atom* a1, Atom* a2) { return _new_bond(a1, a2, false); } diff --git a/src/bundles/atomic_lib/bundle_info.xml b/src/bundles/atomic_lib/bundle_info.xml index ed6613aa52..edc6020d2d 100644 --- a/src/bundles/atomic_lib/bundle_info.xml +++ b/src/bundles/atomic_lib/bundle_info.xml @@ -1,4 +1,4 @@ - - + diff --git a/src/bundles/connect_structure/bundle_info.xml b/src/bundles/connect_structure/bundle_info.xml index 71c932eb8e..b9ee96aa00 100644 --- a/src/bundles/connect_structure/bundle_info.xml +++ b/src/bundles/connect_structure/bundle_info.xml @@ -22,7 +22,7 @@ - + diff --git a/src/bundles/core/src/__main__.py b/src/bundles/core/src/__main__.py index 2fd520add3..5ee14c1573 100644 --- a/src/bundles/core/src/__main__.py +++ b/src/bundles/core/src/__main__.py @@ -806,8 +806,8 @@ def init(argv, event_loop=True): import runpy import warnings exit = SystemExit(os.EX_OK) - from chimerax.core.python_utils import chimerax_user_base - with warnings.catch_warnings(), chimerax_user_base(): + from chimerax.core.python_utils import chimerax_environment + with warnings.catch_warnings(), chimerax_environment(): warnings.filterwarnings("ignore", category=BytesWarning) global_dict = { 'session': sess diff --git a/src/bundles/core/src/python_utils.py b/src/bundles/core/src/python_utils.py index 4edcb149d0..ac5dd012d2 100644 --- a/src/bundles/core/src/python_utils.py +++ b/src/bundles/core/src/python_utils.py @@ -32,21 +32,57 @@ def chimerax_python_executable(): @contextmanager -def chimerax_user_base(): - """Make pip install packages to ChimeraX's customary PYTHONUSERBASE. +def chimerax_environment(): + """Setup environment for Python to match ChimeraX setup - Without this context manager, Python will install packages in the traditional - user directory at, on macOS, ~/Library/Python/(version)/lib/python/site-packages - instead of our location at ~/Library/Application Support/ChimeraX/(cx_version) + In particular, set PYTHONUSERBASE so pip will install/uninstall packages + in the ChimeraX "user" location. Also remove from environment any + variables that would alter ChimeraX's behaviour. """ from chimerax import app_dirs - old_pythonuserbase = os.environ.get('PYTHONUSERBASE', None) - os.environ['PYTHONUSERBASE'] = app_dirs.user_data_dir + PROTECT = { + 'PYTHONDONTWRITEBYTECODE': None, + # 'PYTHONDEBUG': None, + # 'PYTHONINSPECT': None, + # 'PYTHONOPTIMIZE': None, + 'PYTHONNOUSERSITE': None, + # 'PYTHONUNBUFFERED': None, + # 'PYTHONVERBOSE': None, + # 'PYTHONWARNINGS': None, + 'PYTHONSTARTUP': None, + 'PYTHONPATH': None, + 'PYTHONHOME': None, + 'PYTHONPLATLIBDIR': None, + 'PYTHONCASEOK': None, + 'PYTHONUTF8': None, + 'PYTHONIOENCODING': None, + 'PYTHONFAULTHANDLER': None, + # 'PYTHONHASHSEED': None, + 'PYTHONINTMAXSTRDIGITS': None, + # 'PYTHONMALLOC': None, + 'PYTHONCOERCECLOCALE': None, + # 'PYTHONBREAKPOINT': None, + # 'PYTHONDEVMODE': None, + 'PYTHONPYCACHEPREFIX': None, + 'PYTHONWARNDEFAULTENCODING': None, + 'PYTHONUSERBASE': app_dirs.user_data_dir, + } + old_environ = {} + for var, new_value in PROTECT.items(): + old_value = os.environ.get(var, None) + if old_value is None and new_value is None: + continue + old_environ[var] = old_value + if new_value is None: + del os.environ[var] + else: + os.environ[var] = new_value yield - if old_pythonuserbase is None: - del os.environ['PYTHONUSERBASE'] - else: - os.environ['PYTHONUSERBASE'] = old_pythonuserbase + for var, value in old_environ.items(): + if value is None: + del os.environ[var] + else: + os.environ[var] = value def is_link(path): @@ -83,10 +119,9 @@ def run_pip(command): # the user site directory is the ChimeraX application location. import subprocess prog = chimerax_python_executable() - pip_cmd = [prog] + subprocess._args_from_interpreter_flags() + ["-m", "pip"] + pip_cmd = [prog] + ["-m", "pip"] # pip_cmd = [sys.executable, "-m", "pip"] - from chimerax.core.python_utils import chimerax_user_base - with chimerax_user_base(): + with chimerax_environment(): cp = subprocess.run(pip_cmd + command, capture_output=True) return cp diff --git a/src/bundles/core/src/scripting.py b/src/bundles/core/src/scripting.py index 80fb1f65c5..b36896080c 100644 --- a/src/bundles/core/src/scripting.py +++ b/src/bundles/core/src/scripting.py @@ -223,8 +223,6 @@ def probably_chimera1_session(evalue): return False chimera1_session_message = """\ -ChimeraX cannot open a regular Chimera session. An exporter from Chimera -to ChimeraX is being worked on but only handles molecules and molecular surfaces -(not volumes) at this time. If that is sufficient, use the latest Chimera -daily build and its File->Export Scene menu item, and change the resulting -dialog's "File Type" to ChimeraX.""" +ChimeraX cannot open a regular Chimera session. An exporter from Chimera to +ChimeraX is available in the latest Chimera release. Use its File->Export Scene +menu item, and change the resulting dialog's "File Type" to ChimeraX.""" diff --git a/src/bundles/dssp/bundle_info.xml b/src/bundles/dssp/bundle_info.xml index 01c11af70e..f507121eae 100644 --- a/src/bundles/dssp/bundle_info.xml +++ b/src/bundles/dssp/bundle_info.xml @@ -25,7 +25,7 @@ - + diff --git a/src/bundles/mmcif/bundle_info.xml b/src/bundles/mmcif/bundle_info.xml index 6c4786be39..ae23a1f37a 100644 --- a/src/bundles/mmcif/bundle_info.xml +++ b/src/bundles/mmcif/bundle_info.xml @@ -53,7 +53,7 @@ - + diff --git a/src/bundles/mmtf/bundle_info.xml b/src/bundles/mmtf/bundle_info.xml index 01b3d664a9..9d9967d4c0 100644 --- a/src/bundles/mmtf/bundle_info.xml +++ b/src/bundles/mmtf/bundle_info.xml @@ -32,7 +32,7 @@ - + diff --git a/src/bundles/mouse_modes/src/std_modes.py b/src/bundles/mouse_modes/src/std_modes.py index a06b9881da..56342e151f 100644 --- a/src/bundles/mouse_modes/src/std_modes.py +++ b/src/bundles/mouse_modes/src/std_modes.py @@ -865,22 +865,42 @@ class MoveToCenterMode(MouseMode): def mouse_down(self, event): MouseMode.mouse_down(self, event) xyz = _picked_xyz(event, self.session) - if xyz is None: - return - - # Move camera so it is centered on picked point. - c = self.session.main_view.camera - cx,cy,cz = c.position.inverse() * xyz - steps = self.frames - from chimerax.core.commands import Axis - mxy = (-cx/steps, -cy/steps, 0) - axis = Axis(coords = mxy) - from chimerax.std_commands.move import move - move(self.session, axis, frames = steps) + if xyz is not None: + _move_to_center(self.session, xyz, steps=self.frames) + +def _move_to_center(session, xyz, steps = 10): + '''Move camera so it is centered on picked point.''' + c = session.main_view.camera + cx,cy,cz = c.position.inverse() * xyz + from chimerax.core.commands import Axis + mxy = (-cx/steps, -cy/steps, 0) + axis = Axis(coords = mxy) + from chimerax.std_commands.move import move + move(session, axis, frames = steps) + + # Set center of rotation + from chimerax.std_commands import cofr + cofr.cofr(session, pivot=xyz) + +class MoveToCenterOrTranslateMode(MoveMouseMode): + ''' + Clicking on an atom, bond, ribbon, pseudobond or volume surface + centers the view on that point and sets the center of rotation at that position + on mouse release if no drag has been done. Drag translates models. + ''' + name = 'center or translate' + mouse_action = 'translate' + frames = 10 # Animate motion over this number of frames + min_drag = 5 # Pixels. Smaller drags do a centering. - # Set center of rotation - from chimerax.std_commands import cofr - cofr.cofr(self.session, pivot=xyz) + def mouse_up(self, event): + dx,dy = self.mouse_down_position + MoveMouseMode.mouse_up(self, event) + ux,uy = event.position() + if abs(dx-ux) < self.min_drag or abs(dy-uy) < self.min_drag: + xyz = _picked_xyz(event, self.session) + if xyz is not None: + _move_to_center(self.session, xyz, steps=self.frames) class NullMouseMode(MouseMode): '''Used to assign no mode to a mouse button.''' @@ -1161,6 +1181,7 @@ def standard_mouse_mode_classes(): ObjectIdMouseMode, CenterOfRotationMode, MoveToCenterMode, + MoveToCenterOrTranslateMode, SwipeAsScrollMouseMode, NullMouseMode, ] diff --git a/src/bundles/nucleotides/src/_data.py b/src/bundles/nucleotides/src/_data.py index 6d99e719eb..956b69589a 100644 --- a/src/bundles/nucleotides/src/_data.py +++ b/src/bundles/nucleotides/src/_data.py @@ -480,9 +480,11 @@ def take_snapshot(self, session, flags): @classmethod def restore_snapshot(cls, session, data): + nuc = _nucleotides(session) + if data is None: + return nuc if data['version'] != 1: raise RestoreError("unknown nucleotide session state version") - nuc = _nucleotides(session) infos = data['infos'] for mol, (info, params) in infos.items(): if info is None: diff --git a/src/bundles/pdb/bundle_info.xml b/src/bundles/pdb/bundle_info.xml index e976415ef6..4b812630bd 100644 --- a/src/bundles/pdb/bundle_info.xml +++ b/src/bundles/pdb/bundle_info.xml @@ -30,7 +30,7 @@ - + diff --git a/src/bundles/pdb_lib/bundle_info.xml b/src/bundles/pdb_lib/bundle_info.xml index 29e6d9d241..5577576524 100644 --- a/src/bundles/pdb_lib/bundle_info.xml +++ b/src/bundles/pdb_lib/bundle_info.xml @@ -37,7 +37,7 @@ - + diff --git a/src/bundles/toolshed_utils/src/__init__.py b/src/bundles/toolshed_utils/src/__init__.py index d130de1f5f..39406211e7 100644 --- a/src/bundles/toolshed_utils/src/__init__.py +++ b/src/bundles/toolshed_utils/src/__init__.py @@ -261,11 +261,12 @@ def _install_bundle(toolshed, bundles, logger, *, per_user=True, reinstall=False logger.error("You do not have permission to install %s for %s" % (bundle_name, who)) return - installed = re.findall(r"^\s*Successfully installed.*$", results, re.M) - if installed: - logger.info('\n'.join(installed)) - else: - logger.info('No bundles were installed') + # `result` is empty giving -qq, depend on toolshed.reload to report installation + # installed = re.findall(r"^\s*Successfully installed.*$", results, re.M) + # if installed: + # logger.info('\n'.join(installed)) + # else: + # logger.info('No bundles were installed') toolshed.set_install_timestamp(per_user) changes = toolshed.reload(logger, rebuild_cache=True, report=True) diff --git a/src/bundles/ui/bundle_info.xml b/src/bundles/ui/bundle_info.xml index ddfe7c6e25..857b101a0d 100644 --- a/src/bundles/ui/bundle_info.xml +++ b/src/bundles/ui/bundle_info.xml @@ -1,4 +1,4 @@ - diff --git a/src/bundles/ui/src/gui.py b/src/bundles/ui/src/gui.py index 5f11ef37ac..2b80060dfe 100644 --- a/src/bundles/ui/src/gui.py +++ b/src/bundles/ui/src/gui.py @@ -1101,6 +1101,7 @@ def _populate_menus(self, session): self.tools_menu = mb.addMenu("&Tools") self.tools_menu.setToolTipsVisible(True) self.update_tools_menu(session) + self.tools_menu.aboutToShow.connect(self._update_running_tools) self._settings_ui_widget = None self._accumulated_settings_options = [] @@ -1682,8 +1683,8 @@ def update_tools_menu(self, session): self._checkbutton_tools = {} from Qt.QtWidgets import QMenu from Qt.QtGui import QAction - tools_menu = QMenu("&Tools", self.menuBar()) - tools_menu.setToolTipsVisible(True) + tools_menu = self.tools_menu + tools_menu.clear() categories = {} self._tools_cache = set() for bi in session.toolshed.bundle_info(session.logger): @@ -1726,12 +1727,6 @@ def update_tools_menu(self, session): tools_menu.addAction(more_tools) # running tools will go below this... self._tools_menu_separator = tools_menu.addSection("Running Tools") - tools_menu.aboutToShow.connect(self._update_running_tools) - mb = self.menuBar() - old_action = self.tools_menu.menuAction() - mb.insertMenu(old_action, tools_menu) - mb.removeAction(old_action) - self.tools_menu = tools_menu def _update_running_tools(self, *args): # clear out old running tools