import gobject
import gtk
from gtk import gdk
import gtkimageview
import time

def test_version_string():
    '''
    The module has a __version__ attribute which is a string
    containing three numbers separted by periods. The version string
    is >= 1.0.0.
    '''
    major, minor, micro = gtkimageview.__version__.split('.')
    major, minor, micro = int(major), int(minor), int(micro)
    assert major >= 1
    if major == 1:
        assert minor >= 0

def test_default_tool():
    '''
    The default tool is ImageToolDragger.
    '''
    view = gtkimageview.ImageView()
    assert isinstance(view.get_tool(), gtkimageview.ImageToolDragger)

def test_set_wrong_pixbuf_type():
    '''
    A TypeError is raised when set_pixbuf() is called with something
    that is not a pixbuf.
    '''
    view = gtkimageview.ImageView()
    try:
        view.set_pixbuf('Hi mom!', True)
        assert False
    except TypeError:
        assert True

def set_pixbuf_null():
    view = gtkimageview.ImageView()
    view.set_pixbuf(None, True)
    assert not view.get_pixbuf()

def test_set_pixbuf_default():
    '''
    Make sure that set_pixbuf():s second parameter has the default
    value True.
    '''
    view = gtkimageview.ImageView()
    view.set_fitting(False)
    view.set_pixbuf(None)
    assert view.get_fitting()

def check_class(parent, init_args):
    class TheClass(parent):
        __gsignals__ = {'my-signal' : (gobject.SIGNAL_RUN_FIRST,
                                       gobject.TYPE_NONE,
                                       (gobject.TYPE_INT,))}
        def __init__(self):
            parent.__init__(self, *init_args)
            self.arg = 0
        def do_my_signal(self, arg):
            self.arg = arg
    gobject.type_register(TheClass)
    obj = TheClass()
    obj.emit('my-signal', 20)
    assert obj.arg == 20

def test_nav_subclass_with_signals():
    '''
    Ensure that a subclass of ImageNav which adds a signal to the
    class works as expected.
    '''
    check_class(gtkimageview.ImageNav, [gtkimageview.ImageView()])

def test_view_subclass_with_signals():
    '''
    Ensure that a subclass of ImageView which adds a signal to the
    class works as expected.
    '''
    check_class(gtkimageview.ImageView, [])

def test_selector_subclass_with_signals():
    '''
    Ensure that a subclass of ImageToolSelector which adds a signal to
    the class works as expected.
    '''
    check_class(gtkimageview.ImageToolSelector, [gtkimageview.ImageView()])

def test_dragger_subclass_with_signals():
    '''
    Ensure that a subclass of ImageToolDragger which adds a signal to
    the class works as expected.
    '''
    check_class(gtkimageview.ImageToolDragger, [gtkimageview.ImageView()])

def test_scroll_win_subclass_with_signals():
    '''
    Ensure that a subclass of ImageScrollWin which adds a signal to
    the class works as expected.
    '''
    check_class(gtkimageview.ImageScrollWin, [gtkimageview.ImageView()])

def test_min_max_zoom_functions():
    '''
    Ensure that the gtkimageview.zooms_* functions are present and
    works as expected.
    '''
    min_zoom = float(gtkimageview.zooms_get_min_zoom())
    max_zoom = float(gtkimageview.zooms_get_max_zoom())
    assert min_zoom < max_zoom

def test_get_viewport():
    '''
    Ensure that getting the viewport of the view works as expected.
    '''
    view = gtkimageview.ImageView()
    assert not view.get_viewport()

    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view.set_pixbuf(pixbuf)

    rect = view.get_viewport()
    assert rect.x == 0 and rect.y == 0
    assert rect.width == 50 and rect.height == 50

def test_get_viewport_unallocated():
    '''
    If the view is not allocated, get_viewport returns a rectangle
    with 0 width and height.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view = gtkimageview.ImageView()
    view.set_pixbuf(pixbuf)

    for ofs_x, ofs_y in [(30, 30), (10, 20), (5, 10)]:
        view.set_offset(ofs_x, ofs_y)
        rect = view.get_viewport()
        assert rect.x == ofs_x
        assert rect.y == ofs_y
        assert rect.width == 0
        assert rect.height == 0

def test_get_check_colors():
    '''
    Ensure that getting the view:s check colors works as expected.
    '''
    view = gtkimageview.ImageView()
    col1, col2 = view.get_check_colors()
    assert int(col1)
    assert int(col2)

def test_get_check_colors_many_args():
    '''
    Ensure that a correct error is thrown when get_check_colors() is
    invoked with to many arguments.
    '''
    view = gtkimageview.ImageView()
    try:
        view.get_check_colors(1, 2, 3)
        assert False
    except TypeError:
        assert True

def test_image_nav_wrong_nr_args():
    '''
    Ensure that TypeError is raised when ImageNav is instantiated with
    the wrong nr of args.
    '''
    try:
        nav = gtkimageview.ImageNav()
        assert False
    except TypeError:
        assert True
    try:
        nav = gtkimageview.ImageNav(gtkimageview.ImageView(), None, None)
        assert False
    except TypeError:
        assert True

def test_get_draw_rect():
    '''
    Ensure that getting the draw rectangle works as expected.
    '''
    view = gtkimageview.ImageView()
    assert not view.get_draw_rect()

    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view.set_pixbuf(pixbuf)

    rect = view.get_draw_rect()
    assert rect.x == 25 and rect.y == 25
    assert rect.width == 50 and rect.height == 50

def test_draw_rect_unallocated():
    '''
    Ensure that get_draw_rect() always return a zero rectangle when
    the view is not allocated.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view = gtkimageview.ImageView()
    view.set_pixbuf(pixbuf)
    for x_ofs, y_ofs in [(30, 30), (-10, 20), (0, 0), (5, 10)]:
        view.set_offset(x_ofs, y_ofs)
        rect = view.get_draw_rect()
        assert rect.x == 0
        assert rect.y == 0
        assert rect.width == 0
        assert rect.height == 0

def test_set_offset():
    '''
    Ensure that setting the offset works as expected.
    '''
    view = gtkimageview.ImageView()
    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 200, 200)
    view.set_pixbuf(pixbuf)
    view.set_zoom(1)

    view.set_offset(0, 0)
    rect = view.get_viewport()
    assert rect.x == 0 and rect.y == 0

    view.set_offset(100, 100, invalidate = True)
    rect = view.get_viewport()
    assert rect.x == 100 and rect.y == 100
    
def test_set_transp():
    '''
    Ensure that setting the views transparency settings works as
    expected.
    '''
    view = gtkimageview.ImageView()
    view.set_transp(gtkimageview.TRANSP_COLOR, transp_color = 0xff0000)
    col1, col2 = view.get_check_colors()
    assert col1 == col2 == 0xff0000
    
    view.set_transp(gtkimageview.TRANSP_GRID)

def test_presence_of_constants():
    '''
    Check that all enum constants exist in the module.
    '''
    assert hasattr(gtkimageview, 'TRANSP_BACKGROUND')
    assert hasattr(gtkimageview, 'TRANSP_COLOR')
    assert hasattr(gtkimageview, 'TRANSP_GRID')

    assert hasattr(gtkimageview, 'DRAW_METHOD_CONTAINS')
    assert hasattr(gtkimageview, 'DRAW_METHOD_SCALE')
    assert hasattr(gtkimageview, 'DRAW_METHOD_SCROLL')

def test_incomplete_iimage_tool():
    '''
    Ensure that NotImplementedError is raised if an attempt is made to
    instantiate an incomplete IImageTool.
    '''
    class Foo(gtkimageview.IImageTool):
        pass
    try:
        Foo()
        assert False
    except NotImplementedError:
        assert True

def test_pixbuf_draw_opts():
    '''
    Ensure that the PixbufDrawOpts class is present.
    '''
    assert hasattr(gtkimageview, 'PixbufDrawOpts')

def test_pixbuf_draw_cache():
    '''
    Ensure that the PixbufDrawCache class is present.
    '''
    assert hasattr(gtkimageview, 'PixbufDrawCache')

def test_pixbuf_draw_opts_attrs():
    '''
    Ensure that all required attributes are present on the
    PixbufDrawOpts object.
    '''
    obj = gtkimageview.PixbufDrawOpts()
    assert hasattr(obj, 'zoom')
    assert hasattr(obj, 'zoom_rect')
    assert hasattr(obj, 'widget_x')
    assert hasattr(obj, 'widget_y')
    assert hasattr(obj, 'interp')
    assert hasattr(obj, 'pixbuf')
    assert hasattr(obj, 'check_color1')
    assert hasattr(obj, 'check_color2')

def test_pixbuf_draw_cache_attrs():
    '''
    Ensure that all required attributes are present on the
    PixbufDrawCache object.
    '''
    obj = gtkimageview.PixbufDrawCache()
    assert hasattr(obj, 'last_pixbuf')
    assert hasattr(obj, 'old')
    assert hasattr(obj, 'check_size')
    assert callable(obj.draw)
    

def test_get_draw_method():
    '''
    Sanity test for the PixbufDrawCache.get_method() classmethod.
    '''
    obj = gtkimageview.PixbufDrawCache()
    assert hasattr(obj, 'get_method')
    opts = gtkimageview.PixbufDrawOpts()
    gtkimageview.PixbufDrawCache.get_method(opts, opts)

def test_return_of_get_draw_method():
    '''
    Ensure that PixbufDrawCache.get_method() returns either
    DRAW_METHOD_CONTAINS, DRAW_METHOD_SCALE or DRAW_METHOD_SCROLL.
    '''
    obj = gtkimageview.PixbufDrawCache()
    opts = gtkimageview.PixbufDrawOpts()
    ret = obj.get_method(opts, opts)
    assert ret in (gtkimageview.DRAW_METHOD_CONTAINS,
                   gtkimageview.DRAW_METHOD_SCALE,
                   gtkimageview.DRAW_METHOD_SCROLL)

def test_type_error_for_draw_method():
    '''
    Ensure that TypeError is raised if PixbufDrawCache.get_method() is
    called with an argument that is not a PixbufDrawOpts object.
    '''
    arg_pairs = [(None, None),
                 (gtkimageview.PixbufDrawOpts(), None),
                 (None, gtkimageview.PixbufDrawOpts()),
                 ("Hello", "Foo")]
    for last_opts, new_opts in arg_pairs:
        try:
            gtkimageview.PixbufDrawCache.get_method(last_opts, new_opts)
            assert False
        except TypeError:
            assert True

def test_invalidate():
    '''
    Sanity test for the PixbufDrawCache.invalidate() method.
    '''
    cache = gtkimageview.PixbufDrawCache()
    assert hasattr(cache, 'invalidate')

def test_library_verson():
    '''
    Ensure sanity of the library_version() function.
    '''
    version = gtkimageview.library_version()
    maj, min, mic = version.split('.')
    digits = int(maj), int(min), int(mic)

def test_tool_selector_get_selection():
    '''
    Ensure that the default selection rectangle is (0,0)-[0,0].
    '''
    view = gtkimageview.ImageView()
    tool = gtkimageview.ImageToolSelector(view)
    view.set_tool(tool)
    sel = tool.get_selection()
    assert sel.x == 0 and sel.y == 0
    assert sel.width == 0 and sel.height == 0

def test_set_anim_none():
    '''
    gtkimageview.AnimView.set_anim can be called with None.
    '''
    view = gtkimageview.AnimView()
    view.set_anim(None)

def test_damage_pixels():
    '''
    Ensure that gtkimageview.ImageView.damage_pixels can be called.
    '''
    view = gtkimageview.ImageView()
    view.damage_pixels(gdk.Rectangle(0, 0, 100, 100))
    view.damage_pixels(None)
    view.damage_pixels()
    view.damage_pixels(rect = gdk.Rectangle(0, 0, 100, 100))

def test_damage_pixels_badarg():
    '''
    Ensure that TypeError is raised if argument to
    gtkimageview.ImageView.damage_pixels is not None or a
    gdk.Rectangle.
    '''
    view = gtkimageview.ImageView()
    try:
        view.damage_pixels('hello')
        assert False
    except TypeError:
        assert True

def test_widget_to_image_rect():
    '''
    Tests that gtkimageview.ImageView.widget_to_image_rect works.
    '''
    view = gtkimageview.ImageView()
    rect = gdk.Rectangle(0, 0, 20, 20)
    assert not view.widget_to_image_rect(rect)

    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    assert not view.widget_to_image_rect(rect)

    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view.set_pixbuf(pixbuf)

    r = view.widget_to_image_rect(gdk.Rectangle(25, 25, 50, 50))
    assert r.x == 0
    assert r.y == 0
    assert r.width == 50
    assert r.height == 50

def test_image_to_widget_rect():
    '''
    Test that gtkimageview.ImageView.image_to_widget_rect works.
    '''
    view = gtkimageview.ImageView()
    rect = gdk.Rectangle(0, 0, 50, 50)
    assert not view.image_to_widget_rect(rect)

    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 50, 50)
    view.set_pixbuf(pixbuf)

    r = view.image_to_widget_rect(rect)
    assert r.x == 25
    assert r.y == 25
    assert r.width == 50
    assert r.height == 50

def test_image_to_widget_less_than_1_size():
    '''
    If the width or the height of the image space rectangle occupies
    less than one widget space pixel, then it is rounded up to 1.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 100, 100)
    view = gtkimageview.ImageView()
    view.size_allocate(gdk.Rectangle(0, 0, 100, 100))
    view.set_pixbuf(pixbuf)
    view.set_zoom(0.5)

    rect = gdk.Rectangle(0, 0, 1, 1)
    r = view.image_to_widget_rect(rect)
    assert r.x == 25
    assert r.y == 25
    assert r.width == 1
    assert r.height == 1

def test_big_image_small_allocation():
    '''
    This is a test for #23. If it eats up all memory it is a failure.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 5000, 5000);

    alloc = gdk.Rectangle(0, 0, 5, 5)

    view = gtkimageview.ImageView()
    view.set_pixbuf(pixbuf)
    view.set_show_frame(False)
    view.window = gdk.Window(None,
                             alloc.width, alloc.height,
                             gdk.WINDOW_TOPLEVEL,
                             0,
                             gdk.INPUT_OUTPUT)
    view.size_allocate(alloc)
    ev = gdk.Event(gdk.EXPOSE)
    ev.area = alloc
    view.emit('expose-event', ev)

def test_zoom_to_fit_keybinding():
    '''
    Ensure that the 'x' keybinding works as expected.
    '''
    view = gtkimageview.ImageView()
    view.set_fitting(False)
    gtk.bindings_activate(view, gtk.keysyms.x, 0)
    assert view.get_fitting()

def test_step_on_non_anim():
    '''
    Ensure that calling ``gtkimageview.AnimView.step()`` works as
    expected when the view shows a static image.
    '''
    anim = gdk.PixbufSimpleAnim(100, 100, 10)
    anim.add_frame(gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 5000, 5000))

    view = gtkimageview.AnimView()
    view.set_anim(anim)
    view.step()

def test_unload_animation():
    '''
    Ensure that a running animation can be unloaded. Tests #34.
    '''
    # Flush the event queue.
    while gtk.events_pending():
        gtk.main_iteration(True)
    anim = gdk.PixbufSimpleAnim(100, 100, 10)
    for x in range(100):
        pb = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 300, 300)
        anim.add_frame(pb)
    view = gtkimageview.AnimView()
    view.set_anim(anim)
    assert view.get_pixbuf()
    for x in range(10):
        gtk.main_iteration(True)
    view.set_anim(None)

    # No further events for 1 second and no pixbuf in the view.    
    start = time.time()
    while time.time() < start + 1:
        assert not gtk.events_pending()
        assert not view.get_pixbuf()
        gtk.main_iteration(False)

def test_step_on_last_frame_of_anim():
    '''
    Ensure that calling ``gtkimageview.AnimView.step()`` on the last
    frame of the animation that the view shows works as expected.
    '''
    anim = gdk.PixbufSimpleAnim(100, 100, 10)
    for x in range(2):
        anim.add_frame(gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 5000, 5000))

    view = gtkimageview.AnimView()
    view.set_anim(anim)
    for x in range(2):
        view.step()

def test_step_on_fast_player():
    '''
    Ensure that calling step always advances the frame even if the
    animation is one of those that play to fast.
    '''
    # 50 fps = 20ms delay -> will be delayed
    anim = gdk.PixbufSimpleAnim(100, 100, 50)
    for x in range(10):
        anim.add_frame(gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 400, 300))

    view = gtkimageview.AnimView()
    view.set_anim(anim)
    for x in range(9):
        pb_old = view.get_pixbuf()
        view.step()
        pb_new = view.get_pixbuf()
        assert pb_old != pb_new

def test_zoom_in_destroyed_window():
    '''
    This test exposes a bug in GtkRange which causes a segfault when
    the window the ``gtkimageview.ImageScrollWin`` widget is in, is
    destroyed. Unfortunately it will never be fixed, see #551317.
    '''
    # view = gtkimageview.ImageView()
    # view.set_pixbuf(gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 500, 500))
    # scroll = gtkimageview.ImageScrollWin(view)

    # win = gtk.Window()
    # win.add(scroll)
    # win.set_default_size(100, 100)
    # win.show_all()

    # win.destroy()
    #view.set_zoom(3.0)

def test_return_of_motion_notify():
    '''
    Ensure that all tools returns True if it handled a motion
    notify event and False otherwise.
    '''
    # The pixbuf is larger than the view and should be draggable.
    view = gtkimageview.ImageView()
    view.size_allocate((0, 0, 50, 50))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 100, 100)
    view.set_pixbuf(pixbuf)
    view.set_zoom(1.0)

    button_ev = gdk.Event(gdk.BUTTON_PRESS)
    button_ev.x = 10.0
    button_ev.y = 10.0
    button_ev.button = 1
    button_ev.window = window = gdk.Window(None,
                                           100, 100,
                                           gdk.WINDOW_TOPLEVEL,
                                           0,
                                           gdk.INPUT_OUTPUT,
                                           x = 100, y = 100)

    motion_ev = gdk.Event(gdk.MOTION_NOTIFY)
    motion_ev.x = 20.0
    motion_ev.y = 20.0

    for tool_cls in (gtkimageview.ImageToolDragger,
                     gtkimageview.ImageToolSelector):
        tool = tool_cls(view)

        # Simulate a dragging motion. Left mouse button is pressed
        # down at (10, 10) and then moved to (20, 20).
        tool.do_button_press(tool, button_ev)
        assert tool.do_motion_notify(tool, motion_ev)

def test_return_of_button_release():
    '''
    Ensure that all tools return True if it released the grab in
    response to a button release event.
    '''
    # The pixbuf is larger than the view and should be draggable.
    view = gtkimageview.ImageView()
    view.size_allocate((0, 0, 50, 50))
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 100, 100)
    view.set_pixbuf(pixbuf)
    view.set_zoom(1.0)

    press_ev = gdk.Event(gdk.BUTTON_PRESS)
    press_ev.x = 10.0
    press_ev.y = 10.0
    press_ev.button = 1
    press_ev.window = window = gdk.Window(None,
                                          100, 100,
                                          gdk.WINDOW_TOPLEVEL,
                                          0,
                                          gdk.INPUT_OUTPUT,
                                          x = 100, y = 100)

    rel_ev = gdk.Event(gdk.BUTTON_RELEASE)
    rel_ev.button = 1
    for tool_cls in (gtkimageview.ImageToolDragger,
                     gtkimageview.ImageToolSelector):
        tool = tool_cls(view)
        tool.do_button_press(tool, press_ev)
        assert tool.do_button_release(tool, rel_ev)

class DummyTool(gobject.GObject, gtkimageview.IImageTool):
    def __init__(self):
        gobject.GObject.__init__(self)
        self.zoom_rect = None
    def do_button_press(self, ev_button):
        pass
    def do_button_release(self, ev_button):
        pass
    def do_motion_notify(self, ev_motion):
        pass
    def do_pixbuf_changed(self, reset_fit, rect):
        pass
    def do_paint_image(self, opts, drawable):
        self.zoom_rect = opts.zoom_rect
    def do_cursor_at_point(self, x, y):
        pass
gobject.type_register(DummyTool)

def test_correct_repaint_offset():
    '''
    Ensure that there is no off by one error when repainting.

    A 1600*1600 pixbuf viewed in a 700*700 image view widget at zoom
    1.0 should be perfectly centered so that draw starts at
    coordinates 450, 450. However, there may be a mishandled floating
    point conversion which causes the draw to start at 449, 449. See
    #31.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 1600, 1600)
    tool = DummyTool()

    view = gtkimageview.ImageView()
    view.set_tool(tool)
    view.set_show_frame(False)
    view.set_pixbuf(pixbuf)
    view.size_allocate((0, 0, 700, 700))
    view.window = gdk.Window(None,
                             700, 700,
                             gdk.WINDOW_TOPLEVEL,
                             0,
                             gdk.INPUT_OUTPUT)
    view.set_zoom(1.0)
    ev = gdk.Event(gdk.EXPOSE)
    ev.area = view.allocation
    view.do_expose_event(view, ev)
    assert tool.zoom_rect.x == 450
    assert tool.zoom_rect.y == 450

def test_scrolling_offbyone():
    '''
    Ensure that there is no off by one error when scrolling the view.

    The view is scrolled two pixels in the vertical direction, the
    result should be that the tool is asked to draw two new horizontal
    lines of the pixbuf. This is the other problem in bug #31.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 1600, 1600)
    tool = DummyTool()

    view = gtkimageview.ImageView()
    view.set_tool(tool)
    view.set_show_frame(False)
    view.set_pixbuf(pixbuf)
    view.size_allocate((0, 0, 700, 700))
    view.window = gdk.Window(None,
                             700, 700,
                             gdk.WINDOW_TOPLEVEL,
                             0,
                             gdk.INPUT_OUTPUT)
    view.set_zoom(1.0)
    view.set_offset(450.0, 448.0)
    assert tool.zoom_rect == gdk.Rectangle(450, 448, 700, 2)
    view.set_offset(448.0, 450.0)

def test_scrolling_adjustments_offbyone():
    '''
    Ensure that there is no off by one error when scrolling the view
    using the horizontal adjustment.

    First the view is scrolled two small steps, then the same distance
    in one go. In each case, the same number of pixels should be
    painted by the tool. The steps include numbers with the fractional
    part >= 0.5 to test the rounding.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 1600, 1600)
    hadj = gtk.Adjustment()
    vadj = gtk.Adjustment()
    tool = DummyTool()
    view = gtkimageview.ImageView()
    view.set_show_frame(False)
    view.set_tool(tool)
    view.set_scroll_adjustments(hadj, vadj)
    view.set_pixbuf(pixbuf)
    view.size_allocate((0, 0, 700, 700))
    view.window = gdk.Window(None,
                             700, 700,
                             gdk.WINDOW_TOPLEVEL,
                             0,
                             gdk.INPUT_OUTPUT)
    view.set_zoom(3.0)

    pix_drawn = 0
    hadj.value = 2050.0
    hadj.value_changed()
    for ofs in [2073.71, 2088.41]:
        hadj.value = ofs
        hadj.value_changed()
        pix_drawn += tool.zoom_rect.width

    hadj.value = 2050
    hadj.value_changed()

    hadj.value = 2088.41
    hadj.value_changed()
    assert tool.zoom_rect.width == pix_drawn

    
def test_setting_float_offsets_offbyone():
    '''
    Another test for #31.
    '''
    pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, 1600, 1600)
    hadj = gtk.Adjustment()
    vadj = gtk.Adjustment()
    tool = DummyTool()
    view = gtkimageview.ImageView()
    view.set_show_frame(False)
    view.set_tool(tool)
    view.set_scroll_adjustments(hadj, vadj)
    view.set_pixbuf(pixbuf)
    view.size_allocate((0, 0, 700, 700))
    view.window = gdk.Window(None,
                             700, 700,
                             gdk.WINDOW_TOPLEVEL,
                             0,
                             gdk.INPUT_OUTPUT)
    view.set_zoom(3.0)

    view.set_offset(2050.0, 2050.0)
    pix_drawn = 0
    for ofs in [2073.71, 2088.41]:
        view.set_offset(ofs, 2050.0)
        pix_drawn += tool.zoom_rect.width

    view.set_offset(2050.0, 2050.0)
    view.set_offset(2088.41, 2050)
    assert tool.zoom_rect.width == pix_drawn
    
