msspec_python3/msspec/msspecgui/msspec/gui/clustereditor.py

295 lines
11 KiB
Python

'''
Created on Jan 18, 2016
@author: graffy
'''
# standard libraries
import os
import functools
# import math
# import sys
# from fileinput import close
import ase.io
# 3rd party libraries
import wx
# import cairo
# import wx.lib.wxcairo
# local libraries
import msspecgui.datafloweditor as datafloweditor
import msspecgui.dataflow as dataflow
from msspecgui.dataflow import IDataType
from msspecgui.msspec.gui.bulk_interface import BulkFrame
from msspecgui.msspec.gui.viewmanager import IViewCreator
from msspecgui.datafloweditor import OperatorGui
# based on example https://github.com/wxWidgets/wxPython/blob/master/demo/Cairo.py
class BackgroundContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on the background of the dataflow
'''
def __init__(self, panel):
"""
:type panel: msspecgui.msspec.gui.ClusterEditor
"""
wx.Menu.__init__(self)
self.m_panel = panel
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
# self.add_cluster_modifier_menu_item = self.Append(wx.ID_ANY,"Add Cluster Modifier"," adds a cluster modifier in the cluster editor")
self.add_cluster_modifier_menu = wx.Menu()
self.add_cluster_modifier_menu_item = self.AppendMenu(wx.ID_ANY, "Add Cluster Modifier...", self.add_cluster_modifier_menu)
# self.m_panel.Bind(wx.EVT_MENU, self.on_add_cluster_modifier, self.add_cluster_modifier_menu_item)
data_flow = self.m_panel.get_cluster_flow()
for creator in data_flow.get_operator_creators():
operator_type_id = creator.get_operator_type_id()
menu_item = self.add_cluster_modifier_menu.Append(wx.ID_ANY, operator_type_id, " adds a cluster modifier in the cluster editor")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_add_cluster_modifier, operator_type_id=operator_type_id), menu_item)
def on_add_cluster_modifier(self, event, operator_type_id):
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
"""
self.m_panel.create_operator(operator_type_id)
class OperatorContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on an operator of the dataflow
'''
def __init__(self, panel, operator):
"""
:type panel: msspecgui.msspec.gui.ClusterEditor
:type operator: dataflow.operator.Operator
"""
wx.Menu.__init__(self)
self.m_panel = panel
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this operator")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_operator, operator=operator), menu_item)
for action in operator.creator.get_actions():
menu_item = self.Append(wx.ID_ANY, action.get_name(), " performs this action on the operator")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_operator_action, action=action, operator=operator), menu_item)
def on_perform_operator_action(self, event, action, operator):
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
:type action: dataflow.ioperatorcreator.IOperatorCreator.IAction
:type operator: dataflow.operator.Operator
"""
action.execute_on_operator(operator)
def on_delete_operator(self, event, operator):
"""Callback that is invoked when the user chooses to delete an operator
:param dataflow.Operator operator: the operator that needs to be deleted
"""
data_flow = operator.data_flow
data_flow.delete_operator(operator)
class WireContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on a wire of the dataflow
'''
def __init__(self, panel, wire):
"""
:param msspecgui.msspec.gui.ClusterEditor panel:
:param dataflow.Wire wire: the wire for which this context menu is created
"""
wx.Menu.__init__(self)
self.m_panel = panel
# # create a menu item for each wire action
# for wire_action in self.m_panel.get_cluster_flow().wire_actions:
# menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this wire")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_wire, wire=wire), menu_item)
for data_action in wire.data_type.get_actions():
menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
# def on_perform_wire_action(self, event, wire_action, wire):
# """Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
#
# :param dataflow.Wire.IAction wire_action: the action to perform
# :param dataflow.Wire wire: the wire on which to perform the action
# """
# wire_action.execute_on_wire(wire)
def on_perform_data_action(self, event, data_action, data):
"""Callback that is invoked when the user chooses to add a specific data action using the context menu
:param dataflow.IDataType.IAction data_action: the action to perform on the given data
:param data: the data on which to perform the action
"""
assert isinstance(data, data_action.datatype.get_python_class())
data_action.execute_on_data(data)
def on_delete_wire(self, event, wire):
"""Callback that is invoked when the user chooses to delete a wire using the context menu
:param dataflow.Wire wire: the wire that needs to be deleted
"""
data_flow = wire.data_flow
data_flow.delete_wire(wire)
def opj(path):
"""Convert paths to the platform-specific separator"""
platform_path = apply(os.path.join, tuple(path.split('/')))
# HACK: on Linux, a leading / gets lost...
if path.startswith('/'):
platform_path = '/' + platform_path
return platform_path
class BulkGui(dataflow.IOperatorCreator.IAction):
def get_name(self):
return 'properties via gui'
def execute_on_operator(self, operator):
"""
:type operator: dataflow.operator.Operator
"""
dialog = BulkFrame(None, title="create a single cell", bulk_operator=operator)
print("execute_on_operator : before MainLoop")
result = dialog.ShowModal()
if result == wx.ID_OK:
print("execute_on_operator : signaling operator %d as modified" % operator.id)
operator.data_flow.on_modified_operator(operator)
print "OK"
else:
print "Cancel"
dialog.Destroy()
class ExportCluster(IDataType.IAction):
def __init__(self, cluster_flow):
"""
:param msspecgui.cluster.ClusterFlow cluster_flow:
"""
super(ExportCluster, self).__init__(cluster_flow.get_data_type('physics.atomscluster'))
def get_name(self):
return 'export cluster'
def execute_on_data(self, data):
"""
:param data: the data on which this action needs to be performed
"""
assert isinstance(data, self.datatype.get_python_class())
dlg = wx.FileDialog(None, message="Choose a file to store this cluster (the format of the file is defined by the file's extension)", defaultDir='', defaultFile='', style=wx.FD_SAVE)
if dlg.ShowModal() == wx.ID_OK:
cluster_file_path = dlg.GetPath()
ase.io.write(cluster_file_path, data)
dlg.Destroy()
class ClusterEditor(datafloweditor.DataflowView):
class Creator(IViewCreator):
VIEW_TYPE_NAME = 'cluster editor'
def __init__(self, workspace):
"""
:param msspecgui.msspec.Workspace workspace: the workspace that is associated with the cluster editors created by this creator
"""
self._workspace = workspace
@property
def view_type_name(self):
"""
:return str:
"""
return self.VIEW_TYPE_NAME
def create_view(self, parent):
"""
:param wx.Window parent: the wx.Window that owns the view
:return wx.Panel:
"""
return ClusterEditor(parent, self._workspace.get_cluster_flow())
def __init__(self, parent, cluster_flow):
"""
:param cluster_flow: the dataflow that this editor manipulates
:type cluster_flow: msspec.cluster.clusterflow.ClusterFlow
"""
super(ClusterEditor, self).__init__(parent, cluster_flow, -1)
for operator_creator in cluster_flow.get_operator_creators():
if operator_creator.get_operator_type_id() == 'ase.lattice.bulk':
operator_creator.register_operator_action(BulkGui())
else:
operator_creator.register_operator_action(OperatorGui())
datatype = cluster_flow.get_data_type('physics.atomscluster')
datatype.register_datatype_action(ExportCluster(cluster_flow))
def create_operator(self, operator_type_id):
operator = self.get_cluster_flow().create_operator(operator_type_id)
self.get_cluster_flow().add_operator(operator)
def get_cluster_flow(self):
"""
:rtype: msspec.cluster.clusterflow.ClusterFlow
"""
return self.dataflow
def on_background_context_menu(self, event):
'''
called whenever the user right-clicks in the background of the dataflow
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(BackgroundContextMenu(self), pos)
def on_operator_context_menu(self, event, operator):
'''
called whenever the user right-clicks in an operator of the dataflow
:type event: wx.Event
:type operator: dataflow.operator.Operator
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(OperatorContextMenu(self, operator), pos)
def on_wire_context_menu(self, event, wire):
'''
called whenever the user right-clicks in a wire of the dataflow
:type event: wx.Event
:param dataflow.Wire wire:
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(WireContextMenu(self, wire), pos)