Mimic the behaviour and style of the Sugar GTK+ 2 toolbutton palettes in GTK+ 3

First we needed to port the Palette code to use a minimum size. The default size
is two times the GRID_CELL_SIZE. Since the request-phase of the traditional GTK+
geometry management has been replaced by a height-for-width system [1] we have
to compensate for that. Furthermore we need to pass the invoker from the
PaletteWindow to the _PaletteWindowWidget for the gap calculation code for
drawing the border around the Palette.

We do the drawing of the border for the toolbutton in the base class, moved
this from the ToolbarButton and made sure we are drawing in the right order.
In the ToolButton we draw as well a black background for the ToolButton when
the Palette is up. While the mouse is over the button we can do that in the
theme, but not when the mouse moves over the Palette.

[1] http://developer.gnome.org/gtk3/3.0/ch25s02.html#id1525688

Signed-off-by: Simon Schampijer <simon@laptop.org>
This commit is contained in:
Simon Schampijer 2012-03-15 17:56:16 +01:00
parent e04043dc0b
commit 01a06943a2
3 changed files with 86 additions and 59 deletions

View File

@ -98,16 +98,15 @@ class _PaletteMenuWidget(Gtk.Menu):
res_, x, y = self.get_toplevel().get_window().get_origin()
return x, y
def do_size_request(self, requisition):
Gtk.Window.do_size_request(self, requisition)
requisition.width = max(requisition.width, style.GRID_CELL_SIZE * 2)
def move(self, x, y):
self._popup_position = (x, y)
def set_transient_for(self, window):
pass
def set_invoker(self, invoker):
pass
def _position(self, widget, data):
return self._popup_position[0], self._popup_position[1], False
@ -252,7 +251,7 @@ class _PaletteWindowWidget(Gtk.Window):
self.add_accel_group(accel_group)
self._old_alloc = None
self._invoker = None
self._should_accept_focus = True
def set_accept_focus(self, focus):
@ -270,9 +269,13 @@ class _PaletteWindowWidget(Gtk.Window):
self.get_window().set_accept_focus(self._should_accept_focus)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
def do_size_request(self, requisition):
Gtk.Window.do_size_request(self, requisition)
requisition.width = max(requisition.width, style.GRID_CELL_SIZE * 2)
def do_get_preferred_width(self):
size = 0
child = self.get_child()
if child:
minimum_size, natural_size = child.get_preferred_width()
size = max(minimum_size, natural_size, style.GRID_CELL_SIZE * 2)
return size, size
def do_size_allocate(self, allocation):
Gtk.Window.do_size_allocate(self, allocation)
@ -291,6 +294,46 @@ class _PaletteWindowWidget(Gtk.Window):
# the configure request handler and finally size_allocate is called.
self._old_alloc = allocation
def set_invoker(self, invoker):
self._invoker = invoker
def get_rect(self):
win_x, win_y = self.get_origin()
rectangle = self.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
minimum, natural = self.get_preferred_size()
rect = Gdk.Rectangle()
rect.x = x
rect.y = y
rect.width = minimum.width
rect.height = natural.height
return rect
def do_draw(self, cr):
# Fall trough to the container expose handler.
# (Leaving out the window expose handler which redraws everything)
Gtk.Window.do_draw(self, cr)
if self._invoker is not None and self._invoker.has_rectangle_gap():
invoker = self._invoker.get_rect()
palette = self.get_rect()
gap = _calculate_gap(palette, invoker)
else:
gap = False
allocation = self.get_allocation()
context = self.get_style_context()
context.add_class('toolitem')
if gap:
Gtk.render_frame_gap(context, cr, 0, 0, allocation.width, allocation.height,
gap[0], gap[1], gap[2])
else:
Gtk.render_frame(context, cr, 0, 0, allocation.width, allocation.height)
return False
def __enter_notify_event_cb(self, widget, event):
if event.mode == Gdk.CrossingMode.NORMAL and \
event.detail != Gdk.NotifyType.INFERIOR:
@ -427,6 +470,7 @@ class PaletteWindow(GObject.GObject):
self._widget.connect('leave-notify', self.__leave_notify_cb)
self._set_effective_group_id(self._group_id)
self._widget.set_invoker(self._invoker)
self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
self._mouse_detector.parent = self._widget
@ -452,6 +496,7 @@ class PaletteWindow(GObject.GObject):
self._invoker_hids.remove(hid)
self._invoker = invoker
self._widget.set_invoker(self._invoker)
if invoker is not None:
self._invoker_hids.append(self._invoker.connect(
'mouse-enter', self._invoker_mouse_enter_cb))
@ -599,17 +644,17 @@ class PaletteWindow(GObject.GObject):
def get_rect(self):
win_x, win_y = self._widget.get_origin()
rectangle = self.get_allocation()
rectangle = self._widget.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
requisition = self._widget.size_request()
minimum, natural_ = self._widget.get_preferred_size()
rect = Gdk.Rectangle()
rect.x = x
rect.y = y
rect.width = requisition.width
rect.height = requisition.height
rect.width = minimum.width
rect.height = minimum.height
return rect
@ -967,25 +1012,18 @@ class WidgetInvoker(Invoker):
def has_rectangle_gap(self):
return True
def draw_rectangle(self, event, palette):
x, y = self._widget.allocation.x, self._widget.allocation.y
def draw_rectangle(self, cr, palette):
allocation = self.parent.get_allocation()
context = self.parent.get_style_context()
context.add_class('toolitem')
wstyle = self._widget.get_style()
gap = _calculate_gap(self.get_rect(), palette.get_rect())
if gap:
wstyle.paint_box_gap(event.window, Gtk.StateType.PRELIGHT,
Gtk.ShadowType.IN, event.area, self._widget,
'palette-invoker', x, y,
self._widget.allocation.width,
self._widget.allocation.height,
Gtk.render_frame_gap(context, cr, 0, 0,
allocation.width,
allocation.height,
gap[0], gap[1], gap[2])
else:
wstyle.paint_box(event.window, Gtk.StateType.PRELIGHT,
Gtk.ShadowType.IN, event.area, self._widget,
'palette-invoker', x, y,
self._widget.allocation.width,
self._widget.allocation.height)
def __enter_notify_event_cb(self, widget, event):
self.notify_mouse_enter()

View File

@ -38,7 +38,7 @@ class ToolbarButton(ToolButton):
self.connect('clicked',
lambda widget: self.set_expanded(not self.is_expanded()))
self.connect_after('draw', self.__drawing_cb)
self.connect('hierarchy-changed', self.__hierarchy_changed_cb)
def __hierarchy_changed_cb(self, tool_button, previous_toplevel):
@ -125,32 +125,19 @@ class ToolbarButton(ToolButton):
return
page_parent.remove(self.page_widget)
def do_draw(self, cr):
def __drawing_cb(self, button, cr):
alloc = self.get_allocation()
context = self.get_style_context()
context.add_class('toolitem')
arrow_direction = 0
if not self.is_expanded() or self.props.palette is not None and \
self.props.palette.is_up():
arrow_direction = math.pi
if not self.is_expanded() and self.props.palette is not None and \
self.props.palette.is_up():
# draw a black background
cr.set_source_rgba(*style.COLOR_BLACK.get_rgba())
cr.rectangle(0, 0, alloc.width, alloc.height)
cr.fill()
Gtk.ToolButton.do_draw(self, cr)
if self.is_expanded() or self.props.palette is not None and \
self.props.palette.is_up():
ToolButton.do_draw(self, cr)
_paint_arrow(self, cr, math.pi)
return False
Gtk.render_frame_gap(context, cr, 0, 0, alloc.width, alloc.height,
Gtk.PositionType.BOTTOM, 0, alloc.width)
_paint_arrow(self, cr, arrow_direction)
_paint_arrow(self, cr, 0)
return False
class ToolbarBox(Gtk.VBox):

View File

@ -142,20 +142,22 @@ class ToolButton(Gtk.ToolButton):
palette_invoker = GObject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def do_expose_event(self, event):
def do_draw(self, cr):
child = self.get_child()
if self.palette and self.palette.is_up():
allocation = self.get_allocation()
# draw a black background, has been done by the engine before
cr.set_source_rgb(0, 0, 0)
cr.rectangle(0, 0, allocation.width, allocation.height)
cr.paint()
Gtk.ToolButton.do_draw(self, cr)
if self.palette and self.palette.is_up():
invoker = self.palette.props.invoker
invoker.draw_rectangle(event, self.palette)
elif child.state == Gtk.StateType.PRELIGHT:
child.style.paint_box(event.window, Gtk.StateType.PRELIGHT,
Gtk.ShadowType.NONE, event.area,
child, 'toolbutton-prelight',
allocation.x, allocation.y,
allocation.width, allocation.height)
invoker.draw_rectangle(cr, self.palette)
Gtk.ToolButton.do_expose_event(self, event)
return False
def do_clicked(self):
if self.palette: