sugar-toolkit-gtk3/src/sugar3/graphics/tray.py
Sam Parkinson 8d0cc98e43 Set css name for widgets
In Gtk+ 3.20, you need to use the css name to select elements,
rather than the gtype name.  Therefore, these must be added.

The css name must be set before the class instances are created, as
it effects the class rather than the instance.  This is why it must
be places after the class definition.
2016-01-01 12:28:42 +11:00

514 lines
17 KiB
Python

# Copyright (C) 2007, One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
STABLE.
"""
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from sugar3.graphics import style
from sugar3.graphics.palette import ToolInvoker
from sugar3.graphics.toolbutton import ToolButton
from sugar3.graphics.icon import Icon
_PREVIOUS_PAGE = 0
_NEXT_PAGE = 1
class _TrayViewport(Gtk.Viewport):
__gproperties__ = {
'scrollable': (bool, None, None, False, GObject.PARAM_READABLE),
'can-scroll-prev': (bool, None, None, False, GObject.PARAM_READABLE),
'can-scroll-next': (bool, None, None, False, GObject.PARAM_READABLE),
}
def __init__(self, orientation):
self.orientation = orientation
self._scrollable = False
self._can_scroll_next = False
self._can_scroll_prev = False
Gtk.Viewport.__init__(self)
self.set_shadow_type(Gtk.ShadowType.NONE)
self.traybar = Gtk.Toolbar()
self.traybar.set_orientation(orientation)
self.traybar.set_show_arrow(False)
self.add(self.traybar)
self.traybar.show()
self.connect('size_allocate', self._size_allocate_cb)
if self.orientation == Gtk.Orientation.HORIZONTAL:
adj = self.get_hadjustment()
else:
adj = self.get_vadjustment()
adj.connect('changed', self._adjustment_changed_cb)
adj.connect('value-changed', self._adjustment_changed_cb)
def scroll(self, direction):
if direction == _PREVIOUS_PAGE:
self._scroll_previous()
elif direction == _NEXT_PAGE:
self._scroll_next()
def scroll_to_item(self, item):
"""This function scrolls the viewport so that item will be visible."""
assert item in self.traybar.get_children()
# Get the allocation, and make sure that it is visible
allocation = item.get_allocation()
if self.orientation == Gtk.Orientation.HORIZONTAL:
adj = self.get_hadjustment()
start = allocation.x
stop = allocation.x + allocation.width
else:
adj = self.get_vadjustment()
start = allocation.y
stop = allocation.y + allocation.height
if start < adj.get_value():
adj.set_value(start)
elif stop > adj.get_value() + adj.get_page_size():
adj.set_value(stop - adj.get_page_size())
def _scroll_next(self):
allocation = self.get_allocation()
if self.orientation == Gtk.Orientation.HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.get_value() + allocation.width
adj.set_value(min(new_value, adj.get_upper() - allocation.width))
else:
adj = self.get_vadjustment()
new_value = adj.get_value() + allocation.height
adj.set_value(min(new_value, adj.get_upper() - allocation.height))
def _scroll_previous(self):
allocation = self.get_allocation()
if self.orientation == Gtk.Orientation.HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.get_value() - allocation.width
adj.set_value(max(adj.get_lower(), new_value))
else:
adj = self.get_vadjustment()
new_value = adj.get_value() - allocation.height
adj.set_value(max(adj.get_lower(), new_value))
def do_get_preferred_width(self):
if self.orientation == Gtk.Orientation.HORIZONTAL:
min_width, nat_width = Gtk.Viewport.do_get_preferred_width(self)
return 0, nat_width
child_minimum, child_natural = self.get_child().get_preferred_size()
return child_minimum.width, child_natural.width
def do_get_preferred_height(self):
if self.orientation != Gtk.Orientation.HORIZONTAL:
min_height, nat_height = Gtk.Viewport.do_get_preferred_height(self)
return 0, nat_height
child_minimum, child_natural = self.get_child().get_preferred_size()
return child_minimum.height, child_natural.height
def do_get_property(self, pspec):
if pspec.name == 'scrollable':
return self._scrollable
elif pspec.name == 'can-scroll-next':
return self._can_scroll_next
elif pspec.name == 'can-scroll-prev':
return self._can_scroll_prev
def _size_allocate_cb(self, viewport, allocation):
if allocation.width == 1 and allocation.height == 1:
# HACK: the first time this callback is called 'width' and
# 'height' are 1 so we mark the Viewport as scrollable and
# we show the Prev / Next buttons
return
bar_minimum, bar_natural = self.traybar.get_preferred_size()
if self.orientation == Gtk.Orientation.HORIZONTAL:
scrollable = bar_minimum.width > allocation.width
else:
scrollable = bar_minimum.height > allocation.height
if scrollable != self._scrollable:
self._scrollable = scrollable
self.notify('scrollable')
def _adjustment_changed_cb(self, adjustment):
if adjustment.get_value() <= adjustment.get_lower():
can_scroll_prev = False
else:
can_scroll_prev = True
if adjustment.get_value() + adjustment.get_page_size() >= \
adjustment.get_upper():
can_scroll_next = False
else:
can_scroll_next = True
if can_scroll_prev != self._can_scroll_prev:
self._can_scroll_prev = can_scroll_prev
self.notify('can-scroll-prev')
if can_scroll_next != self._can_scroll_next:
self._can_scroll_next = can_scroll_next
self.notify('can-scroll-next')
class _TrayScrollButton(ToolButton):
__gtype_name__ = 'SugarTrayScrollButton'
def __init__(self, icon_name, scroll_direction):
ToolButton.__init__(self)
self._viewport = None
self._scroll_direction = scroll_direction
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
self.icon = Icon(icon_name=icon_name,
pixel_size=style.SMALL_ICON_SIZE)
# The alignment is a hack to work around Gtk.ToolButton code
# that sets the icon_size when the icon_widget is a Gtk.Image
alignment = Gtk.Alignment(xalign=0.5, yalign=0.5)
alignment.add(self.icon)
self.set_icon_widget(alignment)
alignment.show_all()
self.connect('clicked', self._clicked_cb)
def set_viewport(self, viewport):
self._viewport = viewport
self._viewport.connect('notify::scrollable',
self._viewport_scrollable_changed_cb)
if self._scroll_direction == _PREVIOUS_PAGE:
self._viewport.connect('notify::can-scroll-prev',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_prev)
else:
self._viewport.connect('notify::can-scroll-next',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_next)
def _viewport_scrollable_changed_cb(self, viewport, pspec):
self.props.visible = self._viewport.props.scrollable
def _viewport_can_scroll_dir_changed_cb(self, viewport, pspec):
if self._scroll_direction == _PREVIOUS_PAGE:
sensitive = self._viewport.props.can_scroll_prev
else:
sensitive = self._viewport.props.can_scroll_next
self.set_sensitive(sensitive)
def _clicked_cb(self, button):
self._viewport.scroll(self._scroll_direction)
viewport = property(fset=set_viewport)
ALIGN_TO_START = 0
ALIGN_TO_END = 1
class HTray(Gtk.EventBox):
__gtype_name__ = 'SugarHTray'
__gproperties__ = {
'align': (int, None, None, 0, 1, ALIGN_TO_START,
GObject.PARAM_READWRITE |
GObject.PARAM_CONSTRUCT_ONLY),
'drag-active': (bool, None, None, False, GObject.PARAM_READWRITE),
}
def __init__(self, **kwargs):
self._drag_active = False
self.align = ALIGN_TO_START
Gtk.EventBox.__init__(self, **kwargs)
self._box = Gtk.HBox()
self.add(self._box)
self._box.show()
scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
self._box.pack_start(scroll_left, False, False, 0)
self._viewport = _TrayViewport(Gtk.Orientation.HORIZONTAL)
self._box.pack_start(self._viewport, True, True, 0)
self._viewport.show()
scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
self._box.pack_start(scroll_right, False, False, 0)
scroll_left.viewport = self._viewport
scroll_right.viewport = self._viewport
if self.align == ALIGN_TO_END:
spacer = Gtk.SeparatorToolItem()
spacer.set_size_request(0, 0)
spacer.props.draw = False
spacer.set_expand(True)
self._viewport.traybar.insert(spacer, 0)
spacer.show()
def do_set_property(self, pspec, value):
if pspec.name == 'align':
self.align = value
elif pspec.name == 'drag-active':
self._set_drag_active(value)
else:
raise AssertionError
def do_get_property(self, pspec):
if pspec.name == 'align':
return self.align
elif pspec.name == 'drag-active':
return self._drag_active
else:
raise AssertionError
def _set_drag_active(self, active):
if self._drag_active != active:
self._drag_active = active
if self._drag_active:
self._viewport.traybar.modify_bg(
Gtk.StateType.NORMAL,
style.COLOR_BLACK.get_gdk_color())
else:
self._viewport.traybar.modify_bg(Gtk.StateType.NORMAL, None)
def get_children(self):
children = self._viewport.traybar.get_children()[:]
if self.align == ALIGN_TO_END:
children = children[1:]
return children
def add_item(self, item, index=-1):
if self.align == ALIGN_TO_END and index > -1:
index += 1
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
index = self._viewport.traybar.get_item_index(item)
if self.align == ALIGN_TO_END:
index -= 1
return index
def scroll_to_item(self, item):
self._viewport.scroll_to_item(item)
if hasattr(HTray, 'set_css_name'):
HTray.set_css_name('htray')
class VTray(Gtk.EventBox):
__gtype_name__ = 'SugarVTray'
__gproperties__ = {
'align': (int, None, None, 0, 1, ALIGN_TO_START,
GObject.PARAM_READWRITE | GObject.PARAM_CONSTRUCT_ONLY),
'drag-active': (bool, None, None, False, GObject.PARAM_READWRITE),
}
def __init__(self, **kwargs):
self._drag_active = False
self.align = ALIGN_TO_START
Gtk.EventBox.__init__(self, **kwargs)
self._box = Gtk.VBox()
self.add(self._box)
self._box.show()
scroll_up = _TrayScrollButton('go-up', _PREVIOUS_PAGE)
self._box.pack_start(scroll_up, False, False, 0)
self._viewport = _TrayViewport(Gtk.Orientation.VERTICAL)
self._box.pack_start(self._viewport, True, True, 0)
self._viewport.show()
scroll_down = _TrayScrollButton('go-down', _NEXT_PAGE)
self._box.pack_start(scroll_down, False, False, 0)
scroll_up.viewport = self._viewport
scroll_down.viewport = self._viewport
if self.align == ALIGN_TO_END:
spacer = Gtk.SeparatorToolItem()
spacer.set_size_request(0, 0)
spacer.props.draw = False
spacer.set_expand(True)
self._viewport.traybar.insert(spacer, 0)
spacer.show()
def do_set_property(self, pspec, value):
if pspec.name == 'align':
self.align = value
elif pspec.name == 'drag-active':
self._set_drag_active(value)
else:
raise AssertionError
def do_get_property(self, pspec):
if pspec.name == 'align':
return self.align
elif pspec.name == 'drag-active':
return self._drag_active
else:
raise AssertionError
def _set_drag_active(self, active):
if self._drag_active != active:
self._drag_active = active
if self._drag_active:
self._viewport.traybar.modify_bg(
Gtk.StateType.NORMAL,
style.COLOR_BLACK.get_gdk_color())
else:
self._viewport.traybar.modify_bg(Gtk.StateType.NORMAL, None)
def get_children(self):
children = self._viewport.traybar.get_children()[:]
if self.align == ALIGN_TO_END:
children = children[1:]
return children
def add_item(self, item, index=-1):
if self.align == ALIGN_TO_END and index > -1:
index += 1
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
index = self._viewport.traybar.get_item_index(item)
if self.align == ALIGN_TO_END:
index -= 1
return index
def scroll_to_item(self, item):
self._viewport.scroll_to_item(item)
if hasattr(VTray, 'set_css_name'):
VTray.set_css_name('VTray')
class TrayButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, **kwargs)
class _IconWidget(Gtk.EventBox):
__gtype_name__ = 'SugarTrayIconWidget'
def __init__(self, icon_name=None, xo_color=None):
Gtk.EventBox.__init__(self)
self.set_app_paintable(True)
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.TOUCH_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK)
self._icon = Icon(icon_name=icon_name, xo_color=xo_color,
pixel_size=style.STANDARD_ICON_SIZE)
self.add(self._icon)
self._icon.show()
def do_draw(self, cr):
palette = self.get_parent().palette
if palette and 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.EventBox.do_draw(self, cr)
if palette and palette.is_up():
invoker = palette.props.invoker
invoker.draw_rectangle(cr, palette)
return False
def get_icon(self):
return self._icon
class TrayIcon(Gtk.ToolItem):
__gtype_name__ = 'SugarTrayIcon'
def __init__(self, icon_name=None, xo_color=None):
Gtk.ToolItem.__init__(self)
self._icon_widget = _IconWidget(icon_name, xo_color)
self.add(self._icon_widget)
self._icon_widget.show()
self._palette_invoker = ToolInvoker(self)
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
self.connect('destroy', self.__destroy_cb)
def __destroy_cb(self, icon):
if self._palette_invoker is not None:
self._palette_invoker.detach()
def create_palette(self):
return None
def get_palette(self):
return self._palette_invoker.palette
def set_palette(self, palette):
self._palette_invoker.palette = palette
palette = GObject.property(
type=object, setter=set_palette, getter=get_palette)
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = GObject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def get_icon(self):
return self._icon_widget.get_icon()
icon = property(get_icon, None)