#!/usr/bin/python #-*- coding:utf-8 -*- # ValueWheel.py # # Copyright (c) 2007 Marcelo Lira dos Santos # # Author: Marcelo Lira dos Santos # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser 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 Lesser General Public # License along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA import pygtk pygtk.require('2.0') import gtk import gobject import cairo import math class ValueWheel(gtk.DrawingArea): INT = 0 AUR = 1 CAR = 2 FOR = 3 FIS = 4 AGI = 5 PER = 6 __gsignals__ = { 'realize': 'override', 'expose-event' : 'override', 'button-press-event' : 'override', 'button-release-event' : 'override', 'motion-notify-event' : 'override', } def __init__(self, total_points=15, value_range=(-4, 6)): gtk.DrawingArea.__init__(self) self.add_events(gtk.gdk.BUTTON_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) self.drag = False self.attribute = 0 self.modifiers = [0, 0, 0, -2, 1, 2, 1] self.values = [0, 0, 0, 0, 0, 0, 0] self.labels = ['Int', 'Aur', 'Car', 'For', 'Fís', 'Agi', 'Per'] #self.modifLabels = ['(%+d)' % modif for modif in self.modifiers] self.label_radius = 20.0 self.min_value, self.max_value = value_range self.total_points = total_points gobject.signal_new('value-changed', ValueWheel, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) def do_realize(self): self.set_flags(self.flags() | gtk.REALIZED) events = gtk.gdk.EXPOSURE_MASK | gtk.gdk.BUTTON_PRESS_MASK \ | gtk.gdk.POINTER_MOTION_MASK self.window = gtk.gdk.Window(self.get_parent_window(), x=self.allocation.x, y=self.allocation.y, width=self.allocation.width, height=self.allocation.height, window_type=gtk.gdk.WINDOW_CHILD, wclass=gtk.gdk.INPUT_OUTPUT, visual=self.get_visual(), colormap=self.get_colormap(), event_mask=self.get_events() | events) self.window.set_user_data(self) self.style.attach(self.window) self.style.set_background(self.window, gtk.STATE_NORMAL) def do_expose_event(self, event): context = self.window.cairo_create() context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) context.clip() self.draw(context) return False def do_button_press_event(self, event): rect = self.get_allocation() x = rect.x + rect.width / 2 y = rect.y + rect.height / 2 radius = min(rect.width / 2, rect.height / 2) - 50 if event.type == gtk.gdk.BUTTON_PRESS: for i in xrange(0, 7): distance = (radius / (self.max_value - self.min_value)) * \ (self.values[i] + self.modifiers[i] - self.min_value) cx = x + distance * math.cos(i * math.pi / 3.5) cy = y + distance * math.sin(i * math.pi / 3.5) if math.hypot(cx - event.x, cy - event.y) < 4: self.drag = True self.attribute = i return True elif event.type == gtk.gdk.SCROLL: print 'scroll' return False def do_button_release_event(self, event): self.drag = False return False def do_motion_notify_event(self, event): if self.drag: rect = self.get_allocation() x = rect.x + rect.width / 2 y = rect.y + rect.height / 2 radius = min(rect.width / 2, rect.height / 2) - 50 distance = math.hypot(x - event.x, y - event.y) segment = radius / (self.max_value - self.min_value) point = int(round((distance / segment)) + self.min_value) if not self.values[self.attribute] == point: if point > self.modifiers[self.attribute] + 4 or \ point < self.modifiers[self.attribute] - 2 or \ point == self.values[self.attribute] + \ self.modifiers[self.attribute]: return self.values[self.attribute] = point - self.modifiers[self.attribute] self.emit('value-changed', self.values) alloc = self.get_allocation() rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height) self.window.invalidate_rect(rect, True) self.window.process_updates(True) return False def reset_values(self): self.values = [] def draw(self, context, redraw=False): rect = self.get_allocation() x = rect.x + rect.width / 2 y = rect.y + rect.height / 2 radius = min(rect.width / 2, rect.height / 2) - 50 external_radius = radius + self.label_radius - 4 total = self.calc_total() r, g, b = self.calc_polygon_color(total) # Show points context.set_font_size (16.0) context.set_source_rgb(r, g, b) context.move_to(6, 20) context.show_text('Pontos: %.1f / %d' % (total, self.total_points)) # wheel context.set_source_rgb(0.9, 0.9, 0.9) context.move_to(x + radius, y) for i in xrange(7): context.line_to(x + radius * math.cos(i * math.pi / 3.5), y + radius * math.sin(i * math.pi / 3.5)) context.close_path() context.fill_preserve() context.set_source_rgb(0.7, 0.7, 0.7) context.set_font_size (16.0) for i in xrange(7): context.move_to(x, y) context.line_to(x + radius * math.cos(i * math.pi / 3.5), y + radius * math.sin(i * math.pi / 3.5)) context.move_to(x - 10 + external_radius * math.cos(i * math.pi / 3.5), y + 5 + external_radius * math.sin(i * math.pi / 3.5)) context.show_text(self.labels[i]) context.stroke() # polygon context.save() context.set_source_rgba(r, g, b, 0.4) context.new_path() for i in xrange(0, 7): distance = (radius / (self.max_value - self.min_value)) * \ (self.values[i] + self.modifiers[i] - self.min_value) context.line_to(x + distance * math.cos(i * math.pi / 3.5), y + distance * math.sin(i * math.pi / 3.5)) context.fill() context.restore() context.close_path() # dots context.set_source_rgba(r, g, b, 0.8) for i in xrange(0, 7): distance = (radius / (self.max_value - self.min_value)) * \ (self.values[i] + self.modifiers[i] - self.min_value) context.arc(x + distance * math.cos(i * math.pi / 3.5), y + distance * math.sin(i * math.pi / 3.5), 4.2, 0, 2 * math.pi) context.fill() if self.values[i] < 1: rad = radius + 2 else: rad = radius - 10 distance = 5 + (rad / (self.max_value - self.min_value)) * \ (self.values[i] + self.modifiers[i] - self.min_value + 1) context.move_to(x - 5 + distance * math.cos((i + 0.15) * \ math.pi / 3.5), y + 7 + distance * math.sin((i + 0.15) * \ math.pi / 3.5)) context.set_source_rgba(r, g, b, 1.0) context.show_text(str(self.values[i] + self.modifiers[i])) context.stroke() def calc_polygon_color(self, total): r, g, b = 0, 0, 0 if total == self.total_points: r, g, b = 0.1, 0.4, 0.1 elif total < self.total_points: b = 1.0 else: r = 1.0 return (r, g, b) def calc_total(self): total = sum([sum(range(1, value + 1)) for value in self.values if value >= 0]) total += sum([value * 0.5 for value in self.values if value < 0]) return total def teste(widget, data): print widget.labels print widget.values print widget.modifiers print 'data: ', data if __name__ == '__main__': window = gtk.Window() window.set_title('ValueWheel') window.set_default_size(500, 400) wheel = ValueWheel(15, (-4, 6)) window.add(wheel) window.connect('delete-event', gtk.main_quit) wheel.connect('value-changed', teste) window.show_all() gtk.main()