class EditTopLevelToolBar(EditToolBar, PreviewMixin): WXG_BASE = "EditToolBar" IS_TOPLEVEL = True PROPERTIES = EditToolBar.PROPERTIES[:] np.insert_after(PROPERTIES, "name", "class") np.insert_after(PROPERTIES, "tools", "preview") TREE_ICON = "EditMenuBar" def __init__(self, name, parent, klass): EditBase.__init__( self, name, parent, None, klass ) EditStylesMixin.__init__(self) self._init_properties() PreviewMixin.__init__(self) # add a preview button
class EditTopLevelMenuBar(EditMenuBar, PreviewMixin): WXG_BASE = "EditMenuBar" IS_TOPLEVEL = True _PROPERTIES = ["menus", "preview"] PROPERTIES = EditBase.PROPERTIES + _PROPERTIES + EditBase.EXTRA_PROPERTIES np.insert_after(PROPERTIES, "name", "class") TREE_ICON = "EditMenuBar" def __init__(self, name, parent, class_): EditBase.__init__(self, name, parent, None, class_) self.menus = MenuProperty() self.window_id = None # just a dummy for code generation self._mb = None # the real menubar PreviewMixin.__init__(self) # add a preview button
class EditNotebook(ManagedBase, EditStylesMixin): "Class to handle wxNotebook objects" _next_notebook_number = 1 # next free number for notebook names WX_CLASS = "wxNotebook" IS_CONTAINER = True _PROPERTIES = ["Widget", "no_custom_class", "style", "tabs"] PROPERTIES = ManagedBase.PROPERTIES + _PROPERTIES + ManagedBase.EXTRA_PROPERTIES np.insert_after(PROPERTIES, "name", "class", "custom_base") def __init__(self, name, parent, index, style): ManagedBase.__init__(self, name, parent, index) EditStylesMixin.__init__(self) self.properties["style"].set(style) # initialise instance properties self.pages = None # on loading from XML, this will be used tabs = [] # list of page labels of this notebook tab_cols = [('Tab label', np.GridProperty.STRING)] self.tabs = NotebookPagesProperty(tabs, tab_cols) self.no_custom_class = np.CheckBoxProperty(False, default_value=False) def create_widget(self): self.widget = wx.Notebook(self.parent_window.widget, wx.ID_ANY, style=self.style) def child_widgets_created(self, level): # at this time, all children should be available self.pages = None if not self.parent.IS_SIZER or not self.widget: return w, h = self.properties["size"].get_size(self.widget) sz = self.parent sz.widget.SetItemMinSize(self.widget, w, h) def on_set_focus(self, event): # allow switching of pages misc.set_focused_widget(self) event.Skip() #################################################################################################################### def vs_insert_tab(self, index): "inserts or adds a page" label = self.tabs[index][0] item = self.children[index] if not (index < self.widget.GetPageCount()): self.widget.AddPage(item.widget, label) elif self.widget.GetPage(index) is not item.widget: self.widget.InsertPage(index, item.widget, label) try: wx.CallAfter(item.sel_marker.update) except AttributeError: pass if hasattr(self.parent, "set_item_best_size"): self.parent.set_item_best_size(self, size=self.widget.GetBestSize()) def add_slot(self): # actually adds a page, but it needs to be compatible to sizers self.insert_tab(len(self.children), "new tab") def insert_tab(self, index, label, add_panel=True, add_sizer=False): # add tab/page; called from GUI self.properties["tabs"].insert(index, [ label, ]) # create panel and node, add to tree self.insert_item(None, index) # placeholder if add_panel: if not isinstance(add_panel, compat.unicode): add_panel = self.next_pane_name() editor = panel.EditPanel(add_panel, self, index) if add_sizer: sizer = edit_sizers._builder(editor, 0) else: # just add a slot editor = edit_base.Slot(self, index) if self.widget: # add to widget editor.create() compat.SetToolTip(editor.widget, _("Notebook page pane:\nAdd a sizer here")) self.vs_insert_tab(index) try: wx.CallAfter(editor.sel_marker.update) except AttributeError: #self._logger.exception(_('Internal Error')) if config.debugging: raise self.widget.SetSelection(index) self.properties["tabs"].update_display() misc.rebuild_tree(self) def remove_item(self, child, level, keep_slot=False): if not keep_slot: tabs_p = self.properties["tabs"] # if the length is different, then due to tabs_p.apply calling self.set_tabs and so it's removed already if len(tabs_p.value) == len(self.children): del tabs_p.value[child.index] ManagedBase.remove_item(self, child, level, keep_slot) @misc.restore_focus def set_tabs(self, old_labels, indices): # called from tabs property on Apply button """tabs: list of strings indices: the current indices of the tabs or None for a new tab; re-ordering is currently not supported""" keep_indices = [index for index in indices if index is not None] if keep_indices != sorted(keep_indices): raise ValueError("Re-ordering is not yet implemented") keep_indices = set(keep_indices) # set tab labels of existing pages, if modified for (label, ), index in zip(self.tabs, indices): if index is not None and old_labels[index] != label and self.widget: self.widget.SetPageText(index, label) # remove tabs for index in range(len(old_labels) - 1, -1, -1): if not index in keep_indices: self.children[index].recursive_remove(level=0) # insert/add tabs added = None for index, (label, ) in enumerate(self.tabs): if indices[index] is not None: continue # keep tab # actually add/insert self.children.insert(index, None) # create panel and node, add to tree suggestion = "%s_%s" % (self.name, label) editor = panel.EditPanel(self.next_pane_name(suggestion), self, index) if self.widget: # add to widget editor.create() self.vs_insert_tab(index) try: wx.CallAfter(editor.sel_marker.update) except AttributeError: if config.debugging: raise added = index # remember last added index for selection # select the last added tab if added is not None and self.widget: self.widget.SetSelection(added) common.app_tree.build(self, recursive=False) #################################################################################################################### def destroying_child_widget(self, child, index): self.widget.RemovePage( index ) # deletes the specified page, without deleting the associated window def child_widget_created(self, child, level): # add, insert or replace a notebook page index = child.index label = self.tabs[index][0] if not (index < self.widget.GetPageCount()): # this is e.g. for the first creation after loading a file self.widget.AddPage(child.widget, label) else: # single page being replaced; e.g. panel -> slot or slot -> panel self.widget.InsertPage(index, child.widget, label) if level == 0: self.widget.SetSelection(index) try: wx.CallAfter(child.sel_marker.update) except AttributeError: pass def get_itempos(self, attrs): "Get position of sizer item (used in xml_parse)" # only used when loading from XML, while pages is defined name = attrs.get("original_name", None) or attrs['name'] try: return self.pages.index(name) except ValueError: from xml_parse import XmlParsingError raise XmlParsingError( _('Notebook widget "%s" does not have tab "%s"!') % (self.name, name)) #################################################################################################################### def destroy_widget(self, level, later=True): if self.widget: self.widget.DeleteAllPages() ManagedBase.destroy_widget(self, level, later) def get_property_handler(self, name): if name == 'tabs': return TabsHandler(self) return ManagedBase.get_property_handler(self, name) def _get_direction(self, value_set): if value_set is None: return None if 'wxNB_LEFT' in value_set: return "L" if 'wxNB_RIGHT' in value_set: return "R" if 'wxNB_BOTTOM' in value_set: return "B" return "T" def _properties_changed(self, modified, actions): if modified and "tabs" in modified and self.widget: for i, (tab, ) in enumerate(self.tabs): self.widget.SetPageText(i, tab) if modified and "style" in modified: # wxNB_TOP to wxNB_BOTTOM can be changed on the fly; any other change requires re-creation p = self.properties["style"] new = self._get_direction(p.value_set) old = self._get_direction(p.previous_value) if wx.Platform == "__WXMSW__" and ((old == "T" and new == "B") or (old == "B" and new == "T")): actions.add("layout") elif old != new: actions.add("recreate2") EditStylesMixin._properties_changed(self, modified, actions) ManagedBase._properties_changed(self, modified, actions) #################################################################################################################### def _add_popup_menu_items(self, menu, widget): # called from managed widget items' _create_popup_menu method i = misc.append_menu_item(menu, -1, _('Add Notebook Tab')) misc.bind_menu_item_after(widget, i, self.add_slot) def _add_parent_popup_menu_items(self, menu, item, widget): # called from managed widget items' _create_popup_menu method if item.IS_SLOT: i = misc.append_menu_item(menu, -1, _('Remove Notebook Tab\tDel')) misc.bind_menu_item_after(widget, i, item.remove) i = misc.append_menu_item( menu, -1, _('Insert Notebook Tab before')) # \tCtrl+I') ) misc.bind_menu_item_after(widget, i, self.insert_tab, item.index, "new tab") if item.index == len(self.tabs) - 1: # last slot -> allow to add i = misc.append_menu_item(menu, -1, _('Add Notebook Tab')) # \tCtrl+A') ) misc.bind_menu_item_after(widget, i, self.add_slot) menu.AppendSeparator() # helpers ########################################################################################################## def next_pane_name(self, suggestion=None): # return new and (still) unused pane name if suggestion: suggestion = [ c for c in suggestion if "a" <= c <= "z" or "A" <= c <= "Z" or "0" <= c <= "9" or c == "_" ] while suggestion and "0" <= suggestion[0] <= "9": del suggestion[0] suggestion = "".join(suggestion) if suggestion and suggestion not in self.toplevel_parent.names: return suggestion tmpl = "%s_pane_%%d" % self.name tabs = set(tab[0] for tab in self.tabs) return self.toplevel_parent.get_next_contained_name(tmpl, tabs) def _get_slot_label(self, index): title = self.tabs[index][0] return '[%s] PAGE %s' % (title, index) def check_compatibility(self, widget, typename=None): return (False, "No objects can be pasted here; paste to empty pages instead.") def check_drop_compatibility(self): # checks whether a widget can be dropped here return ( False, "Items can only be added to empty pages/slots, not to the notebook itself." ) def _get_parent_tooltip(self, index): return "Notebook page %s:" % index
class EditSplitterWindow(ManagedBase, EditStylesMixin): "Class to handle wxSplitterWindow objects; orientation: Orientation of the widget as string e.g. 'wxSPLIT_VERTICAL'" WX_CLASS = 'wxSplitterWindow' IS_CONTAINER = True _PROPERTIES = [ "Widget", "no_custom_class", "style", "sash_pos", "sash_gravity", "min_pane_size" ] PROPERTIES = ManagedBase.PROPERTIES + _PROPERTIES + ManagedBase._EXTRA_PROPERTIES np.insert_after(PROPERTIES, "name", "class", "custom_base") _PROPERTY_LABELS = { 'no_custom_class': "Don't generate code for this class", 'sash_pos': "Sash position" } _PROPERTY_HELP = { 'no_custom_class': "Don't generate code for this class", 'sash_gravity': "0.0: only the bottom/right window is automatically resized\n" "0.5: both windows grow by equal size\n" "1.0: only left/top window grows" } CHILDREN = 2 def __init__(self, name, parent, index, orientation): ManagedBase.__init__(self, name, parent, index) EditStylesMixin.__init__(self) # initialise instance properties self.no_custom_class = np.CheckBoxProperty(False, default_value=False) self.sash_pos = np.SpinPropertyD(0, default_value="") if hasattr(wx, "SpinCtrlDouble"): self.sash_gravity = np.SpinDoublePropertyD(0.5, (0.0, 1.0), default_value=0.0, immediate=True) else: self.sash_gravity = np.FloatPropertyD(0.5, (0.0, 1.0), default_value=0.0) self.min_pane_size = np.SpinPropertyA(20) # hidden properties: orientation string, window names window_1, window_2 self.orientation = np.Property(orientation) self.window_1 = ChildWidgetNameProperty(0) self.window_2 = ChildWidgetNameProperty(1) def _get_label(self, index): if self.orientation == "wxSPLIT_VERTICAL": return ("Left", "Right")[index] return ("Top", "Bottom")[index] def _get_slot_label(self, index): return "SLOT %s" % self._get_label(index) def create_widget(self): size = self._get_default_or_client_size() self.widget = wx.SplitterWindow(self.parent_window.widget, wx.ID_ANY, size=size, style=self.style) def finish_widget_creation(self, level): ManagedBase.finish_widget_creation(self, level, sel_marker_parent=self.widget) sash_pos_p = self.properties['sash_pos'] if sash_pos_p.is_active(): self.widget.SetSashPosition(sash_pos_p.get()) else: sash_pos_p.set(self.widget.GetSashPosition()) sash_gravity_p = self.properties['sash_gravity'] if sash_gravity_p.is_active(): self.widget.SetSashPosition(sash_gravity_p.get()) min_pane_size_p = self.properties['min_pane_size'] if min_pane_size_p.is_active(): self.widget.SetMinimumPaneSize(min_pane_size_p.get()) else: min_pane_size_p.set(self.widget.GetMinimumPaneSize()) self.widget.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.on_sash_pos_changed) if self.widget.GetTopLevelParent().IsShown(): # e.g. when pasting into an existing window wx.CallAfter(self.widget.UpdateSize) def on_set_focus(self, event): misc.set_focused_widget(self) # here we must call event.Skip() also on Win32 as this we should be able to move the sash event.Skip() def split(self): orientation = self.orientation sash_pos_p = self.properties['sash_pos'] if sash_pos_p.is_active(): sash_pos = sash_pos_p.get() else: max_pos = self.widget.GetClientSize()[0 if orientation == 'wxSPLIT_VERTICAL' else 1] sash_pos = max_pos // 2 if orientation == 'wxSPLIT_VERTICAL': self.widget.SplitVertically(self.children[0].widget, self.children[1].widget, sash_pos) else: self.widget.SplitHorizontally(self.children[0].widget, self.children[1].widget, sash_pos) if getattr(self.children[0], 'sel_marker', None): self.children[0].sel_marker.update() if getattr(self.children[1], 'sel_marker', None): self.children[1].sel_marker.update() def _properties_changed(self, modified, actions): if (not modified or "sash_pos" in modified) and self.widget and self.check_prop("sash_pos"): self.widget.SetSashPosition(self.sash_pos) if (not modified or "sash_gravity" in modified ) and self.widget and self.check_prop("sash_gravity"): self.widget.SetSashGravity(self.sash_gravity) if (not modified or "min_pane_size" in modified ) and self.widget and self.check_prop("min_pane_size"): self.widget.SetMinimumPaneSize(self.min_pane_size) EditStylesMixin._properties_changed(self, modified, actions) ManagedBase._properties_changed(self, modified, actions) if modified and "orientation" in modified and common.app_tree is not None: # update horizontal/vertical icons actions.add("image") if self.children[0] and self.children[0].IS_SLOT: self.children[0].label = self._get_slot_label(0) common.app_tree.refresh(self.children[0]) if self.children[1] and self.children[1].IS_SLOT: self.children[1].label = self._get_slot_label(1) common.app_tree.refresh(self.children[1]) def on_size(self, event): if not self.widget: return try: if self.orientation == 'wxSPLIT_VERTICAL': max_pos = self.widget.GetClientSize()[0] else: max_pos = self.widget.GetClientSize()[1] self.properties['sash_pos'].set_range(-max_pos, max_pos) if not self.properties['sash_pos'].is_active(): self.widget.SetSashPosition(max_pos // 2) self.properties['sash_pos'].set(self.widget.GetSashPosition()) except (AttributeError, KeyError): pass ManagedBase.on_size(self, event) def on_child_pasted(self): if not self.widget: return self.widget.UpdateSize() def on_sash_pos_changed(self, event): self.properties['sash_pos'].set(self.widget.GetSashPosition()) event.Skip() def on_mouse_events(self, event): # resize instead of drag & drop event.Skip() def check_compatibility(self, widget, typename=None): return (False, "No objects can be pasted here; paste to empty slots instead.") def check_drop_compatibility(self): # checks whether a widget can be dropped here return ( False, "Items can only be added to empty slots, not to the splitter window itself." ) def _get_parent_tooltip(self, index): return "%s splitter pane:" % self._get_label(index) #################################################################################################################### # methods moved from SplitterWindowSizer: def add_item(self, child, index=None): if index is not None and self.widget and self.widget.IsSplit(): self.widget.Unsplit(self.children[index].widget) ManagedBase.add_item(self, child, index) def _free_slot(self, index, force_layout=True): "Replaces the element at pos with an empty slot" slot = Slot(self, index) if self.widget: slot.create() return slot def destroying_child_widget(self, child, index): if self.widget.IsSplit(): self.widget.Unsplit(child.widget) def child_widget_created(self, widget, level): if level == 0: self.split() # a single child was added def child_widgets_created(self, level): self.split() def get_itempos(self, attrs): "Get position of sizer item (used in xml_parse)" name = attrs.get("original_name", None) or attrs['name'] if name == self.properties["window_1"].value: return 0 if name == self.properties["window_2"].value: return 1 return None
class EditPanel(PanelBase, ManagedBase): "Class to handle wxPanel objects" WX_CLASS = "wxPanel" # this will be overwritten in properties_changed depending on the "scrolled" property WX_CLASSES = ("wxPanel", "wxScrolledWindow") PROPERTIES = ManagedBase.PROPERTIES + PanelBase._PROPERTIES + ManagedBase.EXTRA_PROPERTIES np.insert_after(PROPERTIES, "name", "class", "custom_base") def __init__(self, name, parent, index, style='wxTAB_TRAVERSAL'): ManagedBase.__init__(self, name, parent, index) PanelBase.__init__(self, style) def create_widget(self): # to be done: use ScrolledWindow only if scrolling is required if self.scrollable: self.widget = wx.ScrolledWindow(self.parent_window.widget, wx.ID_ANY, style=self.style) else: self.widget = wx.Panel(self.parent_window.widget, wx.ID_ANY, style=self.style) self.widget.Bind(wx.EVT_ENTER_WINDOW, self.on_enter) self.widget.GetBestSize = self.get_widget_best_size if not self.parent.IS_SIZER: def GetBestSize(): if self.widget and self.widget.GetSizer(): return self.widget.GetSizer().GetMinSize() return self.widget.__class__.GetBestSize(self.widget) self.widget.GetBestSize = GetBestSize def set_sizer(self, sizer): # called from sizer.create: self.window.set_sizer(self) assert self.children and sizer is self.children[0] super(EditPanel, self).set_sizer(sizer) if sizer and sizer.widget and self.widget: sizer.set_item_best_size(self, size=self.widget.GetBestSize()) def _create_popup_menu(self, widget=None): if widget is None: widget = self.widget menu = misc.wxGladePopupMenu(self.name) i = misc.append_menu_item(menu, -1, _('Remove Panel\tDel'), wx.ART_DELETE) misc.bind_menu_item_after(widget, i, self.remove) i = misc.append_menu_item(menu, -1, _('Copy\tCtrl+C'), wx.ART_COPY) misc.bind_menu_item_after(widget, i, clipboard.copy, self) i = misc.append_menu_item(menu, -1, _('Cut\tCtrl+X'), wx.ART_CUT) misc.bind_menu_item_after(widget, i, clipboard.cut, self) i = misc.append_menu_item(menu, -1, _('Paste Sizer\tCtrl+V'), wx.ART_PASTE) misc.bind_menu_item_after(widget, i, clipboard.paste, self) if self.children or not clipboard.check("sizer"): i.Enable(False) menu.AppendSeparator() if hasattr(self.parent, "_add_parent_popup_menu_items"): self.parent._add_parent_popup_menu_items(menu, self, widget) i = misc.append_menu_item(menu, -1, _('Preview')) misc.bind_menu_item_after(widget, i, self.preview_parent) return menu def check_compatibility(self, widget, typename=None, report=True): "check whether widget can be pasted" if self.children and not self.children[0].IS_SLOT: return (False, 'Sizer already set for this panel') if typename is not None: if typename!="sizer": return (False, 'Only sizers can be pasted here') return (True, None) if not widget.IS_SIZER: return (False, 'Only sizers can be pasted here') return (True, None) def clipboard_paste(self, clipboard_data): "Insert a widget from the clipboard to the current destination" if self.widget: size = self.widget.GetSize() ret = clipboard._paste(self, 0, clipboard_data) if self.widget: self.widget.SetSize(size) return ret def _properties_changed(self, modified, actions): if not modified or "scrollable" in modified: if self.scrollable: self.WX_CLASS = "wxScrolledWindow" else: self.WX_CLASS = "wxPanel" PanelBase._properties_changed(self, modified, actions) ManagedBase._properties_changed(self, modified, actions) def get_properties(self, without=set()): # return list of properties to be written to XML file if not self.scrollable: without.add("scroll_rate") return ManagedBase.get_properties(self, without) def _get_tooltip(self): if self.parent.WX_CLASS in ("wxSplitterWindow", "wxNotebook"): return "Add a sizer here; optionally, delete the panel and add e.g. a notebook, splitter or grid. " return "Add a sizer here"