""" * 2d_slider.py * widget to allow for 2D selection of values * * (C) 2008 by Kristian Mueller * All Rights Reserved * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ import os import gtk IMAGE_DIRECTORY = os.path.join(os.path.realpath(os.path.curdir), "./") POINT_IMAGE = os.path.join(IMAGE_DIRECTORY, "point.png") POINT_INV_IMAGE = os.path.join(IMAGE_DIRECTORY, "point_inv.png") IMAGE_SIZE_X = 10 IMAGE_SIZE_Y = 10 class slider_2d(gtk.HBox): """ Widget to allow for two dimensional selection of values """ def __init__(self, x_range = (0, 10), y_range = (0, 10), bg_color = "#222222", fg_color = "#555566"): gtk.HBox.__init__(self, False, 0) self.x_range = x_range self.y_range = y_range self.x_resolution = x_range[1] - x_range[0] self.y_resolution = y_range[1] - y_range[0] self.bg_color = bg_color self.fg_color = fg_color self.mark_color = "#AAAAAA" self.hover_color = "#555588" self.x_value = int(x_range[1] - x_range[0] / 2) self.y_value = int(x_range[1] - x_range[0] / 2) self.mouse_x_position = -1 self.mouse_y_position = -1 self.clicked = False self.area = gtk.DrawingArea() # self.area.set_size_request(400, 300) style = self.area.get_style() self.gc = style.fg_gc[gtk.STATE_NORMAL] self.area.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK ) self.area.add_events(gtk.gdk.BUTTON_MOTION_MASK | \ gtk.gdk.BUTTON_PRESS_MASK | \ gtk.gdk.BUTTON_RELEASE_MASK) self.area.connect("expose-event", self.area_expose_cb) self.area.connect("button-press-event", self.area_click_cb) self.area.connect("button-release-event", self.area_unclick_cb) self.area.connect("motion-notify-event", self.area_mousemove_cb) self.x_value_changed_callback = 0 self.y_value_changed_callback = 0 # define Horizontal and Vertical Rulers self.hruler = gtk.HRuler() self.vruler = gtk.VRuler() self.hruler.set_range(x_range[0], x_range[1], x_range[0], x_range[1]) self.vruler.set_range(y_range[0], y_range[1], y_range[0], y_range[1]) def motion_notify(ruler, event): return ruler.emit("motion_notify_event", event) self.area.connect_object("motion_notify_event", motion_notify, self.hruler) self.area.connect_object("motion_notify_event", motion_notify, self.vruler) def size_allocate_cb(wid, allocation): x, y, width, height = allocation lower, upper, position, max_size = self.hruler.get_range() self.hruler.set_range(lower, self.x_resolution, position, max_size) lower, upper, position, max_size = self.vruler.get_range() self.vruler.set_range(lower, self.y_resolution, position, max_size) self.width = width self.height = height self.area.connect('size-allocate', size_allocate_cb) self.table = gtk.Table(2,2) self.table.attach(self.hruler, 1, 2, 0, 1, yoptions=0) self.table.attach(self.vruler, 0, 1, 1, 2, xoptions=0) self.table.attach(self.area, 1, 2, 1, 2) self.add(self.table) def connect(self, event_name, callback): if event_name == "x_value_changed_event": self.x_value_changed_callback = callback elif event_name == "y_value_changed_event": self.y_value_changed_callback = callback else: super(slider_2d, self).connect(event_name, callback) ############################################################################ ## callbacks to draw 2D Slider ############################################# ############################################################################ def area_expose_cb(self, area, event): self.redraw() def area_click_cb(self, area, event): self.clicked = True self.put_value_point(area) def area_unclick_cb(self, area, event): self.clicked = False self.put_value_point(area) def area_mousemove_cb(self, area, event): pos = area.get_pointer() x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution # get position of point in grid x = int((pos[0] + x_spacing/2) / x_spacing) y = int((pos[1] + y_spacing/2) / y_spacing) if self.mouse_x_position != x or self.mouse_y_position != y: self.redraw_area(self.mouse_x_position, self.mouse_y_position) self.mouse_x_position = x self.mouse_y_position = y self.show_inv_point(x, y) if self.clicked: self.put_value_point(area) def put_value_point(self, area): """ actually setting a new value """ pos = area.get_pointer() x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution # get position of point in grid x = int((pos[0] + x_spacing/2) / x_spacing) y = int((pos[1] + y_spacing/2) / y_spacing) if (self.x_value != x or self.y_value != y) \ and x in range(self.x_range[0], self.x_range[1]+1)\ and y in range(self.y_range[0], self.y_range[1]+1): backup_x = self.x_value backup_y = self.y_value self.x_value = x self.y_value = y self.redraw_area(backup_x, backup_y) self.show_point(self.x_value, self.y_value) # here we call the callbacks! if backup_x != self.x_value: self.x_value_changed_callback(self.x_value) if backup_y != self.y_value: self.y_value_changed_callback(self.y_value) ############################################################################ ## actually writing to our area ############################################ ############################################################################ def redraw(self): """ writing all elements to drawing area (clears screen, draws grid, draws value) """ # get gc style = self.area.get_style() self.gc = style.fg_gc[gtk.STATE_NORMAL] # clear screen if self.gc: fg_backup = self.gc.foreground self.gc.foreground = self.area.window.get_colormap()\ .alloc_color(self.bg_color) # 1) clear screen self.area.window.draw_rectangle(self.gc, True, 0, 0, -1, -1) self.gc.foreground = fg_backup self.draw_grid() self.show_point(self.x_value, self.y_value) def draw_grid(self): backup_fb = self.gc.foreground try: self.set_color(self.fg_color) self.gc.set_line_attributes(1, \ gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, \ gtk.gdk.CAP_ROUND) # 2) draw grid x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution for i in range(self.x_resolution+2): if i == self.x_value: self.set_color(self.mark_color) self.area.window.draw_line(self.gc, int(x_spacing * i),\ 0, int(x_spacing * i), int(y_max)) self.set_color(self.fg_color) elif i == self.mouse_x_position: self.set_color(self.hover_color) self.area.window.draw_line(self.gc, int(x_spacing * i),\ 0, int(x_spacing * i), int(y_max)) self.set_color(self.fg_color) else: self.area.window.draw_line(self.gc, int(x_spacing * i),\ 0, int(x_spacing * i), int(y_max)) for i in range(self.y_resolution+2): if i == self.y_value: self.set_color(self.mark_color) self.area.window.draw_line(self.gc, 0, \ int(y_spacing * i), int(x_max), int(y_spacing * i)) self.set_color(self.fg_color) elif i == self.mouse_y_position: self.set_color(self.hover_color) self.area.window.draw_line(self.gc, 0, \ int(y_spacing * i), int(x_max), int(y_spacing * i)) self.set_color(self.fg_color) else: self.area.window.draw_line(self.gc, 0, \ int(y_spacing * i), int(x_max), int(y_spacing * i)) # redraw hover (was overdrawn my other lines) self.set_color(self.hover_color) self.area.window.draw_line(self.gc, \ int(x_spacing * self.mouse_x_position),\ 0, int(x_spacing * self.mouse_x_position), int(y_max)) self.area.window.draw_line(self.gc, 0, \ int(y_spacing * self.mouse_y_position), int(x_max), \ int(y_spacing * self.mouse_y_position)) # redraw value (was overdrawn my other lines) self.set_color(self.mark_color) self.area.window.draw_line(self.gc, \ int(x_spacing * self.x_value),\ 0, int(x_spacing * self.x_value), int(y_max)) self.area.window.draw_line(self.gc, 0, \ int(y_spacing * self.y_value), int(x_max), \ int(y_spacing * self.y_value)) finally: self.gc.foreground = backup_fb def set_color(self, color_string): self.gc.foreground = self.area.window.get_colormap()\ .alloc_color(color_string) def draw_pixmap(self, x, y, img): """ loads and draws image to our area """ pixmap, mask = gtk.gdk.pixmap_create_from_xpm(self.area.window, \ self.area.window.get_colormap().alloc_color(self.bg_color), img) self.area.window.draw_drawable(self.gc, pixmap, 0, 0, \ int(x-5), int(y-5), -1, -1) def show_point(self, x_value, y_value): """ shows the value-marker at a specific place """ x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution self.draw_pixmap(x_spacing * x_value, y_spacing * y_value, POINT_IMAGE) def show_inv_point(self, x_value, y_value): """ shows the inverted value-marker (mouse indicator) at a specific place """ x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution self.draw_grid() self.show_point(self.x_value, self.y_value) self.draw_pixmap(x_spacing * int(x_value), y_spacing * int(y_value),\ POINT_INV_IMAGE) def redraw_area(self, x_value, y_value): """ redraws the area of a value marker, without clearing out the whole screen - to prevent flickering """ x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution start_x = int((x_spacing * x_value) - (IMAGE_SIZE_X / 2)) start_y = int((y_spacing * y_value) - (IMAGE_SIZE_Y / 2)) fg_backup = self.gc.foreground self.gc.foreground = self.area.window.get_colormap()\ .alloc_color(self.bg_color) self.area.window.draw_rectangle(self.gc, True, \ start_x, start_y, IMAGE_SIZE_X, IMAGE_SIZE_Y) self.gc.foreground = fg_backup # easyest way for now: redraw all lines self.draw_grid() """ backup_fb = self.gc.foreground try: self.gc.foreground = self.area.window.get_colormap()\ .alloc_color(self.fg_color) self.gc.set_line_attributes(1, \ gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, \ gtk.gdk.CAP_ROUND) # 2) draw grid x_max = self.width * 1.0 -1 y_max = self.height * 1.0 -1 x_spacing = x_max / self.x_resolution y_spacing = y_max / self.y_resolution for i in range(self.x_resolution+2): self.area.window.draw_line(self.gc, int(x_spacing * i), 0, int(x_spacing * i), int(y_max)) for i in range(self.y_resolution+2): self.area.window.draw_line(self.gc, 0, int(y_spacing * i), int(x_max), int(y_spacing * i)) finally: self.gc.foreground = backup_fb """ self.show_point(self.x_value, self.y_value)