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]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 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><{user_name}></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>'