# 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)
