Source code for schrodinger.ui.qt.forcefield.ffprojectsettingsdlg

import os
import textwrap

import pyhelp

from schrodinger import get_maestro
from schrodinger.infra import mm
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.ui import maestro_ui
from schrodinger.ui.qt import messagebox
from schrodinger.utils import fileutils
from schrodinger.utils import qt_utils
from schrodinger.utils.env import prepend_sys_path

from . import ffglobalpreferencewidget
from . import ffprojectsettingsdlg_ui
from . import forcefield
from . import pathselectorwidget
from . import stylesheet

maestro = get_maestro()


[docs]def get_current_project_name(): """ Returns current project name if current project is a saved project else, return 'Unsaved Project' :return: current project name :rtype: str """ maestro_hub = maestro_ui.MaestroHub.instance() if not maestro or maestro_hub.isCurrentProjectScratch(): return "Unsaved Project" else: pt = maestro.project_table_get() return pt.project_model.getProjectName()
[docs]class FFProjectSettingsDlg(QtWidgets.QDialog): """ Class to bring dialog for project settings for OPLS4 parameters. It offers user to change project specific override for custom OPLS parameters, with other options such as, setting project specific preference as global preference and clearing current project settings. Note: Dialog can be opened outside of Maestro, but there is no option to update the global settings and the project settings are only valid for the active session. :cvar MOST_RECENTLY_USED_LOCATIONS: used to store most recently used locations for custom opls parameters. Used to communicate recently used locations for project settings across various instances of this dialog. :vartype MOST_RECENTLY_USED_LOCATIONS: list[str] """ MOST_RECENTLY_USED_LOCATIONS = []
[docs] def __init__(self, parent, display_customize_btn=True): """ :param parent: is parent widget. :param display_customize_btn: is set to True if Customize... button needs to be visible,False otherwise. """ super().__init__(parent) self.ui = ffprojectsettingsdlg_ui.Ui_FFProjectSettingsDlg() self.ui.setupUi(self) self.setStyleSheet(stylesheet.PROJECT_SETTING_DLG_STYLESHEET) self._setupPathSelectorWidget() self._setupManageDefaultMenu() self._setupDialogConnections() self.ui.customize_btn.setVisible(display_customize_btn)
[docs] def exec(self): """ Method overridden to update UI of dialog before displaying it. """ self.setWindowTitle( f"Set Custom Parameters Location - {get_current_project_name()}") valid_recent_locations = [ path for path in FFProjectSettingsDlg.MOST_RECENTLY_USED_LOCATIONS if os.path.isdir(path) ] self.path_selector_widget.model().setStringList(valid_recent_locations) self._selectLocationWithoutInUseWarning( forcefield.get_custom_opls_dir()) self._updatePreferenceTypeLabel(can_show_project_preference_label=True) self._updatePathWarningLabelAndToolTip() super().exec()
def _setupPathSelectorWidget(self): """ Setups path selector widget of dialog """ self.path_selector_widget = pathselectorwidget.PathSelectorWidget( self, suggestion_limit=10) self.ui.location_selector_layout.addWidget(self.path_selector_widget) self.path_selector_widget.newLocationSelected.connect( self._onCurrentLocationChanged) def _onCurrentLocationChanged(self, path): """ Called when user selects a different location from path selector widget. Notifies user if selected location is already in use and updates UI. :param path: location selected in path selector widget :type path: str """ if path == forcefield.get_custom_opls_dir(): messagebox.show_info( parent=self, title="Location in Use", text='The selected directory is already in use.') self._updateUI() def _setupManageDefaultMenu(self): """ Setups Manage Default menu of dialog. """ manage_custom_menu = QtWidgets.QMenu(self) self.set_as_global_preference_action = QtGui.QAction( "Set as Global Preference", self) self.clear_project_settings_action = QtGui.QAction( "Clear Project Setting", self) manage_custom_menu.addAction(self.set_as_global_preference_action) manage_custom_menu.addAction(self.clear_project_settings_action) self.ui.manage_defaults_btn.setMenu(manage_custom_menu) self.set_as_global_preference_action.triggered.connect( self._setAsGlobalPreference) self.clear_project_settings_action.triggered.connect( self._clearProjectSettings) def _setupDialogConnections(self): """ Setups dialog component connections. """ self.ui.accept_btn.clicked.connect(self._saveSettingsAndClose) self.ui.cancel_btn.clicked.connect(self.close) self.ui.customize_btn.clicked.connect(self._openFFBuilder) self.ui.help_btn.clicked.connect(self._showHelp) def _updatePathWarningLabelAndToolTip(self): """ Updates invalid path label text and tooltip depending on current selected location. Path invalid label and tooltip is set only when dialog is displayed for the first time or clear project setting action is triggered. """ invalid_path_label = "" current_location = self.path_selector_widget.currentText() if not os.path.isdir(current_location): self.ui.customize_btn.setEnabled(False) invalid_path_label += "Path not found" if forcefield.get_project_preference_custom_opls_path(): invalid_path_tooltip = ( 'Project setting location does not exist - clear the ' 'saved project setting or specify a new location') else: invalid_path_tooltip = ( 'Global preference location does not exist - ' 'specify a new location') self._setPathValidationLabelAndTooltip(invalid_path_tooltip, invalid_path_label) else: self._setPathValidationLabelAndTooltip('', '') def _setPathValidationLabelAndTooltip(self, tooltip, label): """ Sets force field path validation label tooltip and text :param tooltip: tooltip text :type tooltip: str :param label: label text :type label: str """ self.ui.invalid_path_icon.setVisible(bool(label)) self.ui.ff_path_validation_label.setText(label) self.ui.ff_path_validation_label.setToolTip(tooltip) def _setAsGlobalPreference(self): """ Sets current selected location in path selector widget as global preference. Will prompt user if wants to clear project setting, if exists. """ global_preference_widget = ffglobalpreferencewidget.FFGlobalPreferenceWidget.getInstance( ) global_preference_widget.updateCustomOplsDir( self.path_selector_widget.currentText()) msgbox_title = "Global Preference Updated" if forcefield.get_project_preference_custom_opls_path(): msg = ( 'The global preference has been updated. This ' 'change will have no effect on the current project, since it uses a ' 'saved project setting.\n' '\n' 'Do you want to clear the project setting now?') if messagebox.show_question(parent=self, title=msgbox_title, text=msg, yes_text='Yes', no_text='No', add_cancel_btn=False): self._clearProjectSettings() return else: self._selectLocationWithoutInUseWarning( forcefield.get_project_preference_custom_opls_path()) else: # Manual line break is added to deal with bad wrapping of text # on darwin platform. # PANEL-20337 is created to fix this issue. msg = textwrap.dedent(""" The global preference has been updated. The project will use this new location. """) messagebox.show_info(parent=self, title=msgbox_title, text=msg) self._selectLocationWithoutInUseWarning( mm.get_preference_custom_opls_path()) self._updatePathWarningLabelAndToolTip() self._updatePreferenceTypeLabel(can_show_project_preference_label=True) def _clearProjectSettings(self): """ Clears project opls dir, adds global preference opls dir to path selector widget and selects it. """ forcefield.set_project_preference_custom_opls_path('') self._selectLocationWithoutInUseWarning( mm.get_preference_custom_opls_path()) self._updatePathWarningLabelAndToolTip() self._updatePreferenceTypeLabel(can_show_project_preference_label=True) def _updateUI(self): """ Update UI components """ self._updatePreferenceTypeLabel(can_show_project_preference_label=False) self._updateOKAndCancelButton() self._updateManageCustomMenu() self._setPathValidationLabelAndTooltip('', '') self.ui.customize_btn.setEnabled(True) def _selectLocationWithoutInUseWarning(self, path): """ Selects new location in path selector widget without warning, if selected location is currently in use. :param path: new location to be selected :type path: str """ with qt_utils.suppress_signals(self.path_selector_widget): self.path_selector_widget.addAndSelectLocationItem(path) self._updateUI() def _saveSettings(self): """ Saves selected path for current project. Popup information dialog to notify user that project setting for opls directory has been updated. Returns True if project settings was saved, False otherwise. :rtype: bool :return: True if project setting was saved, False otherwise. """ current_path = self.path_selector_widget.currentText() if current_path == forcefield.get_custom_opls_dir(): return True if not os.path.isdir(current_path): msg = "The specified directory does not exist" messagebox.show_warning(parent=self, title="Path Not Found", text=msg) self.path_selector_widget.removeItem(0) self._selectLocationWithoutInUseWarning( forcefield.get_custom_opls_dir()) return False forcefield.set_project_preference_custom_opls_path(current_path) if current_path not in FFProjectSettingsDlg.MOST_RECENTLY_USED_LOCATIONS: FFProjectSettingsDlg.MOST_RECENTLY_USED_LOCATIONS.insert( 0, current_path) # Manual line break is added to deal with bad wrapping of text # on darwin platform. # PANEL-20337 is created to fix this issue in general. information_msg = textwrap.dedent(""" The project setting has been saved. Force Field Builder and other panels will use the specified custom parameters location for this project, across Maestro sessions. """) messagebox.show_info(parent=self, title="Project Setting Saved", text=information_msg) self._updateUI() return True def _updatePreferenceTypeLabel(self, can_show_project_preference_label): """ Updates location type label depending on whether project preference for opls directory is present or global preference is being used. If user selects location other than global preference when project setting is not present, label text is cleared. :param can_show_project_preference_label: is set to true if project preference label can be shown, false otherwise. :type can_show_project_preference_label: bool """ project_custom_opls = forcefield.get_project_preference_custom_opls_path( ) global_custom_opls = mm.get_preference_custom_opls_path() curr_path = self.path_selector_widget.currentText() if (can_show_project_preference_label and project_custom_opls == curr_path): self.ui.location_type_label.setText('Using saved project setting') elif not project_custom_opls and curr_path == global_custom_opls: self.ui.location_type_label.setText('Using global preference') else: self.ui.location_type_label.clear() def _updateOKAndCancelButton(self): """ Updates Ok and Cancel button text and enable state. """ is_location_changed = (forcefield.get_custom_opls_dir() != self.path_selector_widget.currentText()) self.ui.accept_btn.setEnabled(is_location_changed) self.ui.cancel_btn.setText("Cancel" if is_location_changed else "Close") def _updateManageCustomMenu(self): """ Updates enable state of action items under Manage Custom Settings menu. Set as Global preference action is enabled when current selected path is not equal to global preference. Clear project setting is only enabled when current project has a opls location preference. """ self.set_as_global_preference_action.setEnabled( self.path_selector_widget.currentText() != mm.get_preference_custom_opls_path() and bool(maestro)) self.clear_project_settings_action.setEnabled( bool(forcefield.get_project_preference_custom_opls_path())) def _openFFBuilder(self): """ Saves selected location as project setting and display force field builder. """ if self._saveSettings(): launch_ffbuilder_gui() self.close() def _saveSettingsAndClose(self): """ Saves selected location as project setting and closes the dialog. """ if self._saveSettings(): self.close() def _showHelp(self): """ Shows help for this dialog. """ pyhelp.mmpyhelp_show_help_topic("CUSTOM_FORCE_FIELD_LOCATION")
[docs]def launch_ffbuilder_gui(): """ Launch the Force Field builder panel """ if maestro: maestro.command("pythonrunbuiltin ffbuilder_gui.panel") else: # FIXME: This is a bad way to open another panel with prepend_sys_path(fileutils.get_mmshare_scripts_dir()): import ffbuilder_gui ffbuilder_gui.panel()
[docs]def show_ff_project_settings_dlg(parent=None, show_customize_btn=True): """ Shows force field project settings dialog. :param parent: is parent object. :param show_customize_btn: is set to True if Customize... button needs to be displayed, False otherwise. """ ff_project_settings_dlg = FFProjectSettingsDlg(parent, show_customize_btn) ff_project_settings_dlg.exec()