417 lines
14 KiB
Python
417 lines
14 KiB
Python
#---------------------------------------------------------------------------
|
|
#
|
|
# Albow - Root widget
|
|
#
|
|
#---------------------------------------------------------------------------
|
|
|
|
import sys
|
|
import pygame
|
|
from pygame.locals import *
|
|
#from pygame.time import get_ticks
|
|
from pygame.event import Event
|
|
|
|
from glbackground import *
|
|
import widget
|
|
from widget import Widget
|
|
from controls import Label
|
|
|
|
from datetime import datetime, timedelta
|
|
from albow.dialogs import wrapped_label
|
|
start_time = datetime.now()
|
|
|
|
mod_cmd = KMOD_LCTRL | KMOD_RCTRL | KMOD_LMETA | KMOD_RMETA
|
|
double_click_time = timedelta(0, 0, 300000) # days, seconds, microseconds
|
|
|
|
import logging
|
|
log = logging.getLogger(__name__)
|
|
|
|
modifiers = dict(
|
|
shift=False,
|
|
ctrl=False,
|
|
alt=False,
|
|
meta=False,
|
|
)
|
|
|
|
modkeys = {
|
|
K_LSHIFT: 'shift', K_RSHIFT: 'shift',
|
|
K_LCTRL: 'ctrl', K_RCTRL: 'ctrl',
|
|
K_LALT: 'alt', K_RALT: 'alt',
|
|
K_LMETA: 'meta', K_RMETA: 'meta',
|
|
}
|
|
|
|
MUSIC_END_EVENT = USEREVENT + 1
|
|
|
|
last_mouse_event = Event(0, pos=(0, 0), local=(0, 0))
|
|
last_mouse_event_handler = None
|
|
root_widget = None # Root of the containment hierarchy
|
|
top_widget = None # Initial dispatch target
|
|
clicked_widget = None # Target of mouse_drag and mouse_up events
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
class Cancel(Exception):
|
|
pass
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
def set_modifier(key, value):
|
|
attr = modkeys.get(key)
|
|
if attr:
|
|
modifiers[attr] = value
|
|
|
|
|
|
def add_modifiers(event):
|
|
d = event.dict
|
|
d.update(modifiers)
|
|
d['cmd'] = event.ctrl or event.meta
|
|
|
|
|
|
def get_root():
|
|
return root_widget
|
|
|
|
|
|
def get_top_widget():
|
|
return top_widget
|
|
|
|
|
|
def get_focus():
|
|
return top_widget.get_focus()
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
class RootWidget(Widget):
|
|
# surface Pygame display surface
|
|
# is_gl True if OpenGL surface
|
|
|
|
redraw_every_frame = False
|
|
do_draw = False
|
|
_is_gl_container = True
|
|
|
|
def __init__(self, surface):
|
|
global root_widget
|
|
Widget.__init__(self, surface.get_rect())
|
|
self.surface = surface
|
|
root_widget = self
|
|
widget.root_widget = self
|
|
self.is_gl = surface.get_flags() & OPENGL != 0
|
|
self.idle_handlers = []
|
|
|
|
def set_timer(self, ms):
|
|
pygame.time.set_timer(USEREVENT, ms)
|
|
|
|
def run(self):
|
|
self.run_modal(None)
|
|
|
|
captured_widget = None
|
|
|
|
def capture_mouse(self, widget):
|
|
#put the mouse in "virtual mode" and pass mouse moved events to the
|
|
#specified widget
|
|
if widget:
|
|
pygame.mouse.set_visible(False)
|
|
pygame.event.set_grab(True)
|
|
get_root().captured_widget = widget
|
|
else:
|
|
pygame.mouse.set_visible(True)
|
|
pygame.event.set_grab(False)
|
|
get_root().captured_widget = None
|
|
|
|
frames = 0
|
|
hover_widget = None
|
|
|
|
def run_modal(self, modal_widget):
|
|
old_captured_widget = None
|
|
|
|
if self.captured_widget:
|
|
old_captured_widget = self.captured_widget
|
|
self.capture_mouse(None)
|
|
|
|
global last_mouse_event, last_mouse_event_handler
|
|
global top_widget, clicked_widget
|
|
is_modal = modal_widget is not None
|
|
modal_widget = modal_widget or self
|
|
from OpenGL import GL
|
|
try:
|
|
old_top_widget = top_widget
|
|
top_widget = modal_widget
|
|
was_modal = modal_widget.is_modal
|
|
modal_widget.is_modal = True
|
|
modal_widget.modal_result = None
|
|
if not modal_widget.focus_switch:
|
|
modal_widget.tab_to_first()
|
|
mouse_widget = None
|
|
if clicked_widget:
|
|
clicked_widget = modal_widget
|
|
num_clicks = 0
|
|
last_click_time = start_time
|
|
last_click_button = 0
|
|
self.do_draw = True
|
|
|
|
while modal_widget.modal_result is None:
|
|
try:
|
|
self.hover_widget = self.find_widget(mouse.get_pos())
|
|
if self.do_draw:
|
|
if self.is_gl:
|
|
self.gl_clear()
|
|
self.gl_draw_all(self, (0, 0))
|
|
GL.glFlush()
|
|
else:
|
|
self.draw_all(self.surface)
|
|
self.do_draw = False
|
|
pygame.display.flip()
|
|
self.frames += 1
|
|
#events = [pygame.event.wait()]
|
|
events = [pygame.event.poll()]
|
|
events.extend(pygame.event.get())
|
|
for event in events:
|
|
#if event.type:
|
|
#log.debug("%s", event)
|
|
|
|
type = event.type
|
|
if type == QUIT:
|
|
self.quit()
|
|
elif type == MOUSEBUTTONDOWN:
|
|
self.do_draw = True
|
|
t = datetime.now()
|
|
if t - last_click_time <= double_click_time and event.button == last_click_button:
|
|
num_clicks += 1
|
|
else:
|
|
num_clicks = 1
|
|
last_click_button = event.button
|
|
last_click_time = t
|
|
event.dict['num_clicks'] = num_clicks
|
|
add_modifiers(event)
|
|
mouse_widget = self.find_widget(event.pos)
|
|
if self.captured_widget:
|
|
mouse_widget = self.captured_widget
|
|
|
|
if not mouse_widget.is_inside(modal_widget):
|
|
mouse_widget = modal_widget
|
|
#if event.button == 1:
|
|
clicked_widget = mouse_widget
|
|
last_mouse_event_handler = mouse_widget
|
|
last_mouse_event = event
|
|
mouse_widget.notify_attention_loss()
|
|
mouse_widget.handle_mouse('mouse_down', event)
|
|
elif type == MOUSEMOTION:
|
|
self.do_draw = True
|
|
add_modifiers(event)
|
|
modal_widget.dispatch_key('mouse_delta', event)
|
|
last_mouse_event = event
|
|
|
|
mouse_widget = self.update_tooltip(event.pos)
|
|
|
|
if clicked_widget:
|
|
last_mouse_event_handler = clicked_widget
|
|
clicked_widget.handle_mouse('mouse_drag', event)
|
|
else:
|
|
if not mouse_widget.is_inside(modal_widget):
|
|
mouse_widget = modal_widget
|
|
last_mouse_event_handler = mouse_widget
|
|
mouse_widget.handle_mouse('mouse_move', event)
|
|
elif type == MOUSEBUTTONUP:
|
|
add_modifiers(event)
|
|
self.do_draw = True
|
|
mouse_widget = self.find_widget(event.pos)
|
|
if self.captured_widget:
|
|
mouse_widget = self.captured_widget
|
|
if clicked_widget:
|
|
last_mouse_event_handler = clicked_widget
|
|
event.dict['clicked_widget'] = clicked_widget
|
|
else:
|
|
last_mouse_event_handler = mouse_widget
|
|
event.dict['clicked_widget'] = None
|
|
|
|
last_mouse_event = event
|
|
clicked_widget = None
|
|
last_mouse_event_handler.handle_mouse('mouse_up', event)
|
|
elif type == KEYDOWN:
|
|
key = event.key
|
|
set_modifier(key, True)
|
|
self.do_draw = True
|
|
self.send_key(modal_widget, 'key_down', event)
|
|
if last_mouse_event_handler:
|
|
event.dict['pos'] = last_mouse_event.pos
|
|
event.dict['local'] = last_mouse_event.local
|
|
last_mouse_event_handler.setup_cursor(event)
|
|
elif type == KEYUP:
|
|
key = event.key
|
|
set_modifier(key, False)
|
|
self.do_draw = True
|
|
self.send_key(modal_widget, 'key_up', event)
|
|
if last_mouse_event_handler:
|
|
event.dict['pos'] = last_mouse_event.pos
|
|
event.dict['local'] = last_mouse_event.local
|
|
last_mouse_event_handler.setup_cursor(event)
|
|
elif type == MUSIC_END_EVENT:
|
|
self.music_end()
|
|
elif type == USEREVENT:
|
|
make_scheduled_calls()
|
|
if not is_modal:
|
|
self.do_draw = self.redraw_every_frame
|
|
if last_mouse_event_handler:
|
|
event.dict['pos'] = last_mouse_event.pos
|
|
event.dict['local'] = last_mouse_event.local
|
|
add_modifiers(event)
|
|
last_mouse_event_handler.setup_cursor(event)
|
|
self.begin_frame()
|
|
elif type == VIDEORESIZE:
|
|
#add_modifiers(event)
|
|
self.do_draw = True
|
|
self.size = (event.w, event.h)
|
|
#self.dispatch_key('reshape', event)
|
|
elif type == ACTIVEEVENT:
|
|
add_modifiers(event)
|
|
self.dispatch_key('activeevent', event)
|
|
elif type == NOEVENT:
|
|
add_modifiers(event)
|
|
self.call_idle_handlers(event)
|
|
|
|
except Cancel:
|
|
pass
|
|
finally:
|
|
modal_widget.is_modal = was_modal
|
|
top_widget = old_top_widget
|
|
if old_captured_widget:
|
|
self.capture_mouse(old_captured_widget)
|
|
|
|
clicked_widget = None
|
|
|
|
def call_idle_handlers(self, event):
|
|
def call(ref):
|
|
widget = ref()
|
|
if widget:
|
|
widget.idleevent(event)
|
|
else:
|
|
print "Idle ref died!"
|
|
return bool(widget)
|
|
|
|
self.idle_handlers = filter(call, self.idle_handlers)
|
|
|
|
def add_idle_handler(self, widget):
|
|
from weakref import ref
|
|
self.idle_handlers.append(ref(widget))
|
|
|
|
def remove_idle_handler(self, widget):
|
|
from weakref import ref
|
|
self.idle_handlers.remove(ref(widget))
|
|
|
|
def send_key(self, widget, name, event):
|
|
add_modifiers(event)
|
|
widget.dispatch_key(name, event)
|
|
|
|
def begin_frame(self):
|
|
pass
|
|
|
|
def get_root(self):
|
|
return self
|
|
|
|
labelClass = lambda s, t: wrapped_label(t, 45)
|
|
|
|
def show_tooltip(self, widget, pos):
|
|
|
|
if hasattr(self, 'currentTooltip'):
|
|
if self.currentTooltip != None:
|
|
self.remove(self.currentTooltip)
|
|
|
|
self.currentTooltip = None
|
|
|
|
def TextTooltip(text):
|
|
tooltipBacking = Panel()
|
|
tooltipBacking.bg_color = (0.0, 0.0, 0.0, 0.6)
|
|
tooltipBacking.add(self.labelClass(text))
|
|
tooltipBacking.shrink_wrap()
|
|
return tooltipBacking
|
|
|
|
def showTip(tip):
|
|
tip.topleft = pos
|
|
tip.top += 20
|
|
if (tip.bottom > self.bottom) or hasattr(widget, 'tooltipsUp'):
|
|
tip.bottomleft = pos
|
|
tip.top -= 4
|
|
if tip.right > self.right:
|
|
tip.right = pos[0]
|
|
|
|
self.add(tip)
|
|
self.currentTooltip = tip
|
|
|
|
if widget.tooltip is not None:
|
|
tip = widget.tooltip
|
|
showTip(tip)
|
|
|
|
else:
|
|
ttext = widget.tooltipText
|
|
if ttext is not None:
|
|
tip = TextTooltip(ttext)
|
|
showTip(tip)
|
|
|
|
def update_tooltip(self, pos=None):
|
|
if pos is None:
|
|
pos = mouse.get_pos()
|
|
if self.captured_widget:
|
|
mouse_widget = self.captured_widget
|
|
pos = mouse_widget.center
|
|
else:
|
|
mouse_widget = self.find_widget(pos)
|
|
|
|
self.show_tooltip(mouse_widget, pos)
|
|
return mouse_widget
|
|
|
|
def has_focus(self):
|
|
return True
|
|
|
|
def quit(self):
|
|
if self.confirm_quit():
|
|
self.capture_mouse(None)
|
|
sys.exit(0)
|
|
|
|
def confirm_quit(self):
|
|
return True
|
|
|
|
def get_mouse_for(self, widget):
|
|
last = last_mouse_event
|
|
event = Event(0, last.dict)
|
|
event.dict['local'] = widget.global_to_local(event.pos)
|
|
add_modifiers(event)
|
|
return event
|
|
|
|
def gl_clear(self):
|
|
from OpenGL import GL
|
|
bg = self.bg_color
|
|
if bg:
|
|
r = bg[0] / 255.0
|
|
g = bg[1] / 255.0
|
|
b = bg[2] / 255.0
|
|
GL.glClearColor(r, g, b, 0.0)
|
|
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
|
|
|
|
def music_end(self):
|
|
import music
|
|
music.music_end()
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
from time import time
|
|
from bisect import insort
|
|
|
|
scheduled_calls = []
|
|
|
|
|
|
def make_scheduled_calls():
|
|
sched = scheduled_calls
|
|
t = time()
|
|
while sched and sched[0][0] <= t:
|
|
sched[0][1]()
|
|
sched.pop(0)
|
|
|
|
|
|
def schedule(delay, func):
|
|
"""Arrange for the given function to be called after the specified
|
|
delay in seconds. Scheduled functions are called synchronously from
|
|
the event loop, and only when the frame timer is running."""
|
|
t = time() + delay
|
|
insort(scheduled_calls, (t, func))
|