Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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"