pyreverse crash example |
# -*- coding: utf-8 -*-
#!/usr/bin/python
import gtk, gobject, pango, cairo
import math
from gettext import gettext as _
ERROR_HIGHLIGHT_COLOR = (1.0, 0, 0)
BASE_SIZE = 35 # The "normal" size of a box (in pixels)
# And the standard font-sizes -- these should fit nicely with the
# BASE_SIZE
BASE_FONT_SIZE = pango.SCALE * 13
NOTE_FONT_SIZE = pango.SCALE * 6
BORDER_WIDTH = 9.0 # The size of space we leave for a box
BORDER_LINE_WIDTH = 4 # The size of the line we draw around a selected box
NORMAL_LINE_WIDTH = 1 # The size of the line we draw around a box
class NumberSelector (gtk.EventBox):
__gsignals__ = {
'changed':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def __init__ (self, default = None, upper = 9):
self.value = default
gtk.EventBox.__init__(self)
self.table = gtk.Table()
self.add(self.table)
side = int(math.sqrt(upper))
n = 1
for y in range(side):
for x in range(side):
b = gtk.Button()
l = gtk.Label()
if n == self.value:
l.set_markup('<b><span size="x-small">%s</span></b>'%n)
else:
l.set_markup('<span size="x-small">%s</span>'%n)
b.add(l)
b.set_relief(gtk.RELIEF_HALF)
l = b.get_children()[0]
b.set_border_width(0)
l.set_padding(0, 0)
l.get_alignment()
b.connect('clicked', self.number_clicked, n)
self.table.attach(b, x, x+1, y, y+1)
n += 1
if self.value:
db = gtk.Button()
l = gtk.Label()
l.set_markup_with_mnemonic('<span size="x-small">'+_('_Clear')+'</span>')
db.add(l)
l.show()
db.connect('clicked', self.number_clicked, 0)
self.table.attach(db, 0, side, side + 1, side + 2)
self.show_all()
def number_clicked (self, button, n):
self.value = n
self.emit('changed')
def get_value (self):
return self.value
def set_value (self, n):
self.value = n
class NumberBox (gtk.Widget):
text = ''
top_note_text = ''
bottom_note_text = ''
read_only = False
_layout = None
_top_note_layout = None
_bottom_note_layout = None
text_color = None
highlight_color = None
custom_background_color = None
__gsignals__ = {
'value-about-to-change':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
'changed':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
# undo-change - A hacky way to handle the fact that we want to
# respond to undo's changes but we don't want undo to respond
# to itself...
'undo-change':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
'notes-changed':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
base_state = gtk.STATE_NORMAL
npicker = None
draw_boxes = False
def __init__ (self, upper = 9, text = ''):
gtk.Widget.__init__(self)
self.upper = upper
self.font = self.style.font_desc
self.font.set_size(BASE_FONT_SIZE)
self.note_font = self.font.copy()
self.note_font.set_size(NOTE_FONT_SIZE)
self.set_property('can-focus', True)
self.set_property('events', gtk.gdk.ALL_EVENTS_MASK)
self.connect('button-press-event', self.button_press_cb)
self.connect('key-release-event', self.key_press_cb)
self.connect('enter-notify-event', self.pointer_enter_cb)
self.connect('leave-notify-event', self.pointer_leave_cb)
self.connect('focus-in-event', self.focus_in_cb)
self.connect('focus-out-event', self.focus_out_cb)
self.connect('motion-notify-event', self.motion_notify_cb)
self.set_text(text)
def pointer_enter_cb (self, *args):
if not self.is_focus():
self.set_state(gtk.STATE_PRELIGHT)
def pointer_leave_cb (self, *args):
self.set_state(self.base_state)
self._toggle_box_drawing_(False)
def focus_in_cb (self, *args):
self.set_state(gtk.STATE_SELECTED)
self.base_state = gtk.STATE_SELECTED
def focus_out_cb (self, *args):
self.set_state(gtk.STATE_NORMAL)
self.base_state = gtk.STATE_NORMAL
self.destroy_npicker()
def destroy_npicker (self):
if self.npicker:
self.npicker.destroy()
self.npicker = None
def motion_notify_cb (self, *args):
if self.is_focus() and not self.read_only:
self._toggle_box_drawing_(True)
else:
self._toggle_box_drawing_(False)
def _toggle_box_drawing_ (self, val):
if val and not self.draw_boxes:
self.draw_boxes = True
self.queue_draw()
if (not val) and self.draw_boxes:
self.draw_boxes = False
self.queue_draw()
def button_press_cb (self, w, e):
if self.read_only:
return
if e.type == gtk.gdk._2BUTTON_PRESS:
# ignore second click (this makes a double click in the
# middle of a cell get us a display of the numbers, rather
# than selecting a number.
return
if self.is_focus():
x, y = e.get_coords()
alloc = self.get_allocation()
my_w = alloc.width
my_h = alloc.height
border_height = float(BORDER_WIDTH)/BASE_SIZE
if float(y)/my_h < border_height:
self.show_note_editor(top = True)
elif float(y)/my_h > (1-border_height):
self.show_note_editor(top = False)
elif not self.npicker:
# In this case we're a normal old click...
# makes sure there is only one numer selector
self.show_number_picker()
else:
self.grab_focus()
def key_press_cb (self, w, e):
if self.read_only:
return
if self.npicker: # kill number picker no matter what is pressed
self.destroy_npicker()
txt = gtk.gdk.keyval_name(e.keyval)
if type(txt) == type(None):
# Make sure we don't trigger on unplugging the A/C charger etc
return
txt = txt.replace('KP_', '')
if self.get_text() == txt:
# If there's no change, do nothing
return
if txt in ['0', 'Delete', 'BackSpace']:
self.set_text_interactive('')
elif txt in ['n', 'N']:
self.show_note_editor(top = True)
elif txt in ['m', 'M']:
self.show_note_editor(top = False)
# And then add the new value if need be
elif txt in [str(n) for n in range(1, self.upper+1)]:
# First do a removal event -- this is something of a
# kludge, but it works nicely with old code that was based
# on entries, which also behave this way (they generate 2
# events for replacing a number with a new number - a
# removal event and an addition event)
if self.get_text():
self.set_text_interactive('')
# Then add
self.set_text_interactive(txt)
def note_changed_cb (self, w, top = False):
if top:
self.set_note_text_interactive(top_text = w.get_text())
else:
self.set_note_text_interactive(bottom_text = w.get_text())
def show_note_editor (self, top = True):
alloc = self.get_allocation()
w = gtk.Window()
w.set_property('skip-pager-hint', True)
w.set_property('skip-taskbar-hint', True)
w.set_decorated(False)
w.set_position(gtk.WIN_POS_MOUSE)
w.set_size_request(alloc.width, alloc.height/2)
f = gtk.Frame()
e = gtk.Entry()
f.add(e)
if top:
e.set_text(self.top_note_text)
else:
e.set_text(self.bottom_note_text)
w.add(f)
e.connect('changed', self.note_changed_cb, top)
e.connect('focus-out-event', lambda e, ev, w: w.destroy(), w)
e.connect('activate', lambda e, w: w.destroy(), w)
x, y = self.window.get_origin()
if top:
w.move(x, y)
else:
w.move(x, y+int(alloc.height*0.6))
w.show_all()
e.grab_focus()
def number_changed_cb (self, num_selector):
self.destroy_npicker()
self.set_text_interactive('')
newval = num_selector.get_value()
if newval:
self.set_text_interactive(str(newval))
def show_number_picker (self):
w = gtk.Window(type = gtk.WINDOW_POPUP)
ns = NumberSelector(upper = self.upper, default = self.get_value())
ns.connect('changed', self.number_changed_cb)
w.grab_focus()
w.add(ns)
r = w.get_allocation()
my_origin = self.window.get_origin()
x, y = self.window.get_size()
popupx, popupy = w.get_size()
overlapx = popupx-x
overlapy = popupy-y
w.move(my_origin[0]-(overlapx/2), my_origin[1]-(overlapy/2))
w.show()
self.npicker = w
def set_text_interactive (self, text):
self.emit('value-about-to-change')
self.set_text(text)
self.queue_draw()
self.emit('changed')
def set_font (self, font):
if type(font) == str:
font = pango.FontDescription(font)
self.font = font
if self.text:
self.set_text(self.text)
self.queue_draw()
def set_note_font (self, font):
if type(font) == str:
font = pango.FontDescription(font)
self.note_font = font
if self.top_note_text or self.bottom_note_text:
self.set_note_text(self.top_note_text,
self.bottom_note_text)
self.queue_draw()
def set_text (self, text):
self.text = text
self._layout = self.create_pango_layout(text)
self._layout.set_font_description(self.font)
def set_notes (self, notes):
"""Hackish method to allow easy use of Undo API.
Undo API requires a set method that is called with one
argument (the result of a get method)"""
self.set_note_text(top_text = notes[0],
bottom_text = notes[1])
self.queue_draw()
def set_note_text (self, top_text = None, bottom_text = None):
if top_text is not None:
self.top_note_text = top_text
self._top_note_layout = self.create_pango_layout(top_text)
self._top_note_layout.set_font_description(self.note_font)
if bottom_text is not None:
self.bottom_note_text = bottom_text
self._bottom_note_layout = self.create_pango_layout(bottom_text)
self._bottom_note_layout.set_font_description(self.note_font)
self.queue_draw()
def set_note_text_interactive (self, *args, **kwargs):
self.emit('value-about-to-change')
self.set_note_text(*args, **kwargs)
self.emit('notes-changed')
def do_realize (self):
# The do_realize method is responsible for creating GDK (windowing system)
# resources. In this example we will create a new gdk.Window which we
# then draw on
# First set an internal flag telling that we're realized
self.set_flags(self.flags() | gtk.REALIZED)
# Create a new gdk.Window which we can draw on.
# Also say that we want to receive exposure events by setting
# the event_mask
self.window = gtk.gdk.Window(
self.get_parent_window(),
width = self.allocation.width,
height = self.allocation.height,
window_type = gtk.gdk.WINDOW_CHILD,
wclass = gtk.gdk.INPUT_OUTPUT,
event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK)
# Associate the gdk.Window with ourselves, Gtk+ needs a reference
# between the widget and the gdk window
self.window.set_user_data(self)
# Attach the style to the gdk.Window, a style contains colors and
# GC contextes used for drawing
self.style.attach(self.window)
# The default color of the background should be what
# the style (theme engine) tells us.
self.style.set_background(self.window, gtk.STATE_NORMAL)
self.window.move_resize(*self.allocation)
def do_unrealize (self):
# The do_unrealized method is responsible for freeing the GDK resources
# De-associate the window we created in do_realize with ourselves
self.window.set_user_data(None)
def do_size_request (self, requisition):
# The do_size_request method Gtk+ is calling on a widget to ask
# it the widget how large it wishes to be. It's not guaranteed
# that gtk+ will actually give this size to the widget
# In this case, we say that we want to be as big as the
# text is, and a square
width, height = self._layout.get_size()
if width > height:
side = width/pango.SCALE
else:
side = height/pango.SCALE
(requisition.width, requisition.height) = (side, side)
def do_size_allocate(self, allocation):
# The do_size_allocate is called by when the actual size is known
# and the widget is told how much space could actually be allocated
# Save the allocated space
self.allocation = allocation
# If we're realized, move and resize the window to the
# requested coordinates/positions
if self.flags() & gtk.REALIZED:
self.window.move_resize(*allocation)
def do_expose_event(self, event):
# The do_expose_event is called when the widget is asked to draw itself
# Remember that this will be called a lot of times, so it's usually
# a good idea to write this code as optimized as it can be, don't
# Create any resources in here.
x, y, w, h = self.allocation
cr = self.window.cairo_create()
if h < w:
scale = h/float(BASE_SIZE)
else:
scale = w/float(BASE_SIZE)
cr.scale(scale, scale)
self.draw_background_color(cr)
if self.is_focus():
self.draw_highlight_box(cr)
self.draw_normal_box(cr)
self.draw_text(cr)
if self.draw_boxes and self.is_focus():
self.draw_note_area_highlight_box(cr)
def draw_background_color (self, cr):
if self.read_only:
if self.custom_background_color:
r, g, b = self.custom_background_color
cr.set_source_rgb(
r*0.6, g*0.6, b*0.6
)
else:
cr.set_source_color(self.style.base[gtk.STATE_INSENSITIVE])
elif self.is_focus():
cr.set_source_color(self.style.base[gtk.STATE_SELECTED])
elif self.custom_background_color:
cr.set_source_rgb(*self.custom_background_color)
else:
cr.set_source_color(
self.style.base[self.state]
)
cr.rectangle(
0, 0, BASE_SIZE, BASE_SIZE
)
cr.fill()
def draw_normal_box (self, cr):
state = self.state
if state == gtk.STATE_SELECTED:
# When the widget is selected, we still want the outer box to look normal
state = gtk.STATE_NORMAL
cr.set_source_color(
self.style.mid[state]
)
cr.rectangle(
NORMAL_LINE_WIDTH*0.5,
NORMAL_LINE_WIDTH*0.5,
BASE_SIZE-NORMAL_LINE_WIDTH,
BASE_SIZE-NORMAL_LINE_WIDTH,
)
cr.set_line_width(NORMAL_LINE_WIDTH)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
cr.stroke()
# And now draw a thinner line around the very outside...
cr.set_source_color(
self.style.dark[state]
)
cr.rectangle(
NORMAL_LINE_WIDTH*0.25,
NORMAL_LINE_WIDTH*0.25,
BASE_SIZE-NORMAL_LINE_WIDTH*0.5,
BASE_SIZE-NORMAL_LINE_WIDTH*0.5,
)
cr.set_line_width(NORMAL_LINE_WIDTH*0.5)
cr.set_line_join(cairo.LINE_JOIN_MITER)
cr.stroke()
def draw_highlight_box (self, cr):
cr.set_source_color(
self.style.base[gtk.STATE_SELECTED]
)
cr.rectangle(
# left-top
BORDER_LINE_WIDTH*0.5,
BORDER_LINE_WIDTH*0.5,
# bottom-right
BASE_SIZE-(BORDER_LINE_WIDTH),
BASE_SIZE-(BORDER_LINE_WIDTH),
)
cr.set_line_width(BORDER_LINE_WIDTH)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
cr.stroke()
def draw_note_area_highlight_box (self, cr):
# set up our paint brush...
cr.set_source_color(
self.style.mid[self.state]
)
cr.set_line_width(NORMAL_LINE_WIDTH)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
# top rectangle
cr.rectangle(NORMAL_LINE_WIDTH*0.5,
NORMAL_LINE_WIDTH*0.5,
BASE_SIZE-NORMAL_LINE_WIDTH,
BORDER_WIDTH-NORMAL_LINE_WIDTH)
cr.stroke()
# bottom rectangle
cr.rectangle(NORMAL_LINE_WIDTH*0.5, #x
BASE_SIZE - BORDER_WIDTH-(NORMAL_LINE_WIDTH*0.5), #y
BASE_SIZE-NORMAL_LINE_WIDTH, #x2
BASE_SIZE-NORMAL_LINE_WIDTH #y2
)
cr.stroke()
def draw_text (self, cr):
if self.text_color:
cr.set_source_rgb(*self.text_color)
elif self.read_only:
cr.set_source_color(self.style.text[gtk.STATE_NORMAL])
else:
cr.set_source_color(self.style.text[self.state])
# And draw the text in the middle of the allocated space
if self._layout:
fontw, fonth = self._layout.get_pixel_size()
cr.move_to(
(BASE_SIZE/2)-(fontw/2),
(BASE_SIZE/2) - (fonth/2),
)
cr.update_layout(self._layout)
cr.show_layout(self._layout)
cr.set_source_color(self.style.text[self.state])
# And draw any note text...
if self._top_note_layout:
fontw, fonth = self._top_note_layout.get_pixel_size()
cr.move_to(
NORMAL_LINE_WIDTH,
0,
)
cr.update_layout(self._top_note_layout)
cr.show_layout(self._top_note_layout)
if self._bottom_note_layout:
fontw, fonth = self._bottom_note_layout.get_pixel_size()
cr.move_to(
NORMAL_LINE_WIDTH,
BASE_SIZE-fonth,
)
cr.update_layout(self._bottom_note_layout)
cr.show_layout(self._bottom_note_layout)
def set_text_color (self, color):
self.text_color = color
self.queue_draw()
def set_background_color (self, color):
self.custom_background_color = color
self.queue_draw()
def hide_notes (self):
pass
def show_notes (self):
pass
def set_value_from_undo (self, v):
self.set_value(v)
self.emit('undo_change')
def set_value (self, v):
if 0 < v <= self.upper:
self.set_text(str(v))
else:
self.set_text('')
self.queue_draw()
def get_value (self):
try:
return int(self.text)
except:
return None
def get_text (self):
return self.text
def get_note_text (self):
return self.top_note_text, self.bottom_note_text
class SudokuNumberBox (NumberBox):
normal_color = None
highlight_color = ERROR_HIGHLIGHT_COLOR
def set_color (self, color):
self.normal_color = color
self.set_text_color(self.normal_color)
def unset_color (self):
self.set_color(None)
def set_error_highlight (self, val):
if val:
self.set_text_color((1.0, 0, 0))
else:
self.set_text_color(self.normal_color)
def set_read_only (self, val):
self.read_only = val
if not hasattr(self, 'bold_font'):
self.normal_font = self.font
self.bold_font = self.font.copy()
self.bold_font.set_weight(pango.WEIGHT_BOLD)
if self.read_only:
self.set_font(self.bold_font)
else:
self.set_font(self.normal_font)
self.queue_draw()
def set_impossible (self, val):
if val:
self.set_text('X')
else: self.set_text('')
gobject.type_register(NumberBox)
if __name__ == '__main__':
window = gtk.Window()
window.connect('delete-event', gtk.main_quit)
def test_number_selector ():
nselector = NumberSelector(default = 3)
def tell_me (b):
print 'value->', b.get_value()
nselector.connect('changed', tell_me)
window.add(nselector)
def test_number_box ():
window.set_size_request(100, 100)
nbox = NumberBox()
window.add(nbox)
# test_number_selector()
test_number_box()
window.show_all()
gtk.main()



#9018 when using defining-attr-method, method order matters