677 self.Table.ResetView(self.VariablesGrid) |
678 self.Table.ResetView(self.VariablesGrid) |
678 event.Skip() |
679 event.Skip() |
679 |
680 |
680 def OnVariablesGridEditorShown(self, event): |
681 def OnVariablesGridEditorShown(self, event): |
681 row, col = event.GetRow(), event.GetCol() |
682 row, col = event.GetRow(), event.GetCol() |
682 classtype = self.Table.GetValueByName(row, "Class") |
683 |
683 if self.Table.GetColLabelValue(col) == "Type": |
684 label_value = self.Table.GetColLabelValue(col) |
684 type_menu = wx.Menu(title='') |
685 if label_value == "Type": |
|
686 debug = self.ParentWindow.Debug |
|
687 type_menu = wx.Menu(title='') # the root menu |
|
688 |
|
689 # build a submenu containing standard IEC types |
685 base_menu = wx.Menu(title='') |
690 base_menu = wx.Menu(title='') |
686 for base_type in self.Controler.GetBaseTypes(): |
691 for base_type in self.Controler.GetBaseTypes(): |
687 new_id = wx.NewId() |
692 new_id = wx.NewId() |
688 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) |
693 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) |
689 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id) |
694 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id) |
|
695 |
690 type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu) |
696 type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu) |
|
697 |
|
698 # build a submenu containing user-defined types |
691 datatype_menu = wx.Menu(title='') |
699 datatype_menu = wx.Menu(title='') |
692 for datatype in self.Controler.GetDataTypes(basetypes = False, debug = self.ParentWindow.Debug): |
700 datatypes = self.Controler.GetDataTypes(basetypes = False, debug = debug) |
|
701 for datatype in datatypes: |
693 new_id = wx.NewId() |
702 new_id = wx.NewId() |
694 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) |
703 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) |
695 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) |
704 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) |
|
705 |
696 type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu) |
706 type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu) |
697 functionblock_menu = wx.Menu(title='') |
707 |
698 bodytype = self.Controler.GetEditedElementBodyType(self.TagName, self.ParentWindow.Debug) |
708 # build a submenu containing function block types |
699 pouname, poutype = self.Controler.GetEditedElementType(self.TagName, self.ParentWindow.Debug) |
709 bodytype = self.Controler.GetEditedElementBodyType(self.TagName, debug) |
700 if classtype in ["Input","Output","InOut","External","Global"] or poutype != "function" and bodytype in ["ST", "IL"]: |
710 pouname, poutype = self.Controler.GetEditedElementType(self.TagName, debug) |
701 for functionblock_type in self.Controler.GetFunctionBlockTypes(self.TagName, self.ParentWindow.Debug): |
711 classtype = self.Table.GetValueByName(row, "Class") |
|
712 |
|
713 if classtype in ["Input", "Output", "InOut", "External", "Global"] or \ |
|
714 poutype != "function" and bodytype in ["ST", "IL"]: |
|
715 functionblock_menu = wx.Menu(title='') |
|
716 fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName, debug) |
|
717 for functionblock_type in fbtypes: |
702 new_id = wx.NewId() |
718 new_id = wx.NewId() |
703 AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) |
719 AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) |
704 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) |
720 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) |
|
721 |
705 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu) |
722 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu) |
|
723 |
706 rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col)) |
724 rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col)) |
707 self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize()) |
725 corner_x = rect.x + rect.width |
|
726 corner_y = rect.y + self.VariablesGrid.GetColLabelSize() |
|
727 |
|
728 # pop up this new menu |
|
729 self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y) |
708 event.Veto() |
730 event.Veto() |
709 else: |
731 else: |
710 event.Skip() |
732 event.Skip() |
711 |
733 |
712 def GetVariableTypeFunction(self, base_type): |
734 def GetVariableTypeFunction(self, base_type): |
801 self.Table.ResetView(self.VariablesGrid) |
823 self.Table.ResetView(self.VariablesGrid) |
802 |
824 |
803 def ClearErrors(self): |
825 def ClearErrors(self): |
804 self.Table.ClearErrors() |
826 self.Table.ClearErrors() |
805 self.Table.ResetView(self.VariablesGrid) |
827 self.Table.ResetView(self.VariablesGrid) |
|
828 |
|
829 class LocationCellControl(wx.PyControl): |
|
830 ''' |
|
831 Custom cell editor control with a text box and a button that launches |
|
832 the BrowseVariableLocationsDialog. |
|
833 ''' |
|
834 def __init__(self, parent, var_panel): |
|
835 wx.Control.__init__(self, parent, -1) |
|
836 self.ParentWindow = parent |
|
837 self.VarPanel = var_panel |
|
838 self.Row = -1 |
|
839 |
|
840 self.Bind(wx.EVT_SIZE, self.OnSize) |
|
841 |
|
842 # create text control |
|
843 self.txt = wx.TextCtrl(self, -1, '') |
|
844 |
|
845 # create browse button |
|
846 self.btn = wx.Button(self, -1, '...', size=(20,20)) |
|
847 self.btn.Bind(wx.EVT_BUTTON, self.OnBtnBrowseClick) |
|
848 |
|
849 szr = wx.BoxSizer(wx.HORIZONTAL) |
|
850 szr.Add(self.txt, proportion=1, flag=wx.EXPAND) |
|
851 szr.Add(self.btn, proportion=0, flag=wx.ALIGN_RIGHT) |
|
852 |
|
853 szr.SetSizeHints(self) |
|
854 self.SetSizer(szr) |
|
855 self.Layout() |
|
856 |
|
857 def SetRow(self, row): |
|
858 '''set the grid row that we're working on''' |
|
859 self.Row = row |
|
860 |
|
861 def OnSize(self, event): |
|
862 '''resize the button and text control to fit''' |
|
863 overall_width = self.GetSize()[0] |
|
864 btn_width, btn_height = self.btn.GetSize() |
|
865 new_txt_width = overall_width - btn_width |
|
866 |
|
867 self.txt.SetSize(wx.Size(new_txt_width, -1)) |
|
868 self.btn.SetDimensions(new_txt_width, -1, btn_width, btn_height) |
|
869 |
|
870 def OnBtnBrowseClick(self, event): |
|
871 # pop up the location browser dialog |
|
872 dia = BrowseVariableLocationsDialog(self.ParentWindow, self.VarPanel) |
|
873 dia.ShowModal() |
|
874 |
|
875 if dia.Selection: |
|
876 loc, iec_type = dia.Selection |
|
877 |
|
878 # set the location |
|
879 self.SetText(loc) |
|
880 |
|
881 # set the variable type |
|
882 # NOTE: this update won't be displayed until editing is complete |
|
883 # (when EndEdit is called). |
|
884 # we can't call VarPanel.RefreshValues() here because it causes |
|
885 # an exception. |
|
886 self.VarPanel.Table.SetValueByName(self.Row, 'Type', iec_type) |
|
887 |
|
888 self.txt.SetFocus() |
|
889 |
|
890 def SetText(self, text): |
|
891 self.txt.SetValue(text) |
|
892 |
|
893 def SetInsertionPoint(self, i): |
|
894 self.txt.SetInsertionPoint(i) |
|
895 |
|
896 def GetText(self): |
|
897 return self.txt.GetValue() |
|
898 |
|
899 def SetFocus(self): |
|
900 self.txt.SetFocus() |
|
901 |
|
902 class LocationCellEditor(wx.grid.PyGridCellEditor): |
|
903 ''' |
|
904 Grid cell editor that uses LocationCellControl to display a browse button. |
|
905 ''' |
|
906 def __init__(self, var_panel): |
|
907 wx.grid.PyGridCellEditor.__init__(self) |
|
908 self.VarPanel = var_panel |
|
909 |
|
910 def Create(self, parent, id, evt_handler): |
|
911 self.text_browse = LocationCellControl(parent, self.VarPanel) |
|
912 self.SetControl(self.text_browse) |
|
913 if evt_handler: |
|
914 self.text_browse.PushEventHandler(evt_handler) |
|
915 |
|
916 def BeginEdit(self, row, col, grid): |
|
917 loc = self.VarPanel.Table.GetValueByName(row, 'Location') |
|
918 self.text_browse.SetText(loc) |
|
919 self.text_browse.SetRow(row) |
|
920 self.text_browse.SetFocus() |
|
921 |
|
922 def EndEdit(self, row, col, grid): |
|
923 loc = self.text_browse.GetText() |
|
924 old_loc = self.VarPanel.Table.GetValueByName(row, 'Location') |
|
925 |
|
926 if loc != old_loc: |
|
927 self.VarPanel.Table.SetValueByName(row, 'Location', loc) |
|
928 |
|
929 # NOTE: this is a really lame hack to force this row's |
|
930 # 'Type' cell to redraw (since it may have changed). |
|
931 # There's got to be a better way than this. |
|
932 self.VarPanel.VariablesGrid.AutoSizeRow(row) |
|
933 return True |
|
934 |
|
935 def SetSize(self, rect): |
|
936 # -2 and +5 give this some extra vertical padding |
|
937 self.text_browse.SetDimensions(rect.x, rect.y-2, |
|
938 rect.width, rect.height+5, |
|
939 wx.SIZE_ALLOW_MINUS_ONE) |
|
940 |
|
941 def Clone(self): |
|
942 return LocationCellEditor(self.VarPanel) |
|
943 |
|
944 class BrowseVariableLocationsDialog(wx.Dialog): |
|
945 # turn LOCATIONDATATYPES inside-out |
|
946 LOCATION_SIZES = {} |
|
947 for size, types in LOCATIONDATATYPES.iteritems(): |
|
948 for type in types: |
|
949 LOCATION_SIZES[type] = size |
|
950 |
|
951 class PluginData: |
|
952 '''contains a plugin's VariableLocationTree''' |
|
953 def __init__(self, plugin): |
|
954 self.subtree = plugin.GetVariableLocationTree() |
|
955 |
|
956 class SubtreeData: |
|
957 '''contains a subtree of a plugin's VariableLocationTree''' |
|
958 def __init__(self, subtree): |
|
959 self.subtree = subtree |
|
960 |
|
961 class VariableData: |
|
962 '''contains all the information about a valid variable location''' |
|
963 def __init__(self, type, dir, loc): |
|
964 self.type = type |
|
965 |
|
966 loc_suffix = '.'.join([str(x) for x in loc]) |
|
967 |
|
968 size = BrowseVariableLocationsDialog.LOCATION_SIZES[type] |
|
969 self.loc = size + loc_suffix |
|
970 |
|
971 # if a direction was given, use it |
|
972 # (if not we'll prompt the user to select one) |
|
973 if dir: |
|
974 self.loc = '%' + dir + self.loc |
|
975 |
|
976 def __init__(self, parent, var_panel): |
|
977 self.VarPanel = var_panel |
|
978 self.Selection = None |
|
979 |
|
980 # create the dialog |
|
981 wx.Dialog.__init__(self, parent=parent, title=_('Browse Variables'), |
|
982 style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, |
|
983 size=(-1, 400)) |
|
984 |
|
985 # create the root sizer |
|
986 sizer = wx.BoxSizer(wx.VERTICAL) |
|
987 self.SetSizer(sizer) |
|
988 |
|
989 # create the tree control |
|
990 self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT) |
|
991 sizer.Add(self.tree, 1, wx.EXPAND|wx.ALL, border=20) |
|
992 |
|
993 # create the Direction sizer and field |
|
994 dsizer = wx.BoxSizer(wx.HORIZONTAL) |
|
995 sizer.Add(dsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, border=20) |
|
996 |
|
997 # direction label |
|
998 ltext = wx.StaticText(self, -1, _('Direction:')) |
|
999 dsizer.Add(ltext, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=20) |
|
1000 |
|
1001 # direction choice |
|
1002 self.DirChoice = wx.Choice(id=-1, parent=self, choices=[_('Input'), _('Output'), _('Memory')]) |
|
1003 dsizer.Add(self.DirChoice, flag=wx.EXPAND) |
|
1004 # set a default for the choice |
|
1005 self.SetSelectedDirection('I') |
|
1006 |
|
1007 # create the button sizer |
|
1008 btsizer = wx.BoxSizer(wx.HORIZONTAL) |
|
1009 sizer.Add(btsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT, border=20) |
|
1010 |
|
1011 # add plugins to the tree |
|
1012 root = self.tree.AddRoot(_('Plugins')) |
|
1013 ctrl = self.VarPanel.Controler |
|
1014 self.AddChildPluginsToTree(ctrl.PluginsRoot, root) |
|
1015 |
|
1016 # -- buttons -- |
|
1017 |
|
1018 # ok button |
|
1019 self.OkButton = wx.Button(self, wx.ID_OK, _('Use Location')) |
|
1020 self.OkButton.SetDefault() |
|
1021 btsizer.Add(self.OkButton, flag=wx.RIGHT, border=5) |
|
1022 |
|
1023 # cancel button |
|
1024 b = wx.Button(self, wx.ID_CANCEL, _('Cancel')) |
|
1025 btsizer.Add(b) |
|
1026 |
|
1027 # -- event handlers -- |
|
1028 |
|
1029 # accept the location on doubleclick or clicking the Use Location button |
|
1030 self.Bind(wx.EVT_BUTTON, self.OnOk, self.OkButton) |
|
1031 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnOk, self.tree) |
|
1032 |
|
1033 # disable the Add button when we're not on a valid variable |
|
1034 self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChange, self.tree) |
|
1035 |
|
1036 # handle the Expand event. this lets us create the tree as it's expanded |
|
1037 # (trying to create it all at once is slow since plugin variable location |
|
1038 # trees can be big) |
|
1039 wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.OnExpand) |
|
1040 |
|
1041 def OnExpand(self, event): |
|
1042 item = event.GetItem() |
|
1043 if not item.IsOk(): |
|
1044 item = self.tree.GetSelection() |
|
1045 |
|
1046 data = self.tree.GetPyData(item) |
|
1047 self.ExpandTree(item, data.subtree) |
|
1048 |
|
1049 def AddChildPluginsToTree(self, plugin, root): |
|
1050 for p in plugin.IECSortedChilds(): |
|
1051 plug_name = p.BaseParams.getName() |
|
1052 new_item = self.tree.AppendItem(root, plug_name) |
|
1053 |
|
1054 # make it look like the tree item has children (since it doesn't yet) |
|
1055 self.tree.SetItemHasChildren(new_item) |
|
1056 |
|
1057 # attach the plugin data to the tree item |
|
1058 self.tree.SetPyData(new_item, BrowseVariableLocationsDialog.PluginData(p)) |
|
1059 |
|
1060 # add child plugins recursively |
|
1061 self.AddChildPluginsToTree(p, new_item) |
|
1062 |
|
1063 def ExpandTree(self, root, subtree): |
|
1064 items = subtree.items() |
|
1065 items.sort() |
|
1066 |
|
1067 for node_name, data in items: |
|
1068 if isinstance(data, dict): |
|
1069 # this is a new subtree |
|
1070 |
|
1071 new_item = self.tree.AppendItem(root, node_name) |
|
1072 self.tree.SetItemHasChildren(new_item) |
|
1073 |
|
1074 # attach the new subtree's data to the tree item |
|
1075 new_data = BrowseVariableLocationsDialog.SubtreeData(data) |
|
1076 self.tree.SetPyData(new_item, new_data) |
|
1077 else: |
|
1078 # this is a new leaf. |
|
1079 |
|
1080 # data is a tuple containing (IEC type, I/Q/M/None, IEC path tuple) |
|
1081 type, dir, loc = data |
|
1082 |
|
1083 node_name = '%s (%s)' % (node_name, type) |
|
1084 new_item = self.tree.AppendItem(root, node_name) |
|
1085 |
|
1086 vd = BrowseVariableLocationsDialog.VariableData(type, dir, loc) |
|
1087 self.tree.SetPyData(new_item, vd) |
|
1088 |
|
1089 def OnSelChange(self, event): |
|
1090 '''updates the text field and the "Use Location" button.''' |
|
1091 item = self.tree.GetSelection() |
|
1092 data = self.tree.GetPyData(item) |
|
1093 |
|
1094 if isinstance(data, BrowseVariableLocationsDialog.VariableData): |
|
1095 self.OkButton.Enable() |
|
1096 |
|
1097 location = data.loc |
|
1098 if location[0] == '%': |
|
1099 # location has a fixed direction |
|
1100 self.SetSelectedDirection(location[1]) |
|
1101 self.DirChoice.Disable() |
|
1102 else: |
|
1103 # this location can have any direction (user selects) |
|
1104 self.DirChoice.Enable() |
|
1105 else: |
|
1106 self.OkButton.Disable() |
|
1107 self.DirChoice.Disable() |
|
1108 |
|
1109 def GetSelectedDirection(self): |
|
1110 selected = self.DirChoice.GetSelection() |
|
1111 if selected == 0: |
|
1112 return 'I' |
|
1113 elif selected == 1: |
|
1114 return 'Q' |
|
1115 else: |
|
1116 return 'M' |
|
1117 |
|
1118 def SetSelectedDirection(self, dir_letter): |
|
1119 if dir_letter == 'I': |
|
1120 self.DirChoice.SetSelection(0) |
|
1121 elif dir_letter == 'Q': |
|
1122 self.DirChoice.SetSelection(1) |
|
1123 else: |
|
1124 self.DirChoice.SetSelection(2) |
|
1125 |
|
1126 def OnOk(self, event): |
|
1127 item = self.tree.GetSelection() |
|
1128 data = self.tree.GetPyData(item) |
|
1129 |
|
1130 if not isinstance(data, BrowseVariableLocationsDialog.VariableData): |
|
1131 return |
|
1132 |
|
1133 location = data.loc |
|
1134 |
|
1135 if location[0] != '%': |
|
1136 # no direction was given, grab the one from the wxChoice |
|
1137 dir = self.GetSelectedDirection() |
|
1138 location = '%' + dir + location |
|
1139 |
|
1140 self.Selection = (location, data.type) |
|
1141 self.Destroy() |