示例#1
0
def test_managing_constraints():
    """Test adding/removing constraints.

    """
    s = Solver()
    v = Variable('foo')
    c1 = v >= 1
    c2 = v <= 0

    with pytest.raises(TypeError):
        s.hasConstraint(object())
    with pytest.raises(TypeError):
        s.addConstraint(object())
    with pytest.raises(TypeError):
        s.removeConstraint(object())

    assert not s.hasConstraint(c1)
    s.addConstraint(c1)
    assert s.hasConstraint(c1)
    with pytest.raises(DuplicateConstraint):
        s.addConstraint(c1)
    with pytest.raises(UnknownConstraint):
        s.removeConstraint(c2)
    with pytest.raises(UnsatisfiableConstraint):
        s.addConstraint(c2)
    s.removeConstraint(c1)
    assert not s.hasConstraint(c1)

    s.addConstraint(c2)
    assert s.hasConstraint(c2)
    s.reset()
    assert not s.hasConstraint(c2)
示例#2
0
def test_managing_constraints():
    """Test adding/removing constraints.

    """
    s = Solver()
    v = Variable('foo')
    c1 = v >= 1
    c2 = v <= 0

    with pytest.raises(TypeError):
        s.hasConstraint(object())
    with pytest.raises(TypeError):
        s.addConstraint(object())
    with pytest.raises(TypeError):
        s.removeConstraint(object())

    assert not s.hasConstraint(c1)
    s.addConstraint(c1)
    assert s.hasConstraint(c1)
    with pytest.raises(DuplicateConstraint):
        s.addConstraint(c1)
    with pytest.raises(UnknownConstraint):
        s.removeConstraint(c2)
    with pytest.raises(UnsatisfiableConstraint):
        s.addConstraint(c2)
    s.removeConstraint(c1)
    assert not s.hasConstraint(c1)

    s.addConstraint(c2)
    assert s.hasConstraint(c2)
    s.reset()
    assert not s.hasConstraint(c2)
示例#3
0
def test_suggesting_values_for_edit_variables():
    """Test suggesting values in different situations.

    """
    # Suggest value for an edit variable entering a weak equality
    s = Solver()
    v1 = Variable('foo')

    s.addEditVariable(v1, 'medium')
    s.addConstraint((v1 == 1) | 'weak')
    s.suggestValue(v1, 2)
    s.updateVariables()
    assert v1.value() == 2

    # Suggest a value for an edit variable entering multiple solver rows
    s.reset()
    v1 = Variable('foo')
    v2 = Variable('bar')
    s = Solver()

    s.addEditVariable(v2, 'weak')
    s.addConstraint(v1 + v2 == 0)
    s.addConstraint((v2 <= -1))
    s.addConstraint((v2 >= 0) | 'weak')
    s.suggestValue(v2, 0)
    s.updateVariables()
    assert v2.value() <= -1
示例#4
0
def test_suggesting_values_for_edit_variables():
    """Test suggesting values in different situations.

    """
    # Suggest value for an edit variable entering a weak equality
    s = Solver()
    v1 = Variable('foo')

    s.addEditVariable(v1, 'medium')
    s.addConstraint((v1 == 1) | 'weak')
    s.suggestValue(v1, 2)
    s.updateVariables()
    assert v1.value() == 2

    # Suggest a value for an edit variable entering multiple solver rows
    s.reset()
    v1 = Variable('foo')
    v2 = Variable('bar')
    s = Solver()

    s.addEditVariable(v2, 'weak')
    s.addConstraint(v1 + v2 == 0)
    s.addConstraint((v2 <= -1))
    s.addConstraint((v2 >= 0) | 'weak')
    s.suggestValue(v2, 0)
    s.updateVariables()
    assert v2.value() <= -1
示例#5
0
def test_solving_with_strength():
    """Test solving a system with unstatisfiable non-required constraint.

    """
    v1 = Variable('foo')
    v2 = Variable('bar')
    s = Solver()

    s.addConstraint(v1 + v2 == 0)
    s.addConstraint(v1 == 10)
    s.addConstraint((v2 >= 0) | 'weak')
    s.updateVariables()
    assert v1.value() == 10 and v2.value() == -10

    s.reset()

    s.addConstraint(v1 + v2 == 0)
    s.addConstraint((v1 >= 10) | 'medium')
    s.addConstraint((v2 == 2) | 'strong')
    s.updateVariables()
    assert v1.value() == -2 and v2.value() == 2
示例#6
0
def test_solving_with_strength():
    """Test solving a system with unstatisfiable non-required constraint.

    """
    v1 = Variable('foo')
    v2 = Variable('bar')
    s = Solver()

    s.addConstraint(v1 + v2 == 0)
    s.addConstraint(v1 == 10)
    s.addConstraint((v2 >= 0) | 'weak')
    s.updateVariables()
    assert v1.value() == 10 and v2.value() == -10

    s.reset()

    s.addConstraint(v1 + v2 == 0)
    s.addConstraint((v1 >= 10) | 'medium')
    s.addConstraint((v2 == 2) | 'strong')
    s.updateVariables()
    assert v1.value() == -2 and v2.value() == 2
示例#7
0
def test_managing_edit_variable():
    """Test adding/removing edit variables.

    """
    s = Solver()
    v1 = Variable('foo')
    v2 = Variable('bar')

    with pytest.raises(TypeError):
        s.hasEditVariable(object())
    with pytest.raises(TypeError):
        s.addEditVariable(object(), 'weak')
    with pytest.raises(TypeError):
        s.removeEditVariable(object())
    with pytest.raises(TypeError):
        s.suggestValue(object(), 10)

    assert not s.hasEditVariable(v1)
    s.addEditVariable(v1, 'weak')
    assert s.hasEditVariable(v1)
    with pytest.raises(DuplicateEditVariable):
        s.addEditVariable(v1, 'medium')
    with pytest.raises(UnknownEditVariable):
        s.removeEditVariable(v2)
    s.removeEditVariable(v1)
    assert not s.hasEditVariable(v1)

    with pytest.raises(BadRequiredStrength):
        s.addEditVariable(v1, 'required')

    s.addEditVariable(v2, 'strong')
    assert s.hasEditVariable(v2)
    with pytest.raises(UnknownEditVariable):
        s.suggestValue(v1, 10)

    s.reset()
    assert not s.hasEditVariable(v2)
示例#8
0
def test_managing_edit_variable():
    """Test adding/removing edit variables.

    """
    s = Solver()
    v1 = Variable('foo')
    v2 = Variable('bar')

    with pytest.raises(TypeError):
        s.hasEditVariable(object())
    with pytest.raises(TypeError):
        s.addEditVariable(object(), 'weak')
    with pytest.raises(TypeError):
        s.removeEditVariable(object())
    with pytest.raises(TypeError):
        s.suggestValue(object(), 10)

    assert not s.hasEditVariable(v1)
    s.addEditVariable(v1, 'weak')
    assert s.hasEditVariable(v1)
    with pytest.raises(DuplicateEditVariable):
        s.addEditVariable(v1, 'medium')
    with pytest.raises(UnknownEditVariable):
        s.removeEditVariable(v2)
    s.removeEditVariable(v1)
    assert not s.hasEditVariable(v1)

    with pytest.raises(BadRequiredStrength):
        s.addEditVariable(v1, 'required')

    s.addEditVariable(v2, 'strong')
    assert s.hasEditVariable(v2)
    with pytest.raises(UnknownEditVariable):
        s.suggestValue(v1, 10)

    s.reset()
    assert not s.hasEditVariable(v2)
示例#9
0
def test_managing_constraints():
    """Test adding/removing constraints.

    """
    s = Solver()
    v = Variable('foo')
    v2 = Variable('bar')
    c1 = v >= 1
    c2 = v <= 0
    c3 = ((v2 >= 1) and (v2 <= 0))

    with pytest.raises(TypeError):
        s.hasConstraint(object())
    with pytest.raises(TypeError):
        s.addConstraint(object())
    with pytest.raises(TypeError):
        s.removeConstraint(object())

    assert not s.hasConstraint(c1)
    s.addConstraint(c1)
    assert s.hasConstraint(c1)
    with pytest.raises(DuplicateConstraint):
        s.addConstraint(c1)
    with pytest.raises(UnknownConstraint):
        s.removeConstraint(c2)
    with pytest.raises(UnsatisfiableConstraint):
        s.addConstraint(c2)
    # XXX need to find how to get an invalid symbol from choose subject
    # with pytest.raises(UnsatisfiableConstraint):
    #     s.addConstraint(c3)
    s.removeConstraint(c1)
    assert not s.hasConstraint(c1)

    s.addConstraint(c2)
    assert s.hasConstraint(c2)
    s.reset()
    assert not s.hasConstraint(c2)
示例#10
0
def test_managing_constraints():
    """Test adding/removing constraints.

    """
    s = Solver()
    v = Variable('foo')
    v2 = Variable('bar')
    c1 = v >= 1
    c2 = v <= 0
    c3 = ((v2 >= 1) and (v2 <= 0))

    with pytest.raises(TypeError):
        s.hasConstraint(object())
    with pytest.raises(TypeError):
        s.addConstraint(object())
    with pytest.raises(TypeError):
        s.removeConstraint(object())

    assert not s.hasConstraint(c1)
    s.addConstraint(c1)
    assert s.hasConstraint(c1)
    with pytest.raises(DuplicateConstraint):
        s.addConstraint(c1)
    with pytest.raises(UnknownConstraint):
        s.removeConstraint(c2)
    with pytest.raises(UnsatisfiableConstraint):
        s.addConstraint(c2)
    # XXX need to find how to get an invalid symbol from choose subject
    # with pytest.raises(UnsatisfiableConstraint):
    #     s.addConstraint(c3)
    s.removeConstraint(c1)
    assert not s.hasConstraint(c1)

    s.addConstraint(c2)
    assert s.hasConstraint(c2)
    s.reset()
    assert not s.hasConstraint(c2)
示例#11
0
class Grid(Widget):
    """Widget for proportionally dividing its internal area into a grid.

    This widget will automatically set the position and size of child widgets
    according to provided constraints.

    Parameters
    ----------
    spacing : int
        Spacing between widgets.
    **kwargs : dict
        Keyword arguments to pass to `Widget`.
    """
    def __init__(self, spacing=6, **kwargs):
        """Create solver and basic grid parameters."""
        self._next_cell = [0, 0]  # row, col
        self._cells = {}
        self._grid_widgets = {}
        self.spacing = spacing
        self._n_added = 0
        self._default_class = ViewBox  # what to add when __getitem__ is used
        self._solver = Solver()
        self._need_solver_recreate = True

        # width and height of the Rect used to place child widgets
        self._var_w = Variable("w_rect")
        self._var_h = Variable("h_rect")

        self._width_grid = None
        self._height_grid = None

        # self._height_stay = None
        # self._width_stay = None

        Widget.__init__(self, **kwargs)

    def __getitem__(self, idxs):
        """Return an item or create it if the location is available."""
        if not isinstance(idxs, tuple):
            idxs = (idxs, )
        if len(idxs) == 1:
            idxs = idxs + (slice(0, 1, None), )
        elif len(idxs) != 2:
            raise ValueError('Incorrect index: %s' % (idxs, ))
        lims = np.empty((2, 2), int)
        for ii, idx in enumerate(idxs):
            if isinstance(idx, int):
                idx = slice(idx, idx + 1, None)
            if not isinstance(idx, slice):
                raise ValueError('indices must be slices or integers, not %s' %
                                 (type(idx), ))
            if idx.step is not None and idx.step != 1:
                raise ValueError('step must be one or None, not %s' % idx.step)
            start = 0 if idx.start is None else idx.start
            end = self.grid_size[ii] if idx.stop is None else idx.stop
            lims[ii] = [start, end]
        layout = self.layout_array
        existing = layout[lims[0, 0]:lims[0, 1], lims[1, 0]:lims[1, 1]] + 1
        if existing.any():
            existing = set(list(existing.ravel()))
            ii = list(existing)[0] - 1
            if len(existing) != 1 or (
                (layout == ii).sum() != np.prod(np.diff(lims))):
                raise ValueError('Cannot add widget (collision)')
            return self._grid_widgets[ii][-1]
        spans = np.diff(lims)[:, 0]
        item = self.add_widget(self._default_class(),
                               row=lims[0, 0],
                               col=lims[1, 0],
                               row_span=spans[0],
                               col_span=spans[1])
        return item

    def add_widget(self,
                   widget=None,
                   row=None,
                   col=None,
                   row_span=1,
                   col_span=1,
                   **kwargs):
        """Add a new widget to this grid.

        This will cause other widgets in the grid to be resized to make room
        for the new widget. Can be used to replace a widget as well.

        Parameters
        ----------
        widget : Widget | None
            The Widget to add. New widget is constructed if widget is None.
        row : int
            The row in which to add the widget (0 is the topmost row)
        col : int
            The column in which to add the widget (0 is the leftmost column)
        row_span : int
            The number of rows to be occupied by this widget. Default is 1.
        col_span : int
            The number of columns to be occupied by this widget. Default is 1.
        **kwargs : dict
            parameters sent to the new Widget that is constructed if
            widget is None

        Notes
        -----
        The widget's parent is automatically set to this grid, and all other
        parent(s) are removed.
        """
        if row is None:
            row = self._next_cell[0]
        if col is None:
            col = self._next_cell[1]

        if widget is None:
            widget = Widget(**kwargs)
        else:
            if kwargs:
                raise ValueError("cannot send kwargs if widget is given")

        _row = self._cells.setdefault(row, {})
        _row[col] = widget
        self._grid_widgets[self._n_added] = (row, col, row_span, col_span,
                                             widget)
        self._n_added += 1
        widget.parent = self

        self._next_cell = [row, col + col_span]

        widget._var_w = Variable("w-(row: %s | col: %s)" % (row, col))
        widget._var_h = Variable("h-(row: %s | col: %s)" % (row, col))

        # update stretch based on colspan/rowspan
        # usually, if you make something consume more grids or columns,
        # you also want it to actually *take it up*, ratio wise.
        # otherwise, it will never *use* the extra rows and columns,
        # thereby collapsing the extras to 0.
        stretch = list(widget.stretch)
        stretch[0] = col_span if stretch[0] is None else stretch[0]
        stretch[1] = row_span if stretch[1] is None else stretch[1]
        widget.stretch = stretch

        self._need_solver_recreate = True

        return widget

    def remove_widget(self, widget):
        """Remove a widget from this grid.

        Parameters
        ----------
        widget : Widget
            The Widget to remove
        """
        self._grid_widgets = dict((key, val)
                                  for (key, val) in self._grid_widgets.items()
                                  if val[-1] != widget)

        self._need_solver_recreate = True

    def resize_widget(self, widget, row_span, col_span):
        """Resize a widget in the grid to new dimensions.

        Parameters
        ----------
        widget : Widget
            The widget to resize
        row_span : int
            The number of rows to be occupied by this widget.
        col_span : int
            The number of columns to be occupied by this widget.
        """
        row = None
        col = None

        for (r, c, _rspan, _cspan, w) in self._grid_widgets.values():
            if w == widget:
                row = r
                col = c

                break

        if row is None or col is None:
            raise ValueError("%s not found in grid" % widget)

        self.remove_widget(widget)
        self.add_widget(widget, row, col, row_span, col_span)
        self._need_solver_recreate = True

    def _prepare_draw(self, view):
        self._update_child_widget_dim()

    def add_grid(self, row=None, col=None, row_span=1, col_span=1, **kwargs):
        """
        Create a new Grid and add it as a child widget.

        Parameters
        ----------
        row : int
            The row in which to add the widget (0 is the topmost row)
        col : int
            The column in which to add the widget (0 is the leftmost column)
        row_span : int
            The number of rows to be occupied by this widget. Default is 1.
        col_span : int
            The number of columns to be occupied by this widget. Default is 1.
        **kwargs : dict
            Keyword arguments to pass to the new `Grid`.
        """
        from .grid import Grid
        grid = Grid(**kwargs)
        return self.add_widget(grid, row, col, row_span, col_span)

    def add_view(self, row=None, col=None, row_span=1, col_span=1, **kwargs):
        """
        Create a new ViewBox and add it as a child widget.

        Parameters
        ----------
        row : int
            The row in which to add the widget (0 is the topmost row)
        col : int
            The column in which to add the widget (0 is the leftmost column)
        row_span : int
            The number of rows to be occupied by this widget. Default is 1.
        col_span : int
            The number of columns to be occupied by this widget. Default is 1.
        **kwargs : dict
            Keyword arguments to pass to `ViewBox`.
        """
        view = ViewBox(**kwargs)
        return self.add_widget(view, row, col, row_span, col_span)

    def next_row(self):
        self._next_cell = [self._next_cell[0] + 1, 0]

    @property
    def grid_size(self):
        rvals = [
            widget[0] + widget[2] for widget in self._grid_widgets.values()
        ]
        cvals = [
            widget[1] + widget[3] for widget in self._grid_widgets.values()
        ]
        return max(rvals + [0]), max(cvals + [0])

    @property
    def layout_array(self):
        locs = -1 * np.ones(self.grid_size, int)
        for key in self._grid_widgets.keys():
            r, c, rs, cs = self._grid_widgets[key][:4]
            locs[r:r + rs, c:c + cs] = key
        return locs

    def __repr__(self):
        return (('<Grid at %s:\n' % hex(id(self))) +
                str(self.layout_array + 1) + '>')

    @staticmethod
    def _add_total_width_constraints(solver, width_grid, _var_w):
        for ws in width_grid:
            width_expr = ws[0]
            for w in ws[1:]:
                width_expr += w
            solver.addConstraint(width_expr == _var_w)

    @staticmethod
    def _add_total_height_constraints(solver, height_grid, _var_h):
        for hs in height_grid:
            height_expr = hs[0]
            for h in hs[1:]:
                height_expr += h
            solver.addConstraint(height_expr == _var_h)

    @staticmethod
    def _add_gridding_width_constraints(solver, width_grid):
        # access widths of one "y", different x
        for ws in width_grid.T:
            for w in ws[1:]:
                solver.addConstraint(ws[0] == w)

    @staticmethod
    def _add_gridding_height_constraints(solver, height_grid):
        # access heights of one "y"
        for hs in height_grid.T:
            for h in hs[1:]:
                solver.addConstraint(hs[0] == h)

    @staticmethod
    def _add_stretch_constraints(solver, width_grid, height_grid, grid_widgets,
                                 widget_grid):
        xmax = len(height_grid)
        ymax = len(width_grid)

        stretch_widths = [[] for _ in range(0, ymax)]
        stretch_heights = [[] for _ in range(0, xmax)]

        for (y, x, ys, xs, widget) in grid_widgets.values():
            for ws in width_grid[y:y + ys]:
                total_w = np.sum(ws[x:x + xs])

                for sw in stretch_widths[y:y + ys]:
                    sw.append((total_w, widget.stretch[0]))

            for hs in height_grid[x:x + xs]:
                total_h = np.sum(hs[y:y + ys])

                for sh in stretch_heights[x:x + xs]:
                    sh.append((total_h, widget.stretch[1]))

        for (x, xs) in enumerate(widget_grid):
            for (y, widget) in enumerate(xs):
                if widget is None:
                    stretch_widths[y].append((width_grid[y][x], 1))
                    stretch_heights[x].append((height_grid[x][y], 1))

        for sws in stretch_widths:
            if len(sws) <= 1:
                continue

            comparator = sws[0][0] / sws[0][1]

            for (stretch_term, stretch_val) in sws[1:]:
                solver.addConstraint((comparator == stretch_term / stretch_val)
                                     | 'weak')

        for sws in stretch_heights:
            if len(sws) <= 1:
                continue

            comparator = sws[0][0] / sws[0][1]

            for (stretch_term, stretch_val) in sws[1:]:
                solver.addConstraint((comparator == stretch_term / stretch_val)
                                     | 'weak')

    @staticmethod
    def _add_widget_dim_constraints(solver, width_grid, height_grid,
                                    total_var_w, total_var_h, grid_widgets):
        assert (total_var_w is not None)
        assert (total_var_h is not None)

        for ws in width_grid:
            for w in ws:
                solver.addConstraint(w >= 0, )

        for hs in height_grid:
            for h in hs:
                solver.addConstraint(h >= 0)

        for (_, val) in grid_widgets.items():
            (y, x, ys, xs, widget) = val

            for ws in width_grid[y:y + ys]:
                total_w = np.sum(ws[x:x + xs])
                # assert(total_w is not None)
                solver.addConstraint(total_w >= widget.width_min)

                if widget.width_max is not None:
                    solver.addConstraint(total_w <= widget.width_max)
                else:
                    solver.addConstraint(total_w <= total_var_w)

            for hs in height_grid[x:x + xs]:
                total_h = np.sum(hs[y:y + ys])
                solver.addConstraint(total_h >= widget.height_min)

                if widget.height_max is not None:
                    solver.addConstraint(total_h <= widget.height_max)
                else:
                    solver.addConstraint(total_h <= total_var_h)

    def _recreate_solver(self):
        self._solver.reset()
        self._var_w = Variable("w_rect")
        self._var_h = Variable("h_rect")
        self._solver.addEditVariable(self._var_w, 'strong')
        self._solver.addEditVariable(self._var_h, 'strong')

        rect = self.rect.padded(self.padding + self.margin)
        ymax, xmax = self.grid_size

        self._solver.suggestValue(self._var_w, rect.width)
        self._solver.suggestValue(self._var_h, rect.height)

        self._solver.addConstraint(self._var_w >= 0)
        self._solver.addConstraint(self._var_h >= 0)

        # self._height_stay = None
        # self._width_stay = None

        # add widths
        self._width_grid = np.array([[
            Variable("width(x: %s, y: %s)" % (x, y)) for x in range(0, xmax)
        ] for y in range(0, ymax)])

        # add heights
        self._height_grid = np.array([[
            Variable("height(x: %s, y: %s" % (x, y)) for y in range(0, ymax)
        ] for x in range(0, xmax)])

        # setup stretch
        stretch_grid = np.zeros(shape=(xmax, ymax, 2), dtype=float)
        stretch_grid.fill(1)

        for (_, val) in self._grid_widgets.items():
            (y, x, ys, xs, widget) = val
            stretch_grid[x:x + xs, y:y + ys] = widget.stretch

        # even though these are REQUIRED, these should never fail
        # since they're added first, and thus the slack will "simply work".
        Grid._add_total_width_constraints(self._solver, self._width_grid,
                                          self._var_w)
        Grid._add_total_height_constraints(self._solver, self._height_grid,
                                           self._var_h)

        try:
            # these are REQUIRED constraints for width and height.
            # These are the constraints which can fail if
            # the corresponding dimension of the widget cannot be fit in the
            # grid.
            Grid._add_gridding_width_constraints(self._solver,
                                                 self._width_grid)
            Grid._add_gridding_height_constraints(self._solver,
                                                  self._height_grid)
        except UnsatisfiableConstraint:
            self._need_solver_recreate = True

        # these are WEAK constraints, so these constraints will never fail
        # with a RequiredFailure.
        Grid._add_stretch_constraints(self._solver, self._width_grid,
                                      self._height_grid, self._grid_widgets,
                                      self._widget_grid)

        Grid._add_widget_dim_constraints(self._solver, self._width_grid,
                                         self._height_grid, self._var_w,
                                         self._var_h, self._grid_widgets)

        self._solver.updateVariables()

    def _update_child_widget_dim(self):
        # think in terms of (x, y). (row, col) makes code harder to read
        ymax, xmax = self.grid_size
        if ymax <= 0 or xmax <= 0:
            return

        rect = self.rect  # .padded(self.padding + self.margin)
        if rect.width <= 0 or rect.height <= 0:
            return
        if self._need_solver_recreate:
            self._need_solver_recreate = False
            self._recreate_solver()

        # we only need to remove and add the height and width constraints of
        # the solver if they are not the same as the current value
        h_changed = abs(rect.height - self._var_h.value()) > 1e-4
        w_changed = abs(rect.width - self._var_w.value()) > 1e-4
        if h_changed:
            self._solver.suggestValue(self._var_h, rect.height)

        if w_changed:
            self._solver.suggestValue(self._var_w, rect.width)
        if h_changed or w_changed:
            self._solver.updateVariables()

        value_vectorized = np.vectorize(lambda x: x.value())

        for (_, val) in self._grid_widgets.items():
            (row, col, rspan, cspan, widget) = val

            width = np.sum(
                value_vectorized(self._width_grid[row][col:col + cspan]))
            height = np.sum(
                value_vectorized(self._height_grid[col][row:row + rspan]))
            if col == 0:
                x = 0
            else:
                x = np.sum(value_vectorized(self._width_grid[row][0:col]))

            if row == 0:
                y = 0
            else:
                y = np.sum(value_vectorized(self._height_grid[col][0:row]))

            if isinstance(widget, ViewBox):
                widget.rect = Rect(x, y, width, height)
            else:
                widget.size = (width, height)
                widget.pos = (x, y)

    @property
    def _widget_grid(self):
        ymax, xmax = self.grid_size
        widget_grid = np.array([[None for _ in range(0, ymax)]
                                for _ in range(0, xmax)])
        for (_, val) in self._grid_widgets.items():
            (y, x, ys, xs, widget) = val
            widget_grid[x:x + xs, y:y + ys] = widget

        return widget_grid