Beispiel #1
0
class SimpleEditor(Editor):
    """ Simple style of editor for lists, which displays a scrolling list box
        with only one item visible at a time. A icon next to the list box
        displays a menu of operations on the list.
    """

    # -- Class Constants --------------------------------------------------------

    # Whether the list is displayed in a single row:
    single_row = True

    # Menu for modifying the list
    list_menu = """
       Add &Before     [_menu_before]: self.add_before()
       Add &After      [_menu_after]:  self.add_after()
       ---
       &Delete         [_menu_delete]: self.delete_item()
       ---
       Move &Up        [_menu_up]:     self.move_up()
       Move &Down      [_menu_down]:   self.move_down()
       Move to &Top    [_menu_top]:    self.move_top()
       Move to &Bottom [_menu_bottom]: self.move_bottom()
    """

    empty_list_menu = """
       Add: self.add_empty()
    """

    # -- Facet Definitions ------------------------------------------------------

    # The kind of editor to create for each list item:
    kind = Str

    # Is the list of items being edited mutable?
    mutable = Bool(True)

    # -- Public Methods ---------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        # Initialize the facet handler to use:
        facet_handler = self.factory.facet_handler
        if facet_handler is None:
            facet_handler = self.object.base_facet(self.name).handler
        self._facet_handler = facet_handler

        # Create a scrolled window to hold all of the list item controls:
        self.control = QScrollArea(parent)
        self.control.setFrameShape(QFrame.NoFrame)

        # Create a widget with a grid layout as the container.
        self._list_pane = QWidget()
        layout = QGridLayout(self._list_pane)
        layout.setMargin(0)

        # Remember the editor to use for each individual list item:
        editor = self.factory.editor
        if editor is None:
            editor = facet_handler.item_facet.get_editor()

        self._editor = getattr(editor, self.kind)

        # Set up the additional 'list items changed' event handler needed for
        # a list based facet:
        self.context_object.on_facet_set(self.update_editor_item, self.extended_name + "_items?", dispatch="ui")
        self.set_tooltip()

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        self.context_object.on_facet_set(self.update_editor_item, self.extended_name + "_items?", remove=True)

        super(SimpleEditor, self).dispose()

    def update_editor(self):
        """ Updates the editor when the object facet changes externally to the
            editor.
        """
        # Disconnect the editor from any control about to be destroyed:
        self._dispose_items()

        list_pane = self._list_pane
        layout = list_pane.layout()

        # Create all of the list item facet editors:
        facet_handler = self._facet_handler
        resizable = (facet_handler.minlen != facet_handler.maxlen) and self.mutable
        item_facet = facet_handler.item_facet
        values = self.value
        index = 0

        is_fake = resizable and (len(values) == 0)
        if is_fake:
            values = [item_facet.default_value()[1]]

        editor = self._editor
        # FIXME: Add support for more than one column.
        for value in values:
            if resizable:
                control = IconButton("@facets:list_editor", self.popup_menu)
                layout.addWidget(control, index, 0)

            try:
                proxy = ListItemProxy(self.object, self.name, index, item_facet, value)
                if resizable:
                    control.proxy = proxy

                peditor = editor(self.ui, proxy, "value", self.description).set(object_name="")
                peditor.prepare(list_pane)
                pcontrol = peditor.control
                pcontrol.proxy = proxy
            except:
                if not is_fake:
                    raise

                pcontrol = QPushButton("sample", list_pane)

            if isinstance(pcontrol, QWidget):
                layout.addWidget(pcontrol, index, 1)
            else:
                layout.addLayout(pcontrol, index, 1)

            index += 1

        if is_fake:
            self._cur_control = control
            self.empty_list()
            control.setParent(None)

        if self.single_row:
            rows = 1
        else:
            rows = self.factory.rows

        # list_pane.SetSize( wx.Size(
        #     width + ((facet_handler.maxlen > rows) * scrollbar_dx),
        #     height * rows ) )

        # QScrollArea can have problems if the widget being scrolled is set too
        # early (ie. before it contains something).
        if self.control.widget() is None:
            self.control.setWidget(list_pane)

    def update_editor_item(self, object, name, old, event):
        """ Updates the editor when an item in the object facet changes
            externally to the editor.
        """
        # If this is not a simple, single item update, rebuild entire editor:
        if (len(event.removed) != 1) or (len(event.added) != 1):
            self.update_editor()

            return

        # Otherwise, find the proxy for this index and update it with the
        # changed value:
        for control in self.control.widget().children():
            if isinstance(control, QLayout):
                continue

            proxy = control.proxy
            if proxy.index == event.index:
                proxy.value = event.added[0]
                break

    def empty_list(self):
        """ Creates an empty list entry (so the user can add a new item).
        """
        control = IconButton("@facets:list_editor", self.popup_menu)
        control.is_empty = True
        proxy = ListItemProxy(self.object, self.name, -1, None, None)
        pcontrol = QLabel("   (Empty List)")
        pcontrol.proxy = control.proxy = proxy
        self.reload_sizer([(control, pcontrol)])

    def reload_sizer(self, controls, extra=0):
        """ Reloads the layout from the specified list of ( button, proxy )
            pairs.
        """
        layout = self._list_pane.layout()

        child = layout.takeAt(0)
        while child is not None:
            child = layout.takeAt(0)

        del child

        index = 0
        for control, pcontrol in controls:
            layout.addWidget(control)
            layout.addWidget(pcontrol)

            control.proxy.index = index
            index += 1

    def get_info(self):
        """ Returns the associated object list and current item index.
        """
        proxy = self._cur_control.proxy
        return (proxy.list, proxy.index)

    def popup_empty_menu(self, control):
        """ Displays the empty list editor popup menu.
        """
        self._cur_control = control
        control.PopupMenuXY(MakeMenu(self.empty_list_menu, self, True, control).menu, 0, 0)

    def popup_menu(self):
        """ Displays the list editor popup menu.
        """
        self._cur_control = control = self.control.sender()
        proxy = control.proxy
        index = proxy.index
        menu = MakeMenu(self.list_menu, self, True, control).menu
        len_list = len(proxy.list)
        not_full = len_list < self._facet_handler.maxlen
        self._menu_before.enabled(not_full)
        self._menu_after.enabled(not_full)
        self._menu_delete.enabled(len_list > self._facet_handler.minlen)
        self._menu_up.enabled(index > 0)
        self._menu_top.enabled(index > 0)
        self._menu_down.enabled(index < (len_list - 1))
        self._menu_bottom.enabled(index < (len_list - 1))
        menu.exec_(control.mapToGlobal(QPoint(0, 0)))

    def add_item(self, offset):
        """ Adds a new value at the specified list index.
        """
        list, index = self.get_info()
        index += offset
        item_facet = self._facet_handler.item_facet
        dv = item_facet.default_value()
        if dv[0] == 7:
            func, args, kw = dv[1]
            if kw is None:
                kw = {}
            value = func(*args, **kw)
        else:
            value = dv[1]
        self.value = list[:index] + [value] + list[index:]
        self.update_editor()

    def add_before(self):
        """ Inserts a new item before the current item.
        """
        self.add_item(0)

    def add_after(self):
        """ Inserts a new item after the current item.
        """
        self.add_item(1)

    def add_empty(self):
        """ Adds a new item when the list is empty.
        """
        list, index = self.get_info()
        self.add_item(0)

    def delete_item(self):
        """ Delete the current item.
        """
        list, index = self.get_info()
        self.value = list[:index] + list[index + 1 :]
        self.update_editor()

    def move_up(self):
        """ Move the current item up one in the list.
        """
        list, index = self.get_info()
        self.value = list[: index - 1] + [list[index], list[index - 1]] + list[index + 1 :]

    def move_down(self):
        """ Moves the current item down one in the list.
        """
        list, index = self.get_info()
        self.value = list[:index] + [list[index + 1], list[index]] + list[index + 2 :]

    def move_top(self):
        """ Moves the current item to the top of the list.
        """
        list, index = self.get_info()
        self.value = [list[index]] + list[:index] + list[index + 1 :]

    def move_bottom(self):
        """ Moves the current item to the bottom of the list.
        """
        list, index = self.get_info()
        self.value = list[:index] + list[index + 1 :] + [list[index]]

    # -- Private Methods --------------------------------------------------------

    def _dispose_items(self):
        """ Disposes of each current list item.
        """
        list_pane = self._list_pane
        layout = list_pane.layout()

        for control in list_pane.children():
            editor = getattr(control, "_editor", None)
            if editor is not None:
                editor.dispose()
                editor.control = None
            elif control is not layout:
                control.setParent(None)

        del control