#! /usr/bin/env python
import sys;

"""
A scrollbar for urwid.

Author: Rebecca Breu (rebecca@rbreu.de)
License: GPL
"""

import math;

import urwid;
import urwid.util;
import urwid.raw_display;
import urwid.canvas;


######################################################################
#Some stuff for old Python:

try:
    sum([1]);  #Since 2.3
except NameError:
    
    def sum(sequence):
        def add(x, y):
            return x + y
        
        return reduce(add, sequence, 0)


try:
    True, False; #Since 2.3
except NameError:
    False = (1==2);
    True = (1==1);


try:
    enumerate([]);  #Since 2.3
    
except NameError:
    
    def enumerate(llist):
        """
        This is only for lists!
        """
        i = 0;
        enum = [];
        for entry in llist:
            enum.append((i, entry))
            i += 1;

        return enum;

    
######################################################################
def hamilton_allocation(counts, alloc):
    """
    Implements the Hamilton Method (Largest remainder method) to calculate
    integral ratios. (Like it is used in some elections.)

    counts -- list of integers ('votes per party')
    alloc -- total amount to be allocated ('total amount of seats')
    """

    total_counts = sum(counts);
    quotas = [float(count)*alloc/total_counts for count in counts]

    fracts = []
    for (i, fp) in enumerate(quotas):
        fracts.append((math.modf(fp)[0], i))
        
    fracts.sort()
    fracts.reverse()

    results = [int(math.modf(quota)[1]) for quota in quotas]    
    remainder = alloc - sum(results)

    for i in range(remainder):
        results[fracts[i][1]] += 1;
            
    return results        


######################################################################
class ScrollBar(urwid.BoxWidget):
    """
    A scrollbar. 
    """
    
    def __init__(self, handle, background, middle, top, bottom):
        """
        handle -- (character, attribute) for the drawing of the scrollbar's
                  handle
        background -- (character, attribute) for the drawing of the background
        middle -- number of visible rows of the corresponding widget
        top -- number of lines above visible rows of the corresponding widget
        bottom -- number of lines below visible rows of the corresponding
                  widget
        """

 	handle_canvas = urwid.TextCanvas(handle[0])
        self.handle_char = handle_canvas._text
        self.handle_attr = handle[1]
        self.handle_charset = handle_canvas._cs
#        handle_canvas = urwid.Text(handle[0]).render((1,))
#        self.handle_char = handle_canvas.text[0];
#        self.handle_attr = handle[1];
#        self.handle_charset = handle_canvas.cs[0][0][0];

        background_canvas = urwid.TextCanvas(background[0])
        self.background_char = background_canvas._text
        self.background_attr = background[1]
        self.background_charset = background_canvas._cs
        #background_canvas = urwid.Text(background[0]).render((1,))
        #self.background_char = background_canvas._text[0];
        #self.background_attr = background[1];
        #self.background_charset = background_canvas._cs[0][0][0];

        self.middle = middle;
        self.top = top;
        self.bottom = bottom;

        self.handle_grab_pos = -1;
        self.handle_moved = 0;


    def selectable(self):
        "Not selectable."""
        return False;


    def mouse_event(self, (maxcol, maxrow), event, button, col, row, focus):
        """
        Handle mouse events.
        """

        (middle, top, bottom) = hamilton_allocation([self.middle, self.top,
                                                     self.bottom], maxrow);

        if (event == "mouse press" and button == 1 and row >= top and
            row < middle + top):

            #User grabs handle with left mouse button
            self.handle_grab_pos = row;
            return True;
        
        elif self.handle_grab_pos >= 0 and event == "mouse release":
            #User releases handle
            self.handle_moved += row - self.handle_grab_pos;
            self.handle_grab_pos = -1;
            return True;

        else:
            return False;
        
        
    def render(self, (maxcol, maxrow), focus=False):
        """
        Render the ScrollBar as a canvas and return it.
        """

        (middle, top, bottom) = hamilton_allocation([self.middle, self.top,
                                                     self.bottom], maxrow);

        text_list = [self.background_char] * top + \
                    [self.handle_char] * middle + \
                    [self.background_char] * bottom;

        #Length of chars maybe > 1 for escape sequences
        handle_rl = len(self.handle_char);
        background_rl = len(self.background_char);

        attr_list = [[(self.background_attr, background_rl)] \
                     for i in range(top)] + \
                    [[(self.handle_attr, handle_rl)] \
                     for i in range(middle)] + \
                    [[(self.background_attr, background_rl)] \
                     for i in range(bottom)]

        charset_list = [[(self.background_charset,  background_rl)] \
                        for i in range(top)] + \
                       [[(self.handle_charset, handle_rl)] \
                        for i in range(middle)] + \
                       [[(self.background_charset,  background_rl)] \
                        for i in range(bottom)]

            
	return urwid.TextCanvas(text_list, attr_list, charset_list)
#        return urwid.Canvas(text_list, attr_list, charset_list)


    def read_move(self, maxrow, length):
        """
        Updates the position of the scrollbar and returns the amount of lines
        the corresponding widget has to be scrolled. 

        maxrow -- number of displayed rows of the widget to which the
                  scrollbar belongs
        length -- overall length of the widgets list of that widget

        """

        moved = int(float(self.handle_moved)/self.middle * length);
        self.handle_moved = 0;

        #Ensure that we stay in a correct range:
        if self.top + moved < 0:
            moved -= self.top + moved;
        
        if self.bottom - moved < 0:
            moved += self.bottom - moved;
        
        self.top += moved;
        self.bottom -= moved;

        return moved;
    

######################################################################
class SBListBox(urwid.WidgetWrap):
    """
    A ListBox with a scroll bar.
    """

    def __init__(self, body, handle, background):
        """
        body -- list or a SimpleListWalker object that contains the
                widgets to be displayed inside the list box. 
                The __len__ attribute must be implemented!
        handle -- (character, attribute) for the drawing of the scrollbar's
                  handle
        background -- (character, attribute) for the drawing of the scrollbar's
                      background
        """

        self.length = len(body);
        self.listbox = urwid.ListBox(body);
        self.scrollbar = ScrollBar(handle, background, 1, 0, 0);
        self.columns = urwid.Columns([("weight", 1, self.listbox),
                                      ("fixed", 1, self.scrollbar)]);
        
        urwid.WidgetWrap.__init__(self, self.columns);


    def calc_sb_midtopbot(self, size):
        """
        Calculate middle, top, bottom for the scrollbar.
        """
        
        (middle, top, bottom) = self.listbox.calculate_visible(size);

        self.scrollbar.middle = min(size[1], self.length);
        self.scrollbar.top = middle[2] - len(top[1]);
        self.scrollbar.bottom = self.length - middle[2] - len(bottom[1]) - 1;
        

    def render(self, size, focus=False):
        """
        Render the SBListBox as a canvas and return it.
        """
        
        self.calc_sb_midtopbot(size);
        return self.columns.render(size, focus);


    def mouse_event(self, size, event, button, col, row, focus):
        """
        Handle mouse events.
        """
        
        if event == "mouse release" and self.scrollbar.handle_grab_pos >= 0:
            #If the handle is grabbed, pretend that every release is over the
            #scrollbar:
            col = size[0] - 1;
            
        handled = self.columns.mouse_event(size, event, button, col, row,
                                           focus);

        #Scroll the listbox according to handle movement:
        moved = self.scrollbar.read_move(size[1], self.length);
        
        position = self.listbox.get_focus()[1];
        (offset, inset) = self.listbox.get_focus_offset_inset(size);
        
        assert (position + moved >= 0) or (position + moved <= self.length), \
               "Move out of list range: %i\n" % (position + moved)
        
        self.listbox.change_focus(size, position + moved, offset)


        return handled;
        

######################################################################
def run():
    global ui;

    ui.set_mouse_tracking()
    dim = ui.get_cols_rows();

    wlist = [urwid.AttrWrap(urwid.Edit(str(i)), None, "focused")
             for i in range(50)]
    listbox = SBListBox(wlist, (" ", "handle"), ("#", "scrollbar_bg"));
    #listbox = SBListBox(wlist, (" ", "handle"), (u"\u2592", "scrollbar_bg"));

    keys = True;

    #Main event loop:
    while True:
        if keys:
            canvas = listbox.render(dim,1)
            ui.draw_screen(dim, canvas)
           
        keys = ui.get_input()

        for k in keys:
            if urwid.is_mouse_event(k):
                event, button, col, row = k
                listbox.mouse_event(dim, event, button, col, row, focus=True)
            else:
                listbox.keypress(dim, k);
        
        if "window resize" in keys:
            dim = ui.get_cols_rows();
              
            
######################################################################
#Main part                                                           #
######################################################################
def main():
    global ui;
    ui = urwid.raw_display.Screen();

    ui.register_palette(
        [('focused', 'light red', 'default', 'standout'),
         ('handle', 'light cyan', 'dark cyan', 'standout'),
         ('scrollbar_bg', 'light gray', 'black', 'standout')]);
    
    ui.run_wrapper(run)


if __name__ == "__main__":
    main()
