#!/usr/bin/env ruby
require 'gtk2'
require 'rational'
=begin
A drawing area, which can be panned and zoomed.
(requires rational)
Panning is done with middle mouse drag, zooming is with the scrollwheel.
To zoom only into x or y axis, hold down the shift or ctrl keys respectively.
You must create the following method:
expose_event event, cairo, coordinates
where
event is the expose event
cairo is the cairo context
coordinates is an array with the [xstart, ystart, xend, yend] coordinates
=end
class DragZoomDrawingArea < Gtk::DrawingArea
type_register
def initialize
super
@xscale = @yscale = 1.to_r
@xoffset = @yoffset = 0.to_r
@xscalestep = @yscalestep = 2.to_r
@drag_sig_handlers = []
signal_connect('expose-event') {|widget, event|
cairo = window.create_cairo_context
cairo.scale(@xscale, @yscale)
cairo.translate(@xoffset, @yoffset)
alloc = allocation
width, height = alloc.width, alloc.height
xstart = (0.to_r - @xoffset) / @xscale
xend = (width.to_r - @xoffset) / @xscale
ystart = (0.to_r - @yoffset) / @yscale
yend = (height.to_r - @yoffset) / @yscale
expose_event widget, event, cairo, [xstart, ystart, xend, yend]
true
}
signal_connect('scroll-event') {|widget, event|
do_xscale = do_yscale = true
if event.state & Gdk::Window::ModifierType::SHIFT_MASK != 0
do_yscale = false
end
if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0
do_xscale = false
end
xscale_old, yscale_old = @xscale, @yscale
case event.direction
when Gdk::EventScroll::DOWN
@xscale /= @xscalestep if do_xscale
@yscale /= @yscalestep if do_yscale
when Gdk::EventScroll::UP
@xscale *= @xscalestep if do_xscale
@yscale *= @yscalestep if do_yscale
end
@xoffset = event.x.to_i.to_r / @xscale - (event.x.to_i / xscale_old - @xoffset)
@yoffset = event.y.to_i.to_r / @yscale - (event.y.to_i / yscale_old - @yoffset)
queue_draw
}
@motion_cb = proc {|widget, event|
deltax, deltay = event.x.to_i.to_r - @startx.to_i, event.y.to_i.to_r - @starty.to_i
@xoffset += deltax / @xscale
@yoffset += deltay / @yscale
@startx, @starty = event.x, event.y
queue_draw
}
signal_connect('button-press-event') {|widget, event|
if(event.button == 2)
@startx, @starty = event.x, event.y
@drag_sig_handlers << signal_connect('motion-notify-event', &@motion_cb)
end
}
signal_connect('button-release-event') {|widget, event|
if(event.button == 2)
@drag_sig_handlers.reject!{|s| signal_handler_disconnect(s); true}
end
}
add_events(Gdk::Event::SCROLL_MASK |
Gdk::Event::BUTTON_PRESS_MASK |
Gdk::Event::BUTTON_RELEASE_MASK |
Gdk::Event::POINTER_MOTION_MASK)
end
end
class MyWindow < Gtk::Window
def initialize
super('interactive graph')
set_size_request(500,500)
signal_connect("delete-event") { Gtk.main_quit;true }
drawing_area = DragZoomDrawingArea.new
def drawing_area.expose_event widget, event, cairo, disp
cairo.set_source_rgb(1,0,0)
cairo.rectangle(100, 100, 50, 50)
cairo.stroke
end
add(drawing_area)
show_all
end
end
app = MyWindow.new
Gtk.main