<reponame>emmilinuxorg/emmi-aplicativos<filename>usr/lib/tuquito/tuquito-software-manager/widgets/pathbar2.py
# Copyright (C) 2009 <NAME>
#
# Authors:
#   <NAME>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import rgb
import gtk
import cairo
import pango
import gobject

from rgb import to_float as f

# pi constants
M_PI = 3.1415926535897931
PI_OVER_180 = 0.017453292519943295


class PathBar(gtk.DrawingArea):

    # shapes
    SHAPE_RECTANGLE = 0
    SHAPE_START_ARROW = 1
    SHAPE_MID_ARROW = 2
    SHAPE_END_CAP = 3

    def __init__(self, group=None):
        gtk.DrawingArea.__init__(self)
        self.__init_drawing()
        self.set_redraw_on_allocate(False)

        self.__parts = []
        self.__active_part = None
        self.__focal_part = None
        self.__button_down = False

        self.__scroller = None
        self.__scroll_xO = 0

        self.theme = self.__pick_theme()

        # setup event handling
        self.set_flags(gtk.CAN_FOCUS)
        self.set_events(gtk.gdk.POINTER_MOTION_MASK|
                        gtk.gdk.BUTTON_PRESS_MASK|
                        gtk.gdk.BUTTON_RELEASE_MASK|
                        gtk.gdk.KEY_RELEASE_MASK|
                        gtk.gdk.KEY_PRESS_MASK|
                        gtk.gdk.LEAVE_NOTIFY_MASK)

        self.connect("motion-notify-event", self.__motion_notify_cb)
        self.connect("leave-notify-event", self.__leave_notify_cb)
        self.connect("button-press-event", self.__button_press_cb)
        self.connect("button-release-event", self.__button_release_cb)
#        self.connect("key-release-event", self.__key_release_cb)

        self.connect("realize", self.__realize_cb)
        self.connect("expose-event", self.__expose_cb)
        self.connect("style-set", self.__style_change_cb)
        self.connect("size-allocate", self.__allocation_change_cb)
	self.last_label = None
        return

    def set_active(self, part):	
        part.set_state(gtk.STATE_ACTIVE)
        prev, redraw = self.__set_active(part)
        if redraw:
            self.queue_draw_area(*prev.get_allocation_tuple())
            self.queue_draw_area(*part.get_allocation_tuple())
	self.last_label = None
        return

    def get_active(self):
        return self.__active_part

#    def get_left_part(self):
#        active = self.get_active()
#        if not active:
#            return self.__parts[0]

#        i = self.__parts.index(active)+1
#        if i > len(self.__parts)-1:
#            i = 0
#        return self.__parts[i]

#    def get_right_part(self):
#        active = self.get_active()
#        if not active:
#            return self.__parts[0]

#        i = self.__parts.index(active)-1
#        if i < 0:
#            i = len(self.__parts)-1
#        return self.__parts[i]

    def append(self, part):
        prev, did_shrink = self.__append(part)
        if not self.get_property("visible"):
            return False

        if self.theme.animate and len(self.__parts) > 1:
            aw = self.theme.arrow_width

            # calc draw_area
            x,y,w,h = part.get_allocation_tuple()
            w += aw

            # begin scroll animation
            self.__hscroll_out_init(
                part.get_width(),
                gtk.gdk.Rectangle(x,y,w,h),
                self.theme.scroll_duration_ms,
                self.theme.scroll_fps
                )
        else:
            self.queue_draw_area(*part.get_allocation_tuple())
        return False

    def remove(self, part):
        if len(self.__parts)-1 < 1:
            #print 'The first part is sacred ;)'
            return

        old_w = self.__draw_width()

        # remove part from interal part list
	try:
	        del self.__parts[self.__parts.index(part)]
	except:
		pass

        self.__compose_parts(self.__parts[-1], False)

        if old_w >= self.allocation.width:
            self.__grow_check(old_w, self.allocation)
            self.queue_draw()

        else:
            self.queue_draw_area(*part.get_allocation_tuple())
            self.queue_draw_area(*self.__parts[-1].get_allocation_tuple())
        return

    def __set_active(self, part):

	bigger = False
	for i in self.id_to_part:	    
            apart = self.id_to_part[i]
	    if bigger:
		self.remove(apart)
	    if apart == part:
		bigger = True

        prev_active = self.__active_part
        redraw = False
        if part.callback:
            part.callback(self, part.obj)
        if prev_active and prev_active != part:
            prev_active.set_state(gtk.STATE_NORMAL)
            redraw = True

        self.__active_part = part
        return prev_active, redraw

    def __append(self, part):
        # clean up any exisitng scroll callbacks
        if self.__scroller:
            gobject.source_remove(self.__scroller)
        self.__scroll_xO = 0

        # the basics
        x = self.__draw_width()
        self.__parts.append(part)
        part.set_pathbar(self)

        prev_active = self.set_active(part)

        # determin part shapes, and calc modified parts widths
        prev = self.__compose_parts(part, True)
        # set the position of new part
        part.set_x(x)

        # check parts fit to widgets allocated width
        if x + part.get_width() > self.allocation.width  and \
            self.allocation.width != 1:
            self.__shrink_check(self.allocation)
            return prev, True

        return prev, False

#    def __shorten(self, n):
#        n = int(n)
#        old_w = self.__draw_width()
#        end_active = self.get_active() == self.__parts[-1]

#        if len(self.__parts)-n < 1:
#            print WARNING + 'The first part is sacred ;)' + ENDC
#            return old_w, False

#        del self.__parts[-n:]
#        self.__compose_parts(self.__parts[-1], False)

#        if end_active:
#            self.set_active(self.__parts[-1])

#        if old_w >= self.allocation.width:
#            self.__grow_check(old_w, self.allocation)
#            return old_w, True

#        return old_w, False

    def __shrink_check(self, allocation):
        path_w = self.__draw_width()
        shrinkage = path_w - allocation.width
        mpw = self.theme.min_part_width
        xO = 0

        for part in self.__parts[:-1]:
            w = part.get_width()
            dw = 0

            if w - shrinkage <= mpw:
                dw = w - mpw
                shrinkage -= dw
                part.set_size(mpw, -1)
                part.set_x(part.get_x() - xO)

            else:
                part.set_size(w - shrinkage, -1)
                part.set_x(part.get_x() - xO)
                dw = shrinkage
                shrinkage = 0

            xO += dw

        last = self.__parts[-1]
        last.set_x(last.get_x() - xO)
        return

    def __grow_check(self, old_width, allocation):
        parts = self.__parts
        if len(parts) == 0:
            return

        growth = old_width - self.__draw_width()
        parts.reverse()

        for part in parts:
            bw = part.get_size_requisition()[0]
            w = part.get_width()

            if w < bw:
                dw = bw - w

                if dw <= growth:
                    growth -= dw
                    part.set_size(bw, -1)
                    part.set_x(part.get_x() + growth)

                else:
                    part.set_size(w + growth, -1)
                    growth = 0

            else:
                part.set_x(part.get_x() + growth)

        parts.reverse()
        shift =  parts[0].get_x()

        # left align parts
        if shift > 0:
            for part in parts: part.set_x(part.get_x() - shift)
        return

    def __compose_parts(self, last, prev_set_size):
        parts = self.__parts

        if len(parts) == 1:
            last.set_shape(self.SHAPE_RECTANGLE)
            last.set_size(*last.calc_size_requisition())
            prev = None

        elif len(parts) == 2:
            prev = parts[0]
            prev.set_shape(self.SHAPE_START_ARROW)
            prev.calc_size_requisition()

            last.set_shape(self.SHAPE_END_CAP)
            last.set_size(*last.calc_size_requisition())

        else:
            prev = parts[-2]
            prev.set_shape(self.SHAPE_MID_ARROW)
            prev.calc_size_requisition()

            last.set_shape(self.SHAPE_END_CAP)
            last.set_size(*last.calc_size_requisition())

        if prev and prev_set_size:
            prev.set_size(*prev.get_size_requisition())
        return prev

    def __draw_width(self):
        l = len(self.__parts)
        if l == 0:
            return 0
        a = self.__parts[-1].allocation
        return a[0] + a[2]

    def __hscroll_out_init(self, distance, draw_area, duration, fps):
        self.__scroller = gobject.timeout_add(
            int(1000.0 / fps),  # interval
            self.__hscroll_out_cb,
            distance,
            duration*0.001,   # 1 over duration (converted to seconds)
            gobject.get_current_time(),
            draw_area.x,
            draw_area.y,
            draw_area.width,
            draw_area.height)
        return

    def __hscroll_out_cb(self, distance, duration, start_t, x, y, w, h):
        cur_t = gobject.get_current_time()
        xO = distance - distance*((cur_t - start_t) / duration)

        if xO > 0:
            self.__scroll_xO = xO
            self.queue_draw_area(x, y, w, h)
        else:   # final frame
            self.__scroll_xO = 0
            # redraw the entire widget
            # incase some timeouts are skipped due to high system load
            self.queue_draw()
            self.__scroller = None
            return False
        return True

    def __part_at_xy(self, x, y):
        for part in self.__parts:
            a = part.get_allocation()
            region = gtk.gdk.region_rectangle(a)

            if region.point_in(int(x), int(y)):
                return part
        return None

    def __draw_hscroll(self, cr):
        if len(self.__parts) < 2:
            return

        # draw the last two parts
        prev, last = self.__parts[-2:]

        # style theme stuff
        style, r, aw, shapes = self.style, self.theme.curvature, \
            self.theme.arrow_width, self.__shapes

        # draw part that need scrolling
        self.__draw_part(cr,
                         last,
                         style,
                         r,
                         aw,
                         shapes,
                         self.__scroll_xO)

        # draw the last part that does not scroll
        self.__draw_part(cr,
                         prev,
                         style,
                         r,
                         aw,
                         shapes)
        return

    def __draw_all(self, cr, event_area):
        style = self.style
        r = self.theme.curvature
        aw = self.theme.arrow_width
        shapes = self.__shapes
        region = gtk.gdk.region_rectangle(event_area)

        # if a scroll is pending we want to not draw the final part,
        # as we don't want to prematurely reveal the part befor the
        # scroll animation has had a chance to start
        if self.__scroller:
            parts = self.__parts[:-1]
        else:
            parts = self.__parts

        parts.reverse()
        for part in parts:
            if region.rect_in(part.get_allocation()) != gtk.gdk.OVERLAP_RECTANGLE_OUT:
                self.__draw_part(cr, part, style, r, aw, shapes)
        parts.reverse()
        return

    def __draw_part_ltr(self, cr, part, style, r, aw, shapes, sxO=0):
        x, y, w, h = part.get_allocation()
        shape = part.shape
        state = part.state
        icon_pb = part.icon.pixbuf

        cr.save()
        cr.translate(x-sxO, y)

        # draw bg
        self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)

        # determine left margin.  left margin depends on part shape
        # and whether there exists an icon or not
        if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
            margin = int(0.75*self.theme.arrow_width + self.theme.xpadding)
        else:
            margin = self.theme.xpadding

        # draw icon
        if icon_pb:
            cr.set_source_pixbuf(
                icon_pb,
                self.theme.xpadding-sxO,
                (alloc.height - icon_pb.get_height())/2)
            cr.paint()
            margin += icon_pb.get_width() + self.theme.spacing

        # if space is limited and an icon is set, dont draw label
        # otherwise, draw label
        if w == self.theme.min_part_width and icon_pb:
            pass

        else:
            layout = part.get_layout()
            lw, lh = layout.get_pixel_size()
            dst_x = x + margin - int(sxO)
            dst_y = (self.allocation.height - lh)/2+1
            style.paint_layout(
                self.window,
                self.theme.text_state[state],
                False,
                (dst_x, dst_y, lw+4, lh),   # clip area
                self,
                None,
                dst_x,
                dst_y,
                layout)

        cr.restore()
        return

    def __draw_part_rtl(self, cr, part, style, r, aw, shapes, sxO=0):
        x, y, w, h = part.get_allocation()
        shape = part.shape
        state = part.state
        icon_pb = part.icon.pixbuf

        cr.save()
        cr.translate(x+sxO, y)

        # draw bg
        self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)

        # determine left margin.  left margin depends on part shape
        # and whether there exists an icon or not
        if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
            margin = self.theme.arrow_width + self.theme.xpadding
        else:
            margin = self.theme.xpadding

        # draw icon
        if icon_pb:
            margin += icon_pb.get_width()
            cr.set_source_pixbuf(
                icon_pb,
                w - margin + sxO,
                (h - icon_pb.get_height())/2)
            cr.paint()
            margin += self.spacing

        # if space is limited and an icon is set, dont draw label
        # otherwise, draw label
        if w == self.theme.min_part_width and icon_pb:
            pass

        else:
            layout = part.get_layout()
            lw, lh = layout.get_pixel_size()
            dst_x = x + part.get_width() - margin - lw + int(sxO)
            dst_y = (self.allocation.height - lh)/2+1
            style.paint_layout(
                self.window,
                self.theme.text_state[state],
                False,
                None,
                self,
                None,
                dst_x,
                dst_y,
                layout)

        cr.restore()
        return

    def __draw_part_bg(self, cr, part, w, h, state, shape, style, r, aw, shapes):
        # outer slight bevel or focal highlight
        shapes[shape](cr, 0, 0, w, h, r, aw)
        cr.set_source_rgba(0, 0, 0, 0.055)
        cr.fill()

        # colour scheme dicts
        bg = self.theme.bg_colors
        outer = self.theme.dark_line_colors
        inner = self.theme.light_line_colors

        # bg linear vertical gradient
        if state != gtk.STATE_PRELIGHT:
            color1, color2 = bg[state]
        else:
            if part != self.get_active():
                color1, color2 = bg[self.theme.PRELIT_NORMAL]
            else:
                color1, color2 = bg[self.theme.PRELIT_ACTIVE]

        shapes[shape](cr, 1, 1, w-1, h-1, r, aw)
        lin = cairo.LinearGradient(0, 0, 0, h-1)
        lin.add_color_stop_rgb(0.0, *color1)
        lin.add_color_stop_rgb(1.0, *color2)
        cr.set_source(lin)
        cr.fill()

        cr.set_line_width(1.0)
        # strong outline
        shapes[shape](cr, 1.5, 1.5, w-1.5, h-1.5, r, aw)
        cr.set_source_rgb(*outer[state])
        cr.stroke()

        # inner bevel/highlight
        if self.theme.light_line_colors[state]:
            shapes[shape](cr, 2.5, 2.5, w-2.5, h-2.5, r, aw)
            r, g, b = inner[state]
            cr.set_source_rgba(r, g, b, 0.6)
            cr.stroke()
        return

    def __shape_rect(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __shape_start_arrow_ltr(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        # arrow head
        cr.line_to(w-aw+1, y)
        cr.line_to(w, (h+y)*0.5)
        cr.line_to(w-aw+1, h)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __shape_mid_arrow_ltr(self, cr, x, y, w, h, r, aw):
        cr.move_to(-1, y)
        # arrow head
        cr.line_to(w-aw+1, y)
        cr.line_to(w, (h+y)*0.5)
        cr.line_to(w-aw+1, h)
        cr.line_to(-1, h)
        cr.close_path()
        return

    def __shape_end_cap_ltr(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.move_to(-1, y)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.line_to(-1, h)
        cr.close_path()
        return

    def __shape_start_arrow_rtl(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.move_to(x, (h+y)*0.5)
        cr.line_to(aw-1, y)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.line_to(aw-1, h)
        cr.close_path()
        return

    def __shape_mid_arrow_rtl(self, cr, x, y, w, h, r, aw):
        cr.move_to(x, (h+y)*0.5)
        cr.line_to(aw-1, y)
        cr.line_to(w+1, y)
        cr.line_to(w+1, h)
        cr.line_to(aw-1, h)
        cr.close_path()
        return

    def __shape_end_cap_rtl(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        cr.line_to(w+1, y)
        cr.line_to(w+1, h)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __state(self, part):
        # returns the idle state of the part depending on
        # whether part is active or not.
        if part == self.__active_part:
            return gtk.STATE_ACTIVE
        return gtk.STATE_NORMAL

    def __tooltip_check(self, part):
        # only show a tooltip if part is truncated, i.e. not all label text is
        # visible.
        if part.is_truncated():
            self.set_has_tooltip(False)
            gobject.timeout_add(50, self.__set_tooltip_cb, part.label)
        else:
            self.set_has_tooltip(False)
        return

    def __set_tooltip_cb(self, text):
        # callback allows the tooltip position to be updated as pointer moves
        # accross different parts
        self.set_has_tooltip(True)
        self.set_tooltip_markup(text)
        return False

    def __pick_theme(self, name=None):
        name = name or gtk.settings_get_default().get_property("gtk-theme-name")
        themes = PathBarThemes.DICT
        if themes.has_key(name):
            return themes[name]()
        #print "No styling hints for %s are available" % name
        return PathBarThemeHuman()

    def __init_drawing(self):
        if self.get_direction() != gtk.TEXT_DIR_RTL:
            self.__draw_part = self.__draw_part_ltr
            self.__shapes = {
                self.SHAPE_RECTANGLE : self.__shape_rect,
                self.SHAPE_START_ARROW : self.__shape_start_arrow_ltr,
                self.SHAPE_MID_ARROW : self.__shape_mid_arrow_ltr,
                self.SHAPE_END_CAP : self.__shape_end_cap_ltr}
        else:
            self.__draw_part = self.__draw_part_rtl
            self.__shapes = {
                self.SHAPE_RECTANGLE : self.__shape_rect,
                self.SHAPE_START_ARROW : self.__shape_start_arrow_rtl,
                self.SHAPE_MID_ARROW : self.__shape_mid_arrow_rtl,
                self.SHAPE_END_CAP : self.__shape_end_cap_rtl}
        return

    def __motion_notify_cb(self, widget, event):
        if self.__scroll_xO > 0:
            return

        part = self.__part_at_xy(event.x, event.y)
        prev_focal = self.__focal_part

        if self.__button_down:
            if prev_focal and part != prev_focal:
                prev_focal.set_state(self.__state(prev_focal))
                self.queue_draw_area(*prev_focal.get_allocation_tuple())
            return

        self.__button_down = False
        if part and part.state != gtk.STATE_PRELIGHT:
            self.__tooltip_check(part)
            part.set_state(gtk.STATE_PRELIGHT)

            if prev_focal:
                prev_focal.set_state(self.__state(prev_focal))
                self.queue_draw_area(*prev_focal.get_allocation_tuple())

            self.__focal_part = part
            self.queue_draw_area(*part.get_allocation_tuple())

        elif not part and prev_focal != None:
            prev_focal.set_state(self.__state(prev_focal))
            self.queue_draw_area(*prev_focal.get_allocation_tuple())
            self.__focal_part = None
        return

    def __leave_notify_cb(self, widget, event):
        self.__button_down = False
        prev_focal = self.__focal_part
        if prev_focal:
            prev_focal.set_state(self.__state(prev_focal))
            self.queue_draw_area(*prev_focal.get_allocation_tuple())
        self.__focal_part = None
        return

    def __button_press_cb(self, widget, event):
        self.__button_down = True
        part = self.__part_at_xy(event.x, event.y)
        if part:
            part.set_state(gtk.STATE_SELECTED)
            self.queue_draw_area(*part.get_allocation_tuple())
        return

    def __button_release_cb(self, widget, event):
        part = self.__part_at_xy(event.x, event.y)

        if self.__focal_part and self.__focal_part != part:
            pass
        elif part and self.__button_down:
            self.grab_focus()
            prev_active, redraw = self.__set_active(part)
            part.set_state(gtk.STATE_PRELIGHT)
            self.queue_draw_area(*part.get_allocation_tuple())

            if redraw:
                self.queue_draw_area(*prev_active.get_allocation_tuple())
        self.__button_down = False
        return

#    def __key_release_cb(self, widget, event):
#        part = None

#        # left key pressed
#        if event.keyval == 65363:
#            part = self.get_left_part()

#        # right key pressed
#        elif event.keyval == 65361:
#            part = self.get_right_part()

#        if not part: return

#        prev_active = self.set_active(part)
#        self.queue_draw_area(*part.allocation)
#        if prev_active:
#            self.queue_draw_area(*prev_active.allocation)

#        part.emit("clicked", event.copy())
#        return

    def __realize_cb(self, widget):
        self.theme.load(widget.style)
        return

    def __expose_cb(self, widget, event):
        cr = widget.window.cairo_create()

        if self.theme.base_hack:
            cr.set_source_rgb(*self.theme.base_hack)
            cr.paint()

        if self.__scroll_xO:
            self.__draw_hscroll(cr)
        else:
            self.__draw_all(cr, event.area)

        del cr
        return

    def __style_change_cb(self, widget, old_style):
        # when alloc.width == 1, this is typical of an unallocated widget,
        # lets not break a sweat for nothing...
        if self.allocation.width == 1:
            return

        self.theme = self.__pick_theme()
        self.theme.load(widget.style)
        # set height to 0 so that if part height has been reduced the widget will
        # shrink to an appropriate new height based on new font size
        self.set_size_request(-1, 28)

        parts = self.__parts
        self.__parts = []

        # recalc best fits, re-append then draw all
        for part in parts:

            if part.icon.pixbuf:
                part.icon.load_pixbuf()

            part.calc_size_requisition()
            self.__append(part)

        self.queue_draw()
        return

    def __allocation_change_cb(self, widget, allocation):
        if allocation.width == 1:
            return

        path_w = self.__draw_width()
        if path_w == allocation.width:
            return
        elif path_w > allocation.width:
            self.__shrink_check(allocation)
        else:
            self.__grow_check(allocation.width, allocation)

        self.queue_draw()
        return


class PathPart:

    def __init__(self, id, label=None, callback=None, obj=None):
        self.__requisition = (0,0)
        self.__layout = None
        self.__pbar = None

	self.id = id	

        self.allocation = [0, 0, 0, 0]
        self.state = gtk.STATE_NORMAL
        self.shape = PathBar.SHAPE_RECTANGLE

        self.callback = callback
	self.obj = obj
        self.set_label(label or "")
        self.icon = PathBarIcon()
        return

    def set_callback(self, cb):
        self.callback = cb
        return

    def set_label(self, label):
        # escape special characters
        label = gobject.markup_escape_text(label.strip())
        # some hackery to preserve italics markup
        label = label.replace('&lt;i&gt;', '<i>').replace('&lt;/i&gt;', '</i>')
        self.label = label
        return

    def set_icon(self, stock_icon, size=gtk.ICON_SIZE_BUTTON):
        self.icon.specify(stock_icon, size)
        self.icon.load_pixbuf()
        return

    def set_state(self, gtk_state):
        self.state = gtk_state
        return

    def set_shape(self, shape):
        self.shape = shape
        return

    def set_x(self, x):
        self.allocation[0] = int(x)
        return

    def set_size(self, w, h):
        if w != -1: self.allocation[2] = int(w)
        if h != -1: self.allocation[3] = int(h)
        self.__calc_layout_width(self.__layout, self.shape, self.__pbar)
        return

    def set_pathbar(self, path_bar):
        self.__pbar = path_bar
        return

    def get_x(self):
        return self.allocation[0]

    def get_width(self):
        return self.allocation[2]

    def get_height(self):
        return self.allocation[3]

    def get_label(self):
        return self.label

    def get_allocation(self):
        return gtk.gdk.Rectangle(*self.get_allocation_tuple())

    def get_allocation_tuple(self):
        if self.__pbar.get_direction() != gtk.TEXT_DIR_RTL:
            return self.allocation
        x, y, w, h = self.allocation
        x = self.__pbar.allocation[2]-x-w
        return x, y, w, h

    def get_size_requisition(self):
        return self.__requisition

    def get_layout(self):
        return self.__layout

    def activate(self):
        self.__pbar.set_active(self)
        return

    def calc_size_requisition(self):
        pbar = self.__pbar

        # determine widget size base on label width
        self.__layout = self.__layout_text(self.label, pbar.get_pango_context())
        extents = self.__layout.get_pixel_extents()

        # calc text width + 2 * padding, text height + 2 * ypadding
        w = extents[1][2] + 2*pbar.theme.xpadding
        h = max(extents[1][3] + 2*pbar.theme.ypadding, pbar.get_size_request()[1])

        # if has icon add some more pixels on
        if self.icon.pixbuf:
            w += self.icon.pixbuf.get_width() + pbar.theme.spacing
            h = max(self.icon.pixbuf.get_height() + 2*pbar.theme.ypadding, h)

        # extend width depending on part shape  ...
        if self.shape == PathBar.SHAPE_START_ARROW or \
            self.shape == PathBar.SHAPE_END_CAP:
            w += pbar.theme.arrow_width

        elif self.shape == PathBar.SHAPE_MID_ARROW:
            w += 2*pbar.theme.arrow_width

        # if height greater than current height request,
        # reset height request to higher value
        # i get the feeling this should be in set_size_request(), but meh
        if h > pbar.get_size_request()[1]:
            pbar.set_size_request(-1, h)

        self.__requisition = (w,h)
        return w, h

    def is_truncated(self):
        return self.__requisition[0] != self.allocation[2]

    def __layout_text(self, text, pango_context):
        layout = pango.Layout(pango_context)
        layout.set_markup('%s' % text)
        layout.set_ellipsize(pango.ELLIPSIZE_END)
        return layout

    def __calc_layout_width(self, layout, shape, pbar):
        # set layout width
        if self.icon.pixbuf:
            icon_w = self.icon.pixbuf.get_width() + pbar.theme.spacing
        else:
            icon_w = 0

        w = self.allocation[2]
        if shape == PathBar.SHAPE_MID_ARROW:
            layout.set_width((w - 2*pbar.theme.arrow_width -
                2*pbar.theme.xpadding - icon_w)*pango.SCALE)

        elif shape == PathBar.SHAPE_START_ARROW or \
            shape == PathBar.SHAPE_END_CAP:
            layout.set_width((w - pbar.theme.arrow_width - 2*pbar.theme.xpadding -
                icon_w)*pango.SCALE)
        else:
            layout.set_width((w - 2*pbar.theme.xpadding - icon_w)*pango.SCALE)
        return


class PathBarIcon:

    def __init__(self, name=None, size=None):
        self.name = name
        self.size = size
        self.pixbuf = None
        return

    def specify(self, name, size):
        self.name = name
        self.size = size
        return

    def load_pixbuf(self):
        if not self.name:
            print 'Error: No icon specified.'
            return
        if not self.size:
            print 'Note: No icon size specified.'

        def render_icon(icon_set, name, size):
            self.pixbuf = icon_set.render_icon(
                style,
                gtk.TEXT_DIR_NONE,
                gtk.STATE_NORMAL,
                self.size or gtk.ICON_SIZE_BUTTON,
                gtk.Image(),
                None)
            return

        style = gtk.Style()
        icon_set = style.lookup_icon_set(self.name)

        if not icon_set:
            t = gtk.icon_theme_get_default()
            self.pixbuf = t.lookup_icon(self.name, self.size, 0).load_icon()
        else:
            icon_set = style.lookup_icon_set(self.name)
            render_icon(icon_set, self.name, self.size)

        if not self.pixbuf:
            print 'Error: No name failed to match any installed icon set.'
            self.name = gtk.STOCK_MISSING_IMAGE
            icon_set = style.lookup_icon_set(self.name)
            render_icon(icon_set, self.name, self.size)
        return


class PathBarThemeHuman:

    PRELIT_NORMAL = 10
    PRELIT_ACTIVE = 11

    curvature = 2.5
    min_part_width = 56
    xpadding = 8
    ypadding = 2
    spacing = 4
    arrow_width = 13
    scroll_duration_ms = 150
    scroll_fps = 50
    animate = gtk.settings_get_default().get_property("gtk-enable-animations")

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               mid[gtk.STATE_SELECTED], 0.25)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.2)),
                               f(active)),

            gtk.STATE_SELECTED: (f(mid[gtk.STATE_ACTIVE]),
                                 f(mid[gtk.STATE_ACTIVE])),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.25)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.25)),
                                 f(rgb.shade(active, 1.05)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.9)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_ACTIVE,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeHumanClearlooks(PathBarThemeHuman):

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               mid[gtk.STATE_SELECTED], 0.25)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.20)),
                                f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.20)),
                               f(rgb.shade(active, 1.05))),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_ACTIVE], 1.15)),
                                f(mid[gtk.STATE_ACTIVE])),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
            gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: None,
            gtk.STATE_ACTIVE: None,
            gtk.STATE_SELECTED: f(mid[gtk.STATE_ACTIVE]),
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeDust(PathBarThemeHuman):

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               light[gtk.STATE_SELECTED], 0.3)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.3)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.3)),
                               f(active)),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95))),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeNewWave(PathBarThemeHuman):

    curvature = 1.5

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               light[gtk.STATE_SELECTED], 0.5)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.01)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.01)),
                               f(active)),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95))),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.2)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
            gtk.STATE_ACTIVE: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(rgb.shade(light[gtk.STATE_PRELIGHT], 1.2))
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = f(gtk.gdk.color_parse("#F2F2F2"))
        return


class PathBarThemeHicolor:

    PRELIT_NORMAL = 10
    PRELIT_ACTIVE = 11

    curvature = 0.5
    min_part_width = 56
    xpadding = 15
    ypadding = 10
    spacing = 10
    arrow_width = 15
    scroll_duration_ms = 150
    scroll_fps = 50
    animate = gtk.settings_get_default().get_property("gtk-enable-animations")

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(mid[gtk.STATE_NORMAL]),
                               f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(mid[gtk.STATE_ACTIVE]),
                               f(mid[gtk.STATE_ACTIVE])),

            gtk.STATE_SELECTED: (f(mid[gtk.STATE_SELECTED]),
                                 f(mid[gtk.STATE_SELECTED])),

            self.PRELIT_NORMAL: (f(mid[gtk.STATE_PRELIGHT]),
                                 f(mid[gtk.STATE_PRELIGHT])),

            self.PRELIT_ACTIVE: (f(mid[gtk.STATE_PRELIGHT]),
                                 f(mid[gtk.STATE_PRELIGHT]))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(dark[gtk.STATE_SELECTED]),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_SELECTED,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemes:

    DICT = {
        "Human": PathBarThemeHuman,
        "Human-Clearlooks": PathBarThemeHumanClearlooks,
        "HighContrastInverse": PathBarThemeHicolor,
        "HighContrastLargePrintInverse": PathBarThemeHicolor,
        "Dust": PathBarThemeDust,
        "Dust Sand": PathBarThemeDust,
        "New Wave": PathBarThemeNewWave
        }


class NavigationBar(PathBar):
    def __init__(self, group=None):
        PathBar.__init__(self)
        self.set_size_request(-1, 28)
        self.id_to_part = {}	
        return

    def add_with_id(self, label, callback, id, obj, icon=None):
        """
        Add a new button with the given label/callback

        If there is the same id already, replace the existing one
        with the new one
        """
	if label == self.last_label:
		#ignoring duplicate
		return

	#print "Adding %s(%d)" % (label, id)


        # check if we have the button of that id or need a new one
	if id == 1 and len(self.id_to_part) > 0:
		# We already have the first item, just don't do anything
		return
	else:
		for i in self.id_to_part:
			part = self.id_to_part[i]
			if part.id >= id:
				self.remove(part)
        
	part = PathPart(id, label, callback, obj)
        part.set_pathbar(self)
        self.id_to_part[id] = part
        gobject.timeout_add(150, self.append, part)

        if icon: part.set_icon(icon)
	self.last_label = label
        return

    def remove_id(self, id):
        if not id in self.id_to_part:
            return

        part = self.id_to_part[id]
        del self.id_to_part[id]
        self.remove(part)
	self.last_label = None
        return

    def remove_all(self):
        """remove all elements"""
        self.__parts = []
        self.id_to_part = {}
        self.queue_draw()
	self.last_label = None
        return

    def get_button_from_id(self, id):
        """
        return the button for the given id (or None)
        """
        if not id in self.id_to_part:
            return None
        return self.id_to_part[id]

    def get_label(self, id):
        """
        Return the label of the navigation button with the given id
        """
        if not id in self.id_to_part:
            return


# Copyright (C) 2009 <NAME>
#
# Authors:
#   <NAME>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import rgb
import gtk
import cairo
import pango
import gobject

from rgb import to_float as f

# pi constants
M_PI = 3.1415926535897931
PI_OVER_180 = 0.017453292519943295


class PathBar(gtk.DrawingArea):

    # shapes
    SHAPE_RECTANGLE = 0
    SHAPE_START_ARROW = 1
    SHAPE_MID_ARROW = 2
    SHAPE_END_CAP = 3

    def __init__(self, group=None):
        gtk.DrawingArea.__init__(self)
        self.__init_drawing()
        self.set_redraw_on_allocate(False)

        self.__parts = []
        self.__active_part = None
        self.__focal_part = None
        self.__button_down = False

        self.__scroller = None
        self.__scroll_xO = 0

        self.theme = self.__pick_theme()

        # setup event handling
        self.set_flags(gtk.CAN_FOCUS)
        self.set_events(gtk.gdk.POINTER_MOTION_MASK|
                        gtk.gdk.BUTTON_PRESS_MASK|
                        gtk.gdk.BUTTON_RELEASE_MASK|
                        gtk.gdk.KEY_RELEASE_MASK|
                        gtk.gdk.KEY_PRESS_MASK|
                        gtk.gdk.LEAVE_NOTIFY_MASK)

        self.connect("motion-notify-event", self.__motion_notify_cb)
        self.connect("leave-notify-event", self.__leave_notify_cb)
        self.connect("button-press-event", self.__button_press_cb)
        self.connect("button-release-event", self.__button_release_cb)
#        self.connect("key-release-event", self.__key_release_cb)

        self.connect("realize", self.__realize_cb)
        self.connect("expose-event", self.__expose_cb)
        self.connect("style-set", self.__style_change_cb)
        self.connect("size-allocate", self.__allocation_change_cb)
	self.last_label = None
        return

    def set_active(self, part):	
        part.set_state(gtk.STATE_ACTIVE)
        prev, redraw = self.__set_active(part)
        if redraw:
            self.queue_draw_area(*prev.get_allocation_tuple())
            self.queue_draw_area(*part.get_allocation_tuple())
	self.last_label = None
        return

    def get_active(self):
        return self.__active_part

#    def get_left_part(self):
#        active = self.get_active()
#        if not active:
#            return self.__parts[0]

#        i = self.__parts.index(active)+1
#        if i > len(self.__parts)-1:
#            i = 0
#        return self.__parts[i]

#    def get_right_part(self):
#        active = self.get_active()
#        if not active:
#            return self.__parts[0]

#        i = self.__parts.index(active)-1
#        if i < 0:
#            i = len(self.__parts)-1
#        return self.__parts[i]

    def append(self, part):
        prev, did_shrink = self.__append(part)
        if not self.get_property("visible"):
            return False

        if self.theme.animate and len(self.__parts) > 1:
            aw = self.theme.arrow_width

            # calc draw_area
            x,y,w,h = part.get_allocation_tuple()
            w += aw

            # begin scroll animation
            self.__hscroll_out_init(
                part.get_width(),
                gtk.gdk.Rectangle(x,y,w,h),
                self.theme.scroll_duration_ms,
                self.theme.scroll_fps
                )
        else:
            self.queue_draw_area(*part.get_allocation_tuple())
        return False

    def remove(self, part):
        if len(self.__parts)-1 < 1:
            #print 'The first part is sacred ;)'
            return

        old_w = self.__draw_width()

        # remove part from interal part list
	try:
	        del self.__parts[self.__parts.index(part)]
	except:
		pass

        self.__compose_parts(self.__parts[-1], False)

        if old_w >= self.allocation.width:
            self.__grow_check(old_w, self.allocation)
            self.queue_draw()

        else:
            self.queue_draw_area(*part.get_allocation_tuple())
            self.queue_draw_area(*self.__parts[-1].get_allocation_tuple())
        return

    def __set_active(self, part):

	bigger = False
	for i in self.id_to_part:	    
            apart = self.id_to_part[i]
	    if bigger:
		self.remove(apart)
	    if apart == part:
		bigger = True

        prev_active = self.__active_part
        redraw = False
        if part.callback:
            part.callback(self, part.obj)
        if prev_active and prev_active != part:
            prev_active.set_state(gtk.STATE_NORMAL)
            redraw = True

        self.__active_part = part
        return prev_active, redraw

    def __append(self, part):
        # clean up any exisitng scroll callbacks
        if self.__scroller:
            gobject.source_remove(self.__scroller)
        self.__scroll_xO = 0

        # the basics
        x = self.__draw_width()
        self.__parts.append(part)
        part.set_pathbar(self)

        prev_active = self.set_active(part)

        # determin part shapes, and calc modified parts widths
        prev = self.__compose_parts(part, True)
        # set the position of new part
        part.set_x(x)

        # check parts fit to widgets allocated width
        if x + part.get_width() > self.allocation.width  and \
            self.allocation.width != 1:
            self.__shrink_check(self.allocation)
            return prev, True

        return prev, False

#    def __shorten(self, n):
#        n = int(n)
#        old_w = self.__draw_width()
#        end_active = self.get_active() == self.__parts[-1]

#        if len(self.__parts)-n < 1:
#            print WARNING + 'The first part is sacred ;)' + ENDC
#            return old_w, False

#        del self.__parts[-n:]
#        self.__compose_parts(self.__parts[-1], False)

#        if end_active:
#            self.set_active(self.__parts[-1])

#        if old_w >= self.allocation.width:
#            self.__grow_check(old_w, self.allocation)
#            return old_w, True

#        return old_w, False

    def __shrink_check(self, allocation):
        path_w = self.__draw_width()
        shrinkage = path_w - allocation.width
        mpw = self.theme.min_part_width
        xO = 0

        for part in self.__parts[:-1]:
            w = part.get_width()
            dw = 0

            if w - shrinkage <= mpw:
                dw = w - mpw
                shrinkage -= dw
                part.set_size(mpw, -1)
                part.set_x(part.get_x() - xO)

            else:
                part.set_size(w - shrinkage, -1)
                part.set_x(part.get_x() - xO)
                dw = shrinkage
                shrinkage = 0

            xO += dw

        last = self.__parts[-1]
        last.set_x(last.get_x() - xO)
        return

    def __grow_check(self, old_width, allocation):
        parts = self.__parts
        if len(parts) == 0:
            return

        growth = old_width - self.__draw_width()
        parts.reverse()

        for part in parts:
            bw = part.get_size_requisition()[0]
            w = part.get_width()

            if w < bw:
                dw = bw - w

                if dw <= growth:
                    growth -= dw
                    part.set_size(bw, -1)
                    part.set_x(part.get_x() + growth)

                else:
                    part.set_size(w + growth, -1)
                    growth = 0

            else:
                part.set_x(part.get_x() + growth)

        parts.reverse()
        shift =  parts[0].get_x()

        # left align parts
        if shift > 0:
            for part in parts: part.set_x(part.get_x() - shift)
        return

    def __compose_parts(self, last, prev_set_size):
        parts = self.__parts

        if len(parts) == 1:
            last.set_shape(self.SHAPE_RECTANGLE)
            last.set_size(*last.calc_size_requisition())
            prev = None

        elif len(parts) == 2:
            prev = parts[0]
            prev.set_shape(self.SHAPE_START_ARROW)
            prev.calc_size_requisition()

            last.set_shape(self.SHAPE_END_CAP)
            last.set_size(*last.calc_size_requisition())

        else:
            prev = parts[-2]
            prev.set_shape(self.SHAPE_MID_ARROW)
            prev.calc_size_requisition()

            last.set_shape(self.SHAPE_END_CAP)
            last.set_size(*last.calc_size_requisition())

        if prev and prev_set_size:
            prev.set_size(*prev.get_size_requisition())
        return prev

    def __draw_width(self):
        l = len(self.__parts)
        if l == 0:
            return 0
        a = self.__parts[-1].allocation
        return a[0] + a[2]

    def __hscroll_out_init(self, distance, draw_area, duration, fps):
        self.__scroller = gobject.timeout_add(
            int(1000.0 / fps),  # interval
            self.__hscroll_out_cb,
            distance,
            duration*0.001,   # 1 over duration (converted to seconds)
            gobject.get_current_time(),
            draw_area.x,
            draw_area.y,
            draw_area.width,
            draw_area.height)
        return

    def __hscroll_out_cb(self, distance, duration, start_t, x, y, w, h):
        cur_t = gobject.get_current_time()
        xO = distance - distance*((cur_t - start_t) / duration)

        if xO > 0:
            self.__scroll_xO = xO
            self.queue_draw_area(x, y, w, h)
        else:   # final frame
            self.__scroll_xO = 0
            # redraw the entire widget
            # incase some timeouts are skipped due to high system load
            self.queue_draw()
            self.__scroller = None
            return False
        return True

    def __part_at_xy(self, x, y):
        for part in self.__parts:
            a = part.get_allocation()
            region = gtk.gdk.region_rectangle(a)

            if region.point_in(int(x), int(y)):
                return part
        return None

    def __draw_hscroll(self, cr):
        if len(self.__parts) < 2:
            return

        # draw the last two parts
        prev, last = self.__parts[-2:]

        # style theme stuff
        style, r, aw, shapes = self.style, self.theme.curvature, \
            self.theme.arrow_width, self.__shapes

        # draw part that need scrolling
        self.__draw_part(cr,
                         last,
                         style,
                         r,
                         aw,
                         shapes,
                         self.__scroll_xO)

        # draw the last part that does not scroll
        self.__draw_part(cr,
                         prev,
                         style,
                         r,
                         aw,
                         shapes)
        return

    def __draw_all(self, cr, event_area):
        style = self.style
        r = self.theme.curvature
        aw = self.theme.arrow_width
        shapes = self.__shapes
        region = gtk.gdk.region_rectangle(event_area)

        # if a scroll is pending we want to not draw the final part,
        # as we don't want to prematurely reveal the part befor the
        # scroll animation has had a chance to start
        if self.__scroller:
            parts = self.__parts[:-1]
        else:
            parts = self.__parts

        parts.reverse()
        for part in parts:
            if region.rect_in(part.get_allocation()) != gtk.gdk.OVERLAP_RECTANGLE_OUT:
                self.__draw_part(cr, part, style, r, aw, shapes)
        parts.reverse()
        return

    def __draw_part_ltr(self, cr, part, style, r, aw, shapes, sxO=0):
        x, y, w, h = part.get_allocation()
        shape = part.shape
        state = part.state
        icon_pb = part.icon.pixbuf

        cr.save()
        cr.translate(x-sxO, y)

        # draw bg
        self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)

        # determine left margin.  left margin depends on part shape
        # and whether there exists an icon or not
        if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
            margin = int(0.75*self.theme.arrow_width + self.theme.xpadding)
        else:
            margin = self.theme.xpadding

        # draw icon
        if icon_pb:
            cr.set_source_pixbuf(
                icon_pb,
                self.theme.xpadding-sxO,
                (alloc.height - icon_pb.get_height())/2)
            cr.paint()
            margin += icon_pb.get_width() + self.theme.spacing

        # if space is limited and an icon is set, dont draw label
        # otherwise, draw label
        if w == self.theme.min_part_width and icon_pb:
            pass

        else:
            layout = part.get_layout()
            lw, lh = layout.get_pixel_size()
            dst_x = x + margin - int(sxO)
            dst_y = (self.allocation.height - lh)/2+1
            style.paint_layout(
                self.window,
                self.theme.text_state[state],
                False,
                (dst_x, dst_y, lw+4, lh),   # clip area
                self,
                None,
                dst_x,
                dst_y,
                layout)

        cr.restore()
        return

    def __draw_part_rtl(self, cr, part, style, r, aw, shapes, sxO=0):
        x, y, w, h = part.get_allocation()
        shape = part.shape
        state = part.state
        icon_pb = part.icon.pixbuf

        cr.save()
        cr.translate(x+sxO, y)

        # draw bg
        self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)

        # determine left margin.  left margin depends on part shape
        # and whether there exists an icon or not
        if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
            margin = self.theme.arrow_width + self.theme.xpadding
        else:
            margin = self.theme.xpadding

        # draw icon
        if icon_pb:
            margin += icon_pb.get_width()
            cr.set_source_pixbuf(
                icon_pb,
                w - margin + sxO,
                (h - icon_pb.get_height())/2)
            cr.paint()
            margin += self.spacing

        # if space is limited and an icon is set, dont draw label
        # otherwise, draw label
        if w == self.theme.min_part_width and icon_pb:
            pass

        else:
            layout = part.get_layout()
            lw, lh = layout.get_pixel_size()
            dst_x = x + part.get_width() - margin - lw + int(sxO)
            dst_y = (self.allocation.height - lh)/2+1
            style.paint_layout(
                self.window,
                self.theme.text_state[state],
                False,
                None,
                self,
                None,
                dst_x,
                dst_y,
                layout)

        cr.restore()
        return

    def __draw_part_bg(self, cr, part, w, h, state, shape, style, r, aw, shapes):
        # outer slight bevel or focal highlight
        shapes[shape](cr, 0, 0, w, h, r, aw)
        cr.set_source_rgba(0, 0, 0, 0.055)
        cr.fill()

        # colour scheme dicts
        bg = self.theme.bg_colors
        outer = self.theme.dark_line_colors
        inner = self.theme.light_line_colors

        # bg linear vertical gradient
        if state != gtk.STATE_PRELIGHT:
            color1, color2 = bg[state]
        else:
            if part != self.get_active():
                color1, color2 = bg[self.theme.PRELIT_NORMAL]
            else:
                color1, color2 = bg[self.theme.PRELIT_ACTIVE]

        shapes[shape](cr, 1, 1, w-1, h-1, r, aw)
        lin = cairo.LinearGradient(0, 0, 0, h-1)
        lin.add_color_stop_rgb(0.0, *color1)
        lin.add_color_stop_rgb(1.0, *color2)
        cr.set_source(lin)
        cr.fill()

        cr.set_line_width(1.0)
        # strong outline
        shapes[shape](cr, 1.5, 1.5, w-1.5, h-1.5, r, aw)
        cr.set_source_rgb(*outer[state])
        cr.stroke()

        # inner bevel/highlight
        if self.theme.light_line_colors[state]:
            shapes[shape](cr, 2.5, 2.5, w-2.5, h-2.5, r, aw)
            r, g, b = inner[state]
            cr.set_source_rgba(r, g, b, 0.6)
            cr.stroke()
        return

    def __shape_rect(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __shape_start_arrow_ltr(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        # arrow head
        cr.line_to(w-aw+1, y)
        cr.line_to(w, (h+y)*0.5)
        cr.line_to(w-aw+1, h)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __shape_mid_arrow_ltr(self, cr, x, y, w, h, r, aw):
        cr.move_to(-1, y)
        # arrow head
        cr.line_to(w-aw+1, y)
        cr.line_to(w, (h+y)*0.5)
        cr.line_to(w-aw+1, h)
        cr.line_to(-1, h)
        cr.close_path()
        return

    def __shape_end_cap_ltr(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.move_to(-1, y)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.line_to(-1, h)
        cr.close_path()
        return

    def __shape_start_arrow_rtl(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.new_sub_path()
        cr.move_to(x, (h+y)*0.5)
        cr.line_to(aw-1, y)
        cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
        cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
        cr.line_to(aw-1, h)
        cr.close_path()
        return

    def __shape_mid_arrow_rtl(self, cr, x, y, w, h, r, aw):
        cr.move_to(x, (h+y)*0.5)
        cr.line_to(aw-1, y)
        cr.line_to(w+1, y)
        cr.line_to(w+1, h)
        cr.line_to(aw-1, h)
        cr.close_path()
        return

    def __shape_end_cap_rtl(self, cr, x, y, w, h, r, aw):
        global M_PI, PI_OVER_180
        cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
        cr.line_to(w+1, y)
        cr.line_to(w+1, h)
        cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
        cr.close_path()
        return

    def __state(self, part):
        # returns the idle state of the part depending on
        # whether part is active or not.
        if part == self.__active_part:
            return gtk.STATE_ACTIVE
        return gtk.STATE_NORMAL

    def __tooltip_check(self, part):
        # only show a tooltip if part is truncated, i.e. not all label text is
        # visible.
        if part.is_truncated():
            self.set_has_tooltip(False)
            gobject.timeout_add(50, self.__set_tooltip_cb, part.label)
        else:
            self.set_has_tooltip(False)
        return

    def __set_tooltip_cb(self, text):
        # callback allows the tooltip position to be updated as pointer moves
        # accross different parts
        self.set_has_tooltip(True)
        self.set_tooltip_markup(text)
        return False

    def __pick_theme(self, name=None):
        name = name or gtk.settings_get_default().get_property("gtk-theme-name")
        themes = PathBarThemes.DICT
        if themes.has_key(name):
            return themes[name]()
        #print "No styling hints for %s are available" % name
        return PathBarThemeHuman()

    def __init_drawing(self):
        if self.get_direction() != gtk.TEXT_DIR_RTL:
            self.__draw_part = self.__draw_part_ltr
            self.__shapes = {
                self.SHAPE_RECTANGLE : self.__shape_rect,
                self.SHAPE_START_ARROW : self.__shape_start_arrow_ltr,
                self.SHAPE_MID_ARROW : self.__shape_mid_arrow_ltr,
                self.SHAPE_END_CAP : self.__shape_end_cap_ltr}
        else:
            self.__draw_part = self.__draw_part_rtl
            self.__shapes = {
                self.SHAPE_RECTANGLE : self.__shape_rect,
                self.SHAPE_START_ARROW : self.__shape_start_arrow_rtl,
                self.SHAPE_MID_ARROW : self.__shape_mid_arrow_rtl,
                self.SHAPE_END_CAP : self.__shape_end_cap_rtl}
        return

    def __motion_notify_cb(self, widget, event):
        if self.__scroll_xO > 0:
            return

        part = self.__part_at_xy(event.x, event.y)
        prev_focal = self.__focal_part

        if self.__button_down:
            if prev_focal and part != prev_focal:
                prev_focal.set_state(self.__state(prev_focal))
                self.queue_draw_area(*prev_focal.get_allocation_tuple())
            return

        self.__button_down = False
        if part and part.state != gtk.STATE_PRELIGHT:
            self.__tooltip_check(part)
            part.set_state(gtk.STATE_PRELIGHT)

            if prev_focal:
                prev_focal.set_state(self.__state(prev_focal))
                self.queue_draw_area(*prev_focal.get_allocation_tuple())

            self.__focal_part = part
            self.queue_draw_area(*part.get_allocation_tuple())

        elif not part and prev_focal != None:
            prev_focal.set_state(self.__state(prev_focal))
            self.queue_draw_area(*prev_focal.get_allocation_tuple())
            self.__focal_part = None
        return

    def __leave_notify_cb(self, widget, event):
        self.__button_down = False
        prev_focal = self.__focal_part
        if prev_focal:
            prev_focal.set_state(self.__state(prev_focal))
            self.queue_draw_area(*prev_focal.get_allocation_tuple())
        self.__focal_part = None
        return

    def __button_press_cb(self, widget, event):
        self.__button_down = True
        part = self.__part_at_xy(event.x, event.y)
        if part:
            part.set_state(gtk.STATE_SELECTED)
            self.queue_draw_area(*part.get_allocation_tuple())
        return

    def __button_release_cb(self, widget, event):
        part = self.__part_at_xy(event.x, event.y)

        if self.__focal_part and self.__focal_part != part:
            pass
        elif part and self.__button_down:
            self.grab_focus()
            prev_active, redraw = self.__set_active(part)
            part.set_state(gtk.STATE_PRELIGHT)
            self.queue_draw_area(*part.get_allocation_tuple())

            if redraw:
                self.queue_draw_area(*prev_active.get_allocation_tuple())
        self.__button_down = False
        return

#    def __key_release_cb(self, widget, event):
#        part = None

#        # left key pressed
#        if event.keyval == 65363:
#            part = self.get_left_part()

#        # right key pressed
#        elif event.keyval == 65361:
#            part = self.get_right_part()

#        if not part: return

#        prev_active = self.set_active(part)
#        self.queue_draw_area(*part.allocation)
#        if prev_active:
#            self.queue_draw_area(*prev_active.allocation)

#        part.emit("clicked", event.copy())
#        return

    def __realize_cb(self, widget):
        self.theme.load(widget.style)
        return

    def __expose_cb(self, widget, event):
        cr = widget.window.cairo_create()

        if self.theme.base_hack:
            cr.set_source_rgb(*self.theme.base_hack)
            cr.paint()

        if self.__scroll_xO:
            self.__draw_hscroll(cr)
        else:
            self.__draw_all(cr, event.area)

        del cr
        return

    def __style_change_cb(self, widget, old_style):
        # when alloc.width == 1, this is typical of an unallocated widget,
        # lets not break a sweat for nothing...
        if self.allocation.width == 1:
            return

        self.theme = self.__pick_theme()
        self.theme.load(widget.style)
        # set height to 0 so that if part height has been reduced the widget will
        # shrink to an appropriate new height based on new font size
        self.set_size_request(-1, 28)

        parts = self.__parts
        self.__parts = []

        # recalc best fits, re-append then draw all
        for part in parts:

            if part.icon.pixbuf:
                part.icon.load_pixbuf()

            part.calc_size_requisition()
            self.__append(part)

        self.queue_draw()
        return

    def __allocation_change_cb(self, widget, allocation):
        if allocation.width == 1:
            return

        path_w = self.__draw_width()
        if path_w == allocation.width:
            return
        elif path_w > allocation.width:
            self.__shrink_check(allocation)
        else:
            self.__grow_check(allocation.width, allocation)

        self.queue_draw()
        return


class PathPart:

    def __init__(self, id, label=None, callback=None, obj=None):
        self.__requisition = (0,0)
        self.__layout = None
        self.__pbar = None

	self.id = id	

        self.allocation = [0, 0, 0, 0]
        self.state = gtk.STATE_NORMAL
        self.shape = PathBar.SHAPE_RECTANGLE

        self.callback = callback
	self.obj = obj
        self.set_label(label or "")
        self.icon = PathBarIcon()
        return

    def set_callback(self, cb):
        self.callback = cb
        return

    def set_label(self, label):
        # escape special characters
        label = gobject.markup_escape_text(label.strip())
        # some hackery to preserve italics markup
        label = label.replace('&lt;i&gt;', '<i>').replace('&lt;/i&gt;', '</i>')
        self.label = label
        return

    def set_icon(self, stock_icon, size=gtk.ICON_SIZE_BUTTON):
        self.icon.specify(stock_icon, size)
        self.icon.load_pixbuf()
        return

    def set_state(self, gtk_state):
        self.state = gtk_state
        return

    def set_shape(self, shape):
        self.shape = shape
        return

    def set_x(self, x):
        self.allocation[0] = int(x)
        return

    def set_size(self, w, h):
        if w != -1: self.allocation[2] = int(w)
        if h != -1: self.allocation[3] = int(h)
        self.__calc_layout_width(self.__layout, self.shape, self.__pbar)
        return

    def set_pathbar(self, path_bar):
        self.__pbar = path_bar
        return

    def get_x(self):
        return self.allocation[0]

    def get_width(self):
        return self.allocation[2]

    def get_height(self):
        return self.allocation[3]

    def get_label(self):
        return self.label

    def get_allocation(self):
        return gtk.gdk.Rectangle(*self.get_allocation_tuple())

    def get_allocation_tuple(self):
        if self.__pbar.get_direction() != gtk.TEXT_DIR_RTL:
            return self.allocation
        x, y, w, h = self.allocation
        x = self.__pbar.allocation[2]-x-w
        return x, y, w, h

    def get_size_requisition(self):
        return self.__requisition

    def get_layout(self):
        return self.__layout

    def activate(self):
        self.__pbar.set_active(self)
        return

    def calc_size_requisition(self):
        pbar = self.__pbar

        # determine widget size base on label width
        self.__layout = self.__layout_text(self.label, pbar.get_pango_context())
        extents = self.__layout.get_pixel_extents()

        # calc text width + 2 * padding, text height + 2 * ypadding
        w = extents[1][2] + 2*pbar.theme.xpadding
        h = max(extents[1][3] + 2*pbar.theme.ypadding, pbar.get_size_request()[1])

        # if has icon add some more pixels on
        if self.icon.pixbuf:
            w += self.icon.pixbuf.get_width() + pbar.theme.spacing
            h = max(self.icon.pixbuf.get_height() + 2*pbar.theme.ypadding, h)

        # extend width depending on part shape  ...
        if self.shape == PathBar.SHAPE_START_ARROW or \
            self.shape == PathBar.SHAPE_END_CAP:
            w += pbar.theme.arrow_width

        elif self.shape == PathBar.SHAPE_MID_ARROW:
            w += 2*pbar.theme.arrow_width

        # if height greater than current height request,
        # reset height request to higher value
        # i get the feeling this should be in set_size_request(), but meh
        if h > pbar.get_size_request()[1]:
            pbar.set_size_request(-1, h)

        self.__requisition = (w,h)
        return w, h

    def is_truncated(self):
        return self.__requisition[0] != self.allocation[2]

    def __layout_text(self, text, pango_context):
        layout = pango.Layout(pango_context)
        layout.set_markup('%s' % text)
        layout.set_ellipsize(pango.ELLIPSIZE_END)
        return layout

    def __calc_layout_width(self, layout, shape, pbar):
        # set layout width
        if self.icon.pixbuf:
            icon_w = self.icon.pixbuf.get_width() + pbar.theme.spacing
        else:
            icon_w = 0

        w = self.allocation[2]
        if shape == PathBar.SHAPE_MID_ARROW:
            layout.set_width((w - 2*pbar.theme.arrow_width -
                2*pbar.theme.xpadding - icon_w)*pango.SCALE)

        elif shape == PathBar.SHAPE_START_ARROW or \
            shape == PathBar.SHAPE_END_CAP:
            layout.set_width((w - pbar.theme.arrow_width - 2*pbar.theme.xpadding -
                icon_w)*pango.SCALE)
        else:
            layout.set_width((w - 2*pbar.theme.xpadding - icon_w)*pango.SCALE)
        return


class PathBarIcon:

    def __init__(self, name=None, size=None):
        self.name = name
        self.size = size
        self.pixbuf = None
        return

    def specify(self, name, size):
        self.name = name
        self.size = size
        return

    def load_pixbuf(self):
        if not self.name:
            print 'Error: No icon specified.'
            return
        if not self.size:
            print 'Note: No icon size specified.'

        def render_icon(icon_set, name, size):
            self.pixbuf = icon_set.render_icon(
                style,
                gtk.TEXT_DIR_NONE,
                gtk.STATE_NORMAL,
                self.size or gtk.ICON_SIZE_BUTTON,
                gtk.Image(),
                None)
            return

        style = gtk.Style()
        icon_set = style.lookup_icon_set(self.name)

        if not icon_set:
            t = gtk.icon_theme_get_default()
            self.pixbuf = t.lookup_icon(self.name, self.size, 0).load_icon()
        else:
            icon_set = style.lookup_icon_set(self.name)
            render_icon(icon_set, self.name, self.size)

        if not self.pixbuf:
            print 'Error: No name failed to match any installed icon set.'
            self.name = gtk.STOCK_MISSING_IMAGE
            icon_set = style.lookup_icon_set(self.name)
            render_icon(icon_set, self.name, self.size)
        return


class PathBarThemeHuman:

    PRELIT_NORMAL = 10
    PRELIT_ACTIVE = 11

    curvature = 2.5
    min_part_width = 56
    xpadding = 8
    ypadding = 2
    spacing = 4
    arrow_width = 13
    scroll_duration_ms = 150
    scroll_fps = 50
    animate = gtk.settings_get_default().get_property("gtk-enable-animations")

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               mid[gtk.STATE_SELECTED], 0.25)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.2)),
                               f(active)),

            gtk.STATE_SELECTED: (f(mid[gtk.STATE_ACTIVE]),
                                 f(mid[gtk.STATE_ACTIVE])),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.25)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.25)),
                                 f(rgb.shade(active, 1.05)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.9)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_ACTIVE,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeHumanClearlooks(PathBarThemeHuman):

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               mid[gtk.STATE_SELECTED], 0.25)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.20)),
                                f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.20)),
                               f(rgb.shade(active, 1.05))),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_ACTIVE], 1.15)),
                                f(mid[gtk.STATE_ACTIVE])),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
            gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: None,
            gtk.STATE_ACTIVE: None,
            gtk.STATE_SELECTED: f(mid[gtk.STATE_ACTIVE]),
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeDust(PathBarThemeHuman):

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               light[gtk.STATE_SELECTED], 0.3)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.3)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.3)),
                               f(active)),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95))),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemeNewWave(PathBarThemeHuman):

    curvature = 1.5

    def __init__(self):
        PathBarThemeHuman.__init__(self)
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text
        active = rgb.mix_color(mid[gtk.STATE_NORMAL],
                               light[gtk.STATE_SELECTED], 0.5)

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.01)),
                                f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.01)),
                               f(active)),

            gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95))),

            self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
                                 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),

            self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.2)),
                                 f(rgb.shade(active, 1.15)))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
            gtk.STATE_ACTIVE: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(rgb.shade(light[gtk.STATE_PRELIGHT], 1.2))
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_NORMAL,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = f(gtk.gdk.color_parse("#F2F2F2"))
        return


class PathBarThemeHicolor:

    PRELIT_NORMAL = 10
    PRELIT_ACTIVE = 11

    curvature = 0.5
    min_part_width = 56
    xpadding = 15
    ypadding = 10
    spacing = 10
    arrow_width = 15
    scroll_duration_ms = 150
    scroll_fps = 50
    animate = gtk.settings_get_default().get_property("gtk-enable-animations")

    def __init__(self):
        return

    def load(self, style):
        mid = style.mid
        dark = style.dark
        light = style.light
        text = style.text

        self.bg_colors = {
            gtk.STATE_NORMAL: (f(mid[gtk.STATE_NORMAL]),
                               f(mid[gtk.STATE_NORMAL])),

            gtk.STATE_ACTIVE: (f(mid[gtk.STATE_ACTIVE]),
                               f(mid[gtk.STATE_ACTIVE])),

            gtk.STATE_SELECTED: (f(mid[gtk.STATE_SELECTED]),
                                 f(mid[gtk.STATE_SELECTED])),

            self.PRELIT_NORMAL: (f(mid[gtk.STATE_PRELIGHT]),
                                 f(mid[gtk.STATE_PRELIGHT])),

            self.PRELIT_ACTIVE: (f(mid[gtk.STATE_PRELIGHT]),
                                 f(mid[gtk.STATE_PRELIGHT]))
            }

        self.dark_line_colors = {
            gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: f(dark[gtk.STATE_SELECTED]),
            gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT])
            }

        self.light_line_colors = {
            gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
            gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
            gtk.STATE_SELECTED: None,
            gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT])
            }

        self.text_state = {
            gtk.STATE_NORMAL: gtk.STATE_NORMAL,
            gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
            gtk.STATE_SELECTED: gtk.STATE_SELECTED,
            gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT
            }

        self.base_hack = None
        return


class PathBarThemes:

    DICT = {
        "Human": PathBarThemeHuman,
        "Human-Clearlooks": PathBarThemeHumanClearlooks,
        "HighContrastInverse": PathBarThemeHicolor,
        "HighContrastLargePrintInverse": PathBarThemeHicolor,
        "Dust": PathBarThemeDust,
        "Dust Sand": PathBarThemeDust,
        "New Wave": PathBarThemeNewWave
        }


class NavigationBar(PathBar):
    def __init__(self, group=None):
        PathBar.__init__(self)
        self.set_size_request(-1, 28)
        self.id_to_part = {}	
        return

    def add_with_id(self, label, callback, id, obj, icon=None):
        """
        Add a new button with the given label/callback

        If there is the same id already, replace the existing one
        with the new one
        """
	if label == self.last_label:
		#ignoring duplicate
		return

	#print "Adding %s(%d)" % (label, id)


        # check if we have the button of that id or need a new one
	if id == 1 and len(self.id_to_part) > 0:
		# We already have the first item, just don't do anything
		return
	else:
		for i in self.id_to_part:
			part = self.id_to_part[i]
			if part.id >= id:
				self.remove(part)
        
	part = PathPart(id, label, callback, obj)
        part.set_pathbar(self)
        self.id_to_part[id] = part
        gobject.timeout_add(150, self.append, part)

        if icon: part.set_icon(icon)
	self.last_label = label
        return

    def remove_id(self, id):
        if not id in self.id_to_part:
            return

        part = self.id_to_part[id]
        del self.id_to_part[id]
        self.remove(part)
	self.last_label = None
        return

    def remove_all(self):
        """remove all elements"""
        self.__parts = []
        self.id_to_part = {}
        self.queue_draw()
	self.last_label = None
        return

    def get_button_from_id(self, id):
        """
        return the button for the given id (or None)
        """
        if not id in self.id_to_part:
            return None
        return self.id_to_part[id]

    def get_label(self, id):
        """
        Return the label of the navigation button with the given id
        """
        if not id in self.id_to_part:
            return
# Copyright (C) 2009 <NAME> # # Authors: #   <NAME> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program.  If not, see <http://www.gnu.org/licenses/>. # pi constants # shapes # setup event handling #        self.connect("key-release-event", self.__key_release_cb) #    def get_left_part(self): #        active = self.get_active() #        if not active: #            return self.__parts[0] #        i = self.__parts.index(active)+1 #        if i > len(self.__parts)-1: #            i = 0 #        return self.__parts[i] #    def get_right_part(self): #        active = self.get_active() #        if not active: #            return self.__parts[0] #        i = self.__parts.index(active)-1 #        if i < 0: #            i = len(self.__parts)-1 #        return self.__parts[i] # calc draw_area # begin scroll animation #print 'The first part is sacred ;)' # remove part from interal part list # clean up any exisitng scroll callbacks # the basics # determin part shapes, and calc modified parts widths # set the position of new part # check parts fit to widgets allocated width #    def __shorten(self, n): #        n = int(n) #        old_w = self.__draw_width() #        end_active = self.get_active() == self.__parts[-1] #        if len(self.__parts)-n < 1: #            print WARNING + 'The first part is sacred ;)' + ENDC #            return old_w, False #        del self.__parts[-n:] #        self.__compose_parts(self.__parts[-1], False) #        if end_active: #            self.set_active(self.__parts[-1]) #        if old_w >= self.allocation.width: #            self.__grow_check(old_w, self.allocation) #            return old_w, True #        return old_w, False # left align parts # interval # 1 over duration (converted to seconds) # final frame # redraw the entire widget # incase some timeouts are skipped due to high system load # draw the last two parts # style theme stuff # draw part that need scrolling # draw the last part that does not scroll # if a scroll is pending we want to not draw the final part, # as we don't want to prematurely reveal the part befor the # scroll animation has had a chance to start # draw bg # determine left margin.  left margin depends on part shape # and whether there exists an icon or not # draw icon # if space is limited and an icon is set, dont draw label # otherwise, draw label # clip area # draw bg # determine left margin.  left margin depends on part shape # and whether there exists an icon or not # draw icon # if space is limited and an icon is set, dont draw label # otherwise, draw label # outer slight bevel or focal highlight # colour scheme dicts # bg linear vertical gradient # strong outline # inner bevel/highlight # arrow head # arrow head # returns the idle state of the part depending on # whether part is active or not. # only show a tooltip if part is truncated, i.e. not all label text is # visible. # callback allows the tooltip position to be updated as pointer moves # accross different parts #print "No styling hints for %s are available" % name #    def __key_release_cb(self, widget, event): #        part = None #        # left key pressed #        if event.keyval == 65363: #            part = self.get_left_part() #        # right key pressed #        elif event.keyval == 65361: #            part = self.get_right_part() #        if not part: return #        prev_active = self.set_active(part) #        self.queue_draw_area(*part.allocation) #        if prev_active: #            self.queue_draw_area(*prev_active.allocation) #        part.emit("clicked", event.copy()) #        return # when alloc.width == 1, this is typical of an unallocated widget, # lets not break a sweat for nothing... # set height to 0 so that if part height has been reduced the widget will # shrink to an appropriate new height based on new font size # recalc best fits, re-append then draw all # escape special characters # some hackery to preserve italics markup # determine widget size base on label width # calc text width + 2 * padding, text height + 2 * ypadding # if has icon add some more pixels on # extend width depending on part shape  ... # if height greater than current height request, # reset height request to higher value # i get the feeling this should be in set_size_request(), but meh # set layout width Add a new button with the given label/callback          If there is the same id already, replace the existing one         with the new one #ignoring duplicate #print "Adding %s(%d)" % (label, id) # check if we have the button of that id or need a new one # We already have the first item, just don't do anything remove all elements return the button for the given id (or None) Return the label of the navigation button with the given id