Source code for schrodinger.application.livedesign.ld_export

from collections import defaultdict
from functools import partial
from typing import List

import inflect

from schrodinger.models import parameters
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import mapperwidgets
from schrodinger.ui.qt import propertyselector
from schrodinger.ui.qt.appframework2 import wizards
from schrodinger.ui.qt.widgetmixins import panelmixins

from . import constants
from . import data_classes
from . import entry_types as ets
from . import export_map_manager
from . import export_models
from . import export_tasks
from . import export_ui
from . import ld_folder_tree
from . import ld_utils
from . import live_report_widget
from . import login
from . import panel_components
from . import pose_name_panel
from . import summary

NOT_DEFINED = '(not defined)'
MORE = 'Show More'
LESS = 'Show Less'

MatchCompoundsBy = export_models.MatchCompoundsBy
RefreshResult = export_models.RefreshResult

PROPNAME_IMPORT_ENTITY_ID = constants.PROPNAME_IMPORT_ENTITY_ID
PROPNAME_CORP_ID = constants.PROPNAME_CORP_ID
LD_DATA_3D = export_models.LD_DATA_3D
WARNING_COLOR = QtGui.QColor('firebrick')
SUCCESS_COLOR = QtGui.QColor('green')

# Mapping for user-passed string arguments to custom LD data items. Keys should
# be lowercase strings.
CUSTOM_STR_ITEM_MAP = {'3d': LD_DATA_3D}


[docs]class ExportModel(export_models.LDClientModelMixin, parameters.CompoundParam): ld_client: object ld_models: object entry_data: ets.BaseEntryData = None export_text: str select_text: str entry_type: object = ets.Ligands entry_type_description: str entry_type_name: str input_summary: str ld_destination: export_models.LDDestination match_compounds_by: MatchCompoundsBy match_prop_user_name: str = NOT_DEFINED match_prop_data_name: str more_columns_visible: bool = False task_rl_map: object export_task: export_tasks.MasterExportTask summary_model: export_models.SummaryModel pose_name_text: str = NOT_DEFINED pose_name_model: export_models.PoseNameEditModel lr_widget_model: live_report_widget.LiveReportModel
[docs] @classmethod def configureParam(cls): """ Synchronize parameters that should always have the same value. """ super().configureParam() summary = cls.summary_model task_input = cls.export_task.input lrw_model = cls.lr_widget_model pn_model = cls.pose_name_model ref_pairs = [ (cls.ld_destination, summary.ld_destination), (cls.ld_destination, lrw_model.ld_destination), (cls.ld_destination, task_input.ld_destination), (cls.ld_client, lrw_model.ld_client), (cls.ld_client, task_input.ld_client), (cls.ld_models, task_input.ld_models), (cls.match_compounds_by, summary.match_compounds_by), (cls.entry_type_name, task_input.entry_type_name), (cls.entry_data, cls.pose_name_model.entry_data), (cls.input_summary, summary.input_summary), (task_input.publish_data, summary.publish_data), (task_input.three_d_export_items, summary.three_d_export_items), (task_input.structures_for_2d_export, summary.structures_for_2d_export), (task_input.property_export_specs, summary.property_export_specs), (task_input.ffc_export_specs, summary.ffc_export_specs), (task_input.pose_name_custom_text, pn_model.custom_text_final), (task_input.pose_name_propname, pn_model.property_name_final) ] # yapf: disable for param1, param2 in ref_pairs: cls.setReference(param1, param2)
[docs]class LDExportPanelMixin: """ Mixin for LD Export GUI panels. Provides a standardized window title that includes a custom string and the host name. Subclasses must: 1. Inherit from `mappers.MapperMixin` 2. Have a model that includes a `export_models.LDDestination` parameter called `ld_destination` :cvar TITLE_BASE: the standard string to include at the beginning of the window's title :type TITLE_BASE: str """ TITLE_BASE = NotImplemented
[docs] def getSignalsAndSlots(self, model): return super().getSignalsAndSlots(model) + [ (model.ld_destination.hostChanged, self._updatePanelTitle) ] # yapf: disable
def _updatePanelTitle(self): """ Update the panel title. """ host = self.model.ld_destination.host window_title = self.TITLE_BASE if host: window_title += f' ({host})' self.setWindowTitle(window_title)
[docs]class AbstractExportPanel(LDExportPanelMixin, panelmixins.TaskPanelMixin, wizards.BaseWizardPanel): """ :cvar allow_add_live_reports: whether users should be presented with the option to create a new live report from this panel :vartype allow_add_live_reports: bool """ model_class = ExportModel PANEL_TASKS = [model_class.export_task] ui_module = export_ui EXPORT_MAP_MANAGER_CLASS = export_map_manager.ExportMapManager SELECT_TEXT = 'Maestro properties:' EXPORT_TEXT = 'Map Maestro properties to LiveDesign properties:' MAP_SELECT_TEXT = 'Select Map...' TABLE_MODEL_CLASS = panel_components.ExportTableModel TABLE_VIEW_CLASS = panel_components.ExportTableView SHOW_POSE_PAGE = 0 HIDE_POSE_PAGE = 1 DISABLED_TEXTS = {'', NOT_DEFINED} SAVE_MAPPING_TEXT = 'Save Mapping...' LOAD_MAPPING_TEXT = 'Load Mapping' DELETE_MAPPING_TEXT = 'Delete' DELETE_MAPPING_PREFKEY = 'export_delete_mapping_prefkey' INVALID_MAP_NAME_MSG = 'Please enter a valid name for saving a new map.' MAP_ERROR_MSG = ('Error: The selected mapping could not be successfully ' 'applied. One or more data rows in the export table ' 'may be incorrect.') MAP_EXISTS_MSG = ('The following map name already exists: {0}\nPlease enter' ' a new name.') allow_add_live_reports = True
[docs] def initSetOptions(self): super().initSetOptions() self._default_ld_items_checked = False self._user_default_items = []
[docs] def initSetUp(self): super().initSetUp() self.table_model = self.TABLE_MODEL_CLASS() self.table_model.set3DDataSpecMap(self._get3DDataSpecMap()) self.table_model.setFFCDataSpecMap(self._getFFCDataSpecMap()) self.table_view = self.TABLE_VIEW_CLASS(self) self.table_view.setModel(self.table_model) self.lr_widget = live_report_widget.LiveReportWidget( self, allow_add_live_reports=self.allow_add_live_reports) self.ld_data_tree = panel_components.LDDataTree(self) self.summary_panel = summary.LiveDesignExportSummaryPanel(parent=self) self.pose_name_panel = pose_name_panel.PoseNameEditPanel(parent=self) self.setDownstreamWidgetsEnabled(False) self.match_compounds_btn_grp = mapperwidgets.MappableButtonGroup({ self.ui.match_by_structure_rb: MatchCompoundsBy.structure, self.ui.match_by_corp_id_rb: MatchCompoundsBy.corp_id, }) self.ui.select_property_btn.clicked.connect(self._selectProperty) self.ui.show_more_btn.clicked.connect(self._onShowColumns) self.ui.pose_name_btn.clicked.connect(self.pose_name_panel.run) self.ld_data_tree.dataChanged.connect(self._onLDDataSelectionChanged) self.ui.clear_props_btn.clicked.connect(self.ld_data_tree.uncheckAll) self.ui.use_pose_name_cb.clicked.connect(self._setPoseWidgetsEnabled) # Initialize initial pose widgets self._setPoseWidgetsEnabled(self.ui.use_pose_name_cb.isChecked()) self.publish_btn_grp = mapperwidgets.MappableButtonGroup({ self.ui.unpublish_data_rb: False, self.ui.publish_data_rb: True, }) self.save_mapping_dlg = export_map_manager.SaveMappingDialog( parent=self) self.save_mapping_dlg.saveMappingRequested.connect(self.saveExportMap) # TODO: Hiding the "Show more/less" button for now, since the # functionality afforded by those columns is not yet implemented self.ui.show_more_btn.setVisible(False) # No "Next" or "Cancel" buttons required for this panel self.next_btn.hide() self.cancel_btn.hide()
[docs] def initLayOut(self): super().initLayOut() self.ui.lr_widget_layout.addWidget(self.lr_widget) self.ui.export_table_layout.addWidget(self.table_view) self.ui.ld_data_select_layout.addWidget(self.ld_data_tree)
[docs] def initSetDefaults(self): """ Override `mappers.MapperMixin` to avoid resetting the entire model. """ self._resetPropNameValues() self.updateExportableData() self._setDefaultLDDataItemsChecked()
[docs] def initFinalize(self): super().initFinalize() self._onMatchCompoundMethodChanged() self._onMatchPropertyChanged() self._onPoseNameValueChanged()
[docs] def processPrevPanel(self, state): super().processPrevPanel(state) if not self._default_ld_items_checked: self.initSetDefaults() self._default_ld_items_checked = True
[docs] def setModel(self, model): super().setModel(model) self.summary_panel.setModel(self.model.summary_model) self.lr_widget.setModel(self.model.lr_widget_model) self.pose_name_panel.setModel(self.model.pose_name_model) self.model.export_text = self.EXPORT_TEXT self.model.select_text = self.SELECT_TEXT self.model.ld_destination.host = login.get_host()
[docs] def defineMappings(self): M = self.model_class ui = self.ui return super().defineMappings() + [ (ui.ld_data_select_lbl, M.select_text), (ui.ld_data_export_lbl, M.export_text), (self.match_compounds_btn_grp, M.match_compounds_by), (ui.selected_property_lbl, M.match_prop_user_name), (ui.pose_name_value_lbl, M.pose_name_text), (self.publish_btn_grp, M.export_task.input.publish_data), (ui.input_summary_lbl, M.input_summary), ] # yapf: disable
[docs] def getSignalsAndSlots(self, model): lr_widget = self.lr_widget pn_model = model.pose_name_model return super().getSignalsAndSlots(model) + [ (model.match_compounds_byChanged, self._onMatchCompoundMethodChanged), (model.match_prop_data_nameChanged, self._onMatchPropertyChanged), (model.pose_name_textChanged, self._onPoseNameValueChanged), (model.entry_dataChanged, self.updateExportableData), (model.entry_dataChanged, self._resetPropNameValues), (model.entry_dataChanged, self._updateSummaryData), (model.ld_clientChanged, self._onLDClientChanged), (model.more_columns_visibleChanged, self._onColumnVisibilityChanged), (lr_widget.liveReportSelected, self._onLRSelectionChanged), (lr_widget.newLiveReportSelected, self._onLRSelectionChanged), (lr_widget.liveReportDefaultsApplied, self._onLRSelectionChanged), (lr_widget.disconnected, self._showDisconnectedError), (pn_model.custom_text_finalChanged, self._updatePoseNameLabel), (pn_model.property_name_finalChanged, self._updatePoseNameLabel) ] # yapf: disable
[docs] def defineTaskPreprocessors(self, model): task = model.export_task return super().defineTaskPreprocessors(model) + [ (task, self._checkConnection, constants.ORDER_VALIDATE), (task, self._checkFields, constants.ORDER_VALIDATE), (task, self._checkFFCs, constants.ORDER_VALIDATE), (task, self._copyRLMap, constants.ORDER_COPY_RL_MAP), (task, self._populateTaskParams, constants.ORDER_POPULATE_TASK), (task, self._showSummary, constants.ORDER_SHOW_SUMMARY), (task, self._connectTaskSignals, constants.ORDER_CONNECT_SIGNALS), (task, self._setWaitingStatus, constants.ORDER_POST_SUMMARY) ] # yapf: disable
[docs] def setDefaultCheckedItems(self, item_strs: List[str]): """ Specify which LD data items will be checked by default. :param item_strs: string representations of the desired checked data item. In most cases, this is the structure property name. """ self._default_ld_items_checked = False self._user_default_items = [] unrecognized_item_strs = [] for item_str in item_strs: lddata = CUSTOM_STR_ITEM_MAP.get(item_str.lower()) if lddata is None: try: lddata = data_classes.LDData(data_name=item_str) except: pass if lddata is not None: self._user_default_items.append(lddata) else: unrecognized_item_strs.append(item_str) if unrecognized_item_strs: print(f'Unrecognized item strings: {unrecognized_item_strs}')
def _getDefaultLDDataItems(self) -> List[data_classes.LDData]: """ :return: `LDData` items to be checked by default when first launching the GUI """ return list(self._user_default_items) def _setDefaultLDDataItemsChecked(self): """ Set the desired LD data items to be checked by default. """ default_ldd_items = self._getDefaultLDDataItems() self.setCheckedLDData(default_ldd_items) def _checkConnection(self): """ Determine whether the `LDClient` instance is still connected to the LiveDesign server and attempt to reconnect; if not possible, warn user. """ is_connected = self.model.refreshLDClient() != RefreshResult.failure if not is_connected: self._showDisconnectedError() return is_connected def _showDisconnectedError(self): """ Present the user with an error dialog warning them that they are no longer connected to a LiveDesign server. """ msg = ('The connection to the LiveDesign server has been lost. Try' ' restarting the panel and re-entering your credentials to' ' restore it.') self.error(msg) def _getMissingFields(self): """ Return a list of strings that identify fields in the export table that the user must fill out prior to export. :return: a list of strings describing fields that are required for export but have not been specified :rtype: list[str] """ missing_fields = [] model = self.model if not model.ld_destination.proj_name: missing_fields.append('Project') if not model.ld_destination.lr_name: missing_fields.append('LiveReport') if (model.match_compounds_by == MatchCompoundsBy.corp_id and not model.match_prop_data_name): missing_fields.append('Match Existing Compounds by Property') for spec in self.table_model.getExportSpecMap().values(): missing_table_fields = [] if spec.ld_model == panel_components.MODEL_OR_ASSAY_MISSING: missing_table_fields.append('Model/Assay Column') if spec.ld_endpoint == panel_components.ENDPOINT_MISSING: missing_table_fields.append('Endpoint Column') if missing_table_fields: missing_fields += missing_table_fields break return missing_fields def _checkFields(self): """ Checks that all the needed fields for export are specified. If fields are missing, returns False and an error message detailing the missing fields, otherwise returns True """ missing_fields = self._getMissingFields() if not missing_fields: return True self.table_model.highlight_missing_fields = True msg = 'Export failed. The following fields have missing values:' msg += _get_bulleted_string_from_list(missing_fields) self.error(msg) return False def _checkFFCs(self): """ Prevent the user from exporting multiple FFC items that share the same model/assay name, as this will cause an export failure (PANEL-14681). """ name_to_export_data = defaultdict(list) for spec in self.table_model.getFFCExportSpecMap().values(): name_to_export_data[(spec.ld_model, spec.ld_endpoint)].append(spec) bad_item_strs = [] for model_name, export_data_list in name_to_export_data.items(): if len(export_data_list) > 1: for ed in export_data_list: item_str = f'{model_name}: {ed.name}, {ed.endpoint}' bad_item_strs.append(item_str) if not bad_item_strs: return True msg = ('Export failed. The following data items must not share model/' 'assay names and endpoints:') msg += _get_bulleted_string_from_list(bad_item_strs) self.error(msg) return False def _copyRLMap(self): """ Copy the RL map in anticipation for modification prior to export. """ self.model.task_rl_map = self.model.entry_data.getRLMap() def _populateTaskParams(self): """ Process data from the panel into export task input parameters. """ task_input = self.model.export_task.input table_model = self.table_model rl_map = self.model.task_rl_map prop_specs = table_model.getPropertyExportSpecMap().values() task_input.property_export_specs = list(prop_specs) task_input.three_d_export_items = [] # Prepare data for standard 3D export, if requested corp_id_match_prop = None if self.model.match_compounds_by == MatchCompoundsBy.corp_id: corp_id_match_prop = self.model.match_prop_data_name for spec in table_model.get3DExportSpecMap().values(): spec.addDataToExportTask(self.model.export_task, rl_map, corp_id_match_prop) # Prepare data for standard 2D export. Note that redundancy between # these compounds and the 3D export items will be removed during the # master export task if necessary. compounds = list(set(rl_map.ligands)) ld_utils.set_corp_id_properties(compounds, corp_id_match_prop) task_input.structures_for_2d_export = compounds # Prepare data for freeform column export, if requested ffc_specs = list(table_model.getFFCExportSpecMap().values()) for spec in ffc_specs: spec.getAttachmentData(self.model) task_input.ffc_export_specs = ffc_specs def _showSummary(self): """ Present the user with a summary of data to be exported. """ return self.summary_panel.run(blocking=True, modal=True) def _connectTaskSignals(self): """ Connect signals associated with the task before it is run. This preprocessor should be run last so that there is no chance of connecting a signal/slot pair more than once. """ self.model.export_task.taskDone.connect(self._onExportFinished) self.model.export_task.exportFailed.connect(self._onExportFailed) def _setWaitingStatus(self): """ Notify the user that the export is in progress. """ task = self.model.export_task msg = f'{task.name}: waiting for confirmation from LiveDesign server.' self.setStatus(msg) def _onExportFinished(self): """ Present the user with a dialog with the results of the export process. """ task = self.sender() output = task.output num_attempt = output.num_success + output.num_failure if output.num_success == 0 or output.num_failure > 0: # A failure occurred during the export process if num_attempt == 0: status_msg = 'Something went wrong during the export process.' else: status_msg = (f'{task.name}: {output.num_failure} out of' f' {num_attempt} export processes failed.') lr_url_txt = f'LiveReport URL: {output.lr_url}' result_url_txt = 'Result URLs:' + '\n'.join(output.result_urls) msg = ('\nPlease contact Technical Support at help@schrodinger.com' f' with the following information:\n{lr_url_txt}\n' f'{result_url_txt}') self.setStatus(status_msg, color=WARNING_COLOR) self.error(status_msg + msg) return if output.unexported_items: # No failure occurred, but some 3D items did not get exported num_items = len(output.unexported_items) item_str = inflect.engine().plural('item', num_items) process_str = inflect.engine().plural('process', output.num_success) status_msg = (f'{task.name}: {num_items} {item_str} failed to' f' export during {output.num_success} successful' f' {process_str}.') status_color = WARNING_COLOR else: # Set the active LiveReport to the one to which a successful export # was just performed lr_id = task.input.ld_destination.lr_id if lr_id: self.lr_widget.setLiveReport(lr_id) status_msg = (f'{task.name}: {output.num_success} export' f' processes were successful.') status_color = SUCCESS_COLOR self.setStatus(status_msg, color=status_color) msg = (f'Structures have been added to the LiveReport <a href=' f'"{output.lr_url}">{output.lr_url}</a>') self.info(msg) def _getProperties(self): """ :return: a set of structure property names for all ligands/complexes in the current system :rtype: set[str] """ data_names = set() if not self.model.entry_data: return data_names for ligand in self.model.entry_data.getRLMap().ligands: data_names |= set(ligand.property.keys()) return data_names def _selectProperty(self): """ Launch a dialog prompting the user to select a structure property that contains corporate IDs. """ # Show only string and integer properties, we will further convert # these properties to a string value before exporting. prop_sel_dialog = propertyselector.PropertySelectorDialog( parent=self, type_filter=['s', 'i'], title='Select Corporate ID Property', accept_text='OK', show_alpha_toggle=True, show_filter_field=True) data_names = self._getProperties() properties = prop_sel_dialog.chooseFromList(data_names) if properties: selected_prop = properties[0] self.model.match_prop_user_name = selected_prop.userName() self.model.match_prop_data_name = selected_prop.dataName() def _onMatchCompoundMethodChanged(self): """ Update property selector widget visibility when the user changes how compounds should be matched in LD. """ if self.model.match_compounds_by == MatchCompoundsBy.structure: page = self.HIDE_POSE_PAGE M = self.model_class for param in [M.match_prop_data_name, M.match_prop_user_name]: self.model.reset(param) else: page = self.SHOW_POSE_PAGE self.ui.match_corp_id_stack.setCurrentIndex(page) def _onMatchPropertyChanged(self): """ Update the match property label color when its text is changed. """ enable = self.model.match_prop_data_name not in self.DISABLED_TEXTS self._colorLabel(self.ui.selected_property_lbl, enable) def _setPoseWidgetsEnabled(self, enable): """ Show or hide pose widgets. Reset on hide. """ self.ui.pose_name_value_lbl.setVisible(enable) self.ui.pose_name_btn.setEnabled(enable) if not enable and self.model is not None: self.model.reset(self.model_class.pose_name_text) def _onPoseNameValueChanged(self): pose_name = self.model.pose_name_text enable = pose_name not in self.DISABLED_TEXTS self._colorLabel(self.ui.pose_name_value_lbl, enable) def _colorLabel(self, label, enable: bool): """ Colors label based on enable value. Disabled labels are disabled, enabled labels are bolded """ stylesheet = 'font: bold' if enable else '' label.setEnabled(enable) label.setStyleSheet(stylesheet) def _onLRSelectionChanged(self): """ Respond to the live report ID changing. """ if self.model.refreshLDClient() == RefreshResult.failure: self._showDisconnectedError() lr_defined = False else: lr_defined = bool(self.model.ld_destination.lr_name) self._loadLDFolderTree() self.table_model.disable_lr_columns = not lr_defined self.setDownstreamWidgetsEnabled(lr_defined) self.generateMappingsMenu() def _loadLDFolderTree(self): """ Add the assay names to the assay column tree model. """ proj_id = self.model.ld_destination.proj_id tree = ld_folder_tree.LDFolderTree(proj_id) if proj_id: tree.fillFolderTree() endpoints = dict(tree.endpoints) self.table_model.loadAssayData(endpoints, tree.favorite_endpoints) self.table_model.loadEndpointData(endpoints) # activate the mapping manager host = self.model.ld_destination.host self.export_map_manager = self.EXPORT_MAP_MANAGER_CLASS(host, proj_id) self.generateMappingsMenu() def _getExportableData(self): """ Return a set of data available for the user to select for export to LiveDesign. :return: a list of exportable data :rtype: list[data_classes.LDData] """ ld_data_props = [ data_classes.LDData(data_name=data_name) for data_name in self._getProperties() ] return ld_data_props def _get3DDataSpecMap(self): """ This method associates `LDData` instances with 3D export specs. If a subclass wants to add a non-standard 3D export data to the panel, it must be included here. :return: a dictionary that maps 3D `LDData` instances with their associated export spec classes :rtype: dict[data_classes.LDData, export_models.Base3DExportSpec] """ return {export_models.LD_DATA_3D: export_models.Standard3DExportSpec} def _getFFCDataSpecMap(self): """ This method associates `LDData` instances with FFC export specs. If a subclass wants to add a FFC export data to the panel, it must be included here. :return: a dictionary that maps FFC `LDData` instances with their associated export spec classes :rtype: dict[data_classes.LDData, export_models.FFCExportSpec] """ return {}
[docs] def updateExportableData(self): """ Update the selection and state of the items that can be exported from this panel to LiveDesign. """ _3d_ld_data = self._get3DDataSpecMap().keys() ffc_ld_data = self._getFFCDataSpecMap().keys() ld_data_list = self._getExportableData() ld_data_list += list(_3d_ld_data) + list(ffc_ld_data) self.ld_data_tree.loadData(ld_data_list) self.updateEnabledExportableData() self._onLDDataSelectionChanged()
def _updateSummaryData(self): """ Updates the summary model values on entry data changing """ entry_data = self.model.entry_data self.model.entry_type_description = entry_data.description self.model.input_summary = entry_data.getSummary() self.model.entry_type_name = entry_data.name def _resetPropNameValues(self): """ Resets values for selected property names """ M = self.model_class params_to_reset = [ M.match_compounds_by, M.match_prop_user_name, M.match_prop_data_name ] for param in params_to_reset: self.model.reset(param)
[docs] def updateEnabledExportableData(self): """ Enable or disable exportable data items. Should be overridden in subclasses. """ pass
def _onLDDataSelectionChanged(self): """ Called when LD data selection is changed. """ ld_data_list = self.ld_data_tree.getCheckedData() self.table_model.loadData(ld_data_list)
[docs] def setDownstreamWidgetsEnabled(self, enable): """ Sets the state of the downstream widgets in the Export workflow :param enable: whether to enable the widgets :type enable: bool """ ui = self.ui taskbar = self.getTaskBar() widgets = [ ui.mappings_link, ui.table_splitter, ui.submission_wdg, ui.options_wdg, taskbar ] for widget in widgets: widget.setEnabled(enable)
[docs] def generateMappingsMenu(self): """ Generates the menu used in the mapping link """ self.map_menu = QtWidgets.QMenu(self) all_mappings = self.export_map_manager.getAvailableMappings() if len(all_mappings) == 0: # Only show save mapping self.map_menu.addAction(self.SAVE_MAPPING_TEXT, self.openSaveMappingDialog) else: # Generate "recently used" items with 10 most recent mappings action = self.map_menu.addAction('RECENTLY LOADED') action.setEnabled(False) # Save all actions as lambdas and partials to preserve variable values in current loop for map_name in self.export_map_manager.getMostRecentMappings(): action_lam = lambda m_name: self.openSelectedExportMap(m_name) self.map_menu.addAction(map_name, partial(action_lam, map_name)) load_menu = QtWidgets.QMenu(self.LOAD_MAPPING_TEXT, self) delete_menu = QtWidgets.QMenu(self.DELETE_MAPPING_TEXT, self) for map_name in sorted(all_mappings): # Generate "use" action load_lam = lambda m_name: self.openSelectedExportMap(m_name) load_menu.addAction(map_name, partial(load_lam, map_name)) # Generate "delete" action delete_lam = lambda m_name: self.deleteExportMap(m_name) delete_menu.addAction(map_name, partial(delete_lam, map_name)) self.map_menu.addSeparator() self.map_menu.addMenu(load_menu) self.map_menu.addSeparator() self.map_menu.addAction(self.SAVE_MAPPING_TEXT, self.openSaveMappingDialog) self.map_menu.addSeparator() self.map_menu.addMenu(delete_menu) self.ui.mappings_link.setMenu(self.map_menu)
[docs] def openSaveMappingDialog(self): self.save_mapping_dlg.show() self.save_mapping_dlg.raise_()
[docs] def saveExportMap(self, map_name): """ Save the current export table's state as a map file. """ manager = self.export_map_manager if not map_name: self.warning(self.INVALID_MAP_NAME_MSG) return if map_name in manager.getAvailableMappings(): self.warning(self.MAP_EXISTS_MSG.format(map_name)) return # TODO: Currently, mapping only supports structure property rows. This # can be expanded if necessary to include all non-3D rows mappable_rows = self.table_model.getMappableRows() manager.saveNewMapping(map_name, mappable_rows) self.generateMappingsMenu()
[docs] def openSelectedExportMap(self, map_name): """ Slot connected to selecting a mapping. Open the selected map and use the mappings to populate the export table. This method resets the property selection tree along with the export table. """ file_path = self.export_map_manager.getMapFilePath(map_name) self.openExportMap(file_path) self.generateMappingsMenu()
[docs] def openExportMap(self, map_file): """ Open the map_file and use the mappings to populate the export table. This method resets the property selection tree along with the export table. This function is used in KNIME to restore the Properties table contents from a map.json file :param map_file: Path to a json map file :type map_file: str """ try: map_rows = self.export_map_manager.openMapping(map_file) except export_map_manager.ExportMapTypeError as exc: # The mapping will fail if the host and project from the map do not # match the current host and project self.warning(str(exc)) return self.setExportItemData(map_rows)
[docs] def deleteExportMap(self, map_name): """ Delete the given export map. """ text = (f'Remove "{map_name}" from the list?' '\nThis will cause the corresponding file to be deleted.') if not self.question(title='Warning', text=text, yes_text='Delete', no_text=None, add_cancel_btn=True, icon=QtWidgets.QMessageBox.Warning, save_response_key=self.DELETE_MAPPING_PREFKEY): return self.export_map_manager.deleteMapping(map_name) self.generateMappingsMenu()
[docs] def setExportItemData(self, export_rows): """ Update the panel with the specified export item data. Export data not available in the LD item tree will be ignored. If such data is supplied, the user will be presented with a warning message. :param export_rows: export row data :type export_rows: list[] """ ld_data_list = [row.ld_data for row in export_rows] self.setCheckedLDData(ld_data_list) success = self.table_model.loadMappings(export_rows) if not success: self.warning(self.MAP_ERROR_MSG)
[docs] def setCheckedLDData(self, ld_data_list): """ Assign a check state to the boxes in the data selection tree. May be overridden in subclasses that require more complex behavior when changing the LD data tree state. :param ld_data_list: check boxes associated with these `LDData` objects, and uncheck all other boxes :type ld_data_list: list[data_classes.LDData] """ self.ld_data_tree.setCheckedData(ld_data_list)
def _onLDClientChanged(self): """ Respond to the selection of a new LD client instance. """ version = login.get_LD_version(self.model.ld_client) visible = version >= login.LD_VERSION_CORP_ID_MATCHING self.ui.match_compounds_widget.setVisible(visible) visible = version >= login.LD_VERSION_POSE_NAMES self.ui.pose_name_widget.setVisible(visible) def _onColumnVisibilityChanged(self): """ Update panel state in response to extra column visibility changes. """ text = LESS if self.model.more_columns_visible else MORE self.ui.show_more_btn.setText(text) def _onShowColumns(self): """ Slot invoked when 'Show More' or 'Show Less' button is clicked. """ visible = self.model.more_columns_visible self.table_view.setExtraColumnsVisible(visible) def _updatePoseNameLabel(self): model = self.model.pose_name_model propname = model.property_name user_name = propname.userName() if propname else '' pose_name_text = model.custom_text if user_name: pose_name_text += f'<b>&lt;{user_name}&gt;</b>' if pose_name_text: self.model.pose_name_text = pose_name_text else: self.model.reset(self.model_class.pose_name_text) def _onExportFailed(self, error_str): """ Notify the users when the export fails. :param error_str: a message with more details about the failure :type error_str: str """ self.setStatus(error_str, color=WARNING_COLOR) msg = f'An error occurred during the export process. {error_str}' self.error(msg)
[docs] def setStatus(self, message, timeout=0, color=None): """ Set the status bar text with optional timeout and text color. :param message: the message to display :type message: str :param timeout: time to show message in ms. If set to 0 (default) the message is not cleared. :type timeout: int :param color: text color for message. Default is black. :type color: QtGui.QColor """ color = color or QtGui.QColor(Qt.black) sheet_txt = (f'QStatusBar{{color: rgb({color.red()},{color.green()},' f'{color.blue()}); font:bold}}') self.status_bar.setStyleSheet(sheet_txt) self.status_bar.showMessage(message, timeout)
def _get_bulleted_string_from_list(items): """ Return a HTML-formatted string with a bulleted list of the supplied items. :param items: a list of items to include in the bulleted list string :type items: list(str) :return: a HTML-formatted bulleted list string :type: str """ bulleted_string = '' for item in items: bulleted_string += f'<li>{item}</li>' return f'<ul>{bulleted_string}</ul>'