Switch to tabs for indentation.
Instead of a mix of 2- and 4-space tabs just use actual tabs. ;-P
This commit is contained in:
+161
-161
@@ -48,18 +48,18 @@ GREEN = 70, 200, 70
|
||||
|
||||
|
||||
MOUSE_EVENTS = frozenset({
|
||||
pygame.MOUSEMOTION,
|
||||
pygame.MOUSEBUTTONDOWN,
|
||||
pygame.MOUSEBUTTONUP
|
||||
})
|
||||
pygame.MOUSEMOTION,
|
||||
pygame.MOUSEBUTTONDOWN,
|
||||
pygame.MOUSEBUTTONUP
|
||||
})
|
||||
'PyGame mouse events.'
|
||||
|
||||
ARROW_KEYS = frozenset({
|
||||
pygame.K_UP,
|
||||
pygame.K_DOWN,
|
||||
pygame.K_LEFT,
|
||||
pygame.K_RIGHT
|
||||
})
|
||||
pygame.K_UP,
|
||||
pygame.K_DOWN,
|
||||
pygame.K_LEFT,
|
||||
pygame.K_RIGHT
|
||||
})
|
||||
'PyGame arrow key events.'
|
||||
|
||||
|
||||
@@ -82,201 +82,201 @@ SUCCESS = 1
|
||||
|
||||
|
||||
class Message(object):
|
||||
'''Message base class. Contains ``sender`` field.'''
|
||||
def __init__(self, sender):
|
||||
self.sender = sender
|
||||
'''Message base class. Contains ``sender`` field.'''
|
||||
def __init__(self, sender):
|
||||
self.sender = sender
|
||||
|
||||
|
||||
class CommandMessage(Message):
|
||||
'''For commands, adds ``command`` field.'''
|
||||
def __init__(self, sender, command):
|
||||
Message.__init__(self, sender)
|
||||
self.command = command
|
||||
'''For commands, adds ``command`` field.'''
|
||||
def __init__(self, sender, command):
|
||||
Message.__init__(self, sender)
|
||||
self.command = command
|
||||
|
||||
|
||||
class ModifyMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``subject`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, subject, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.subject = subject
|
||||
self.details = details
|
||||
'''
|
||||
For when resources are modified, adds ``subject`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, subject, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.subject = subject
|
||||
self.details = details
|
||||
|
||||
|
||||
class OpenMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``name``, content_id``,
|
||||
``status``, and ``traceback`` fields.
|
||||
'''
|
||||
def __init__(self, sender, name):
|
||||
Message.__init__(self, sender)
|
||||
self.name = name
|
||||
self.content_id = self.thing = None
|
||||
self.status = PENDING
|
||||
self.traceback = None
|
||||
'''
|
||||
For when resources are modified, adds ``name``, content_id``,
|
||||
``status``, and ``traceback`` fields.
|
||||
'''
|
||||
def __init__(self, sender, name):
|
||||
Message.__init__(self, sender)
|
||||
self.name = name
|
||||
self.content_id = self.thing = None
|
||||
self.status = PENDING
|
||||
self.traceback = None
|
||||
|
||||
|
||||
class PersistMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``content_id`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, content_id, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.content_id = content_id
|
||||
self.details = details
|
||||
'''
|
||||
For when resources are modified, adds ``content_id`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, content_id, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.content_id = content_id
|
||||
self.details = details
|
||||
|
||||
|
||||
class ShutdownMessage(Message):
|
||||
'''Signals that the system is shutting down.'''
|
||||
'''Signals that the system is shutting down.'''
|
||||
|
||||
|
||||
# Joy Interpreter & Context
|
||||
|
||||
|
||||
class World(object):
|
||||
'''
|
||||
This object contains the system context, the stack, dictionary, a
|
||||
reference to the display broadcast method, and the log.
|
||||
'''
|
||||
'''
|
||||
This object contains the system context, the stack, dictionary, a
|
||||
reference to the display broadcast method, and the log.
|
||||
'''
|
||||
|
||||
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
|
||||
self.stack_holder = stack_holder
|
||||
self.dictionary = dictionary
|
||||
self.notify = notify
|
||||
self.stack_id = stack_id
|
||||
self.log = log.lines
|
||||
self.log_id = log.content_id
|
||||
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
|
||||
self.stack_holder = stack_holder
|
||||
self.dictionary = dictionary
|
||||
self.notify = notify
|
||||
self.stack_id = stack_id
|
||||
self.log = log.lines
|
||||
self.log_id = log.content_id
|
||||
|
||||
def handle(self, message):
|
||||
'''
|
||||
Deal with updates to the stack and commands.
|
||||
'''
|
||||
if (isinstance(message, ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._log_lines('', '%s <-' % self.format_stack())
|
||||
if not isinstance(message, CommandMessage):
|
||||
return
|
||||
c, s, d = message.command, self.stack_holder[0], self.dictionary
|
||||
self._log_lines('', '-> %s' % (c,))
|
||||
self.stack_holder[0], _, self.dictionary = run(c, s, d)
|
||||
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
|
||||
self.notify(mm)
|
||||
def handle(self, message):
|
||||
'''
|
||||
Deal with updates to the stack and commands.
|
||||
'''
|
||||
if (isinstance(message, ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._log_lines('', '%s <-' % self.format_stack())
|
||||
if not isinstance(message, CommandMessage):
|
||||
return
|
||||
c, s, d = message.command, self.stack_holder[0], self.dictionary
|
||||
self._log_lines('', '-> %s' % (c,))
|
||||
self.stack_holder[0], _, self.dictionary = run(c, s, d)
|
||||
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
|
||||
self.notify(mm)
|
||||
|
||||
def _log_lines(self, *lines):
|
||||
self.log.extend(lines)
|
||||
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
|
||||
def _log_lines(self, *lines):
|
||||
self.log.extend(lines)
|
||||
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
|
||||
|
||||
def format_stack(self):
|
||||
try:
|
||||
return stack_to_string(self.stack_holder[0])
|
||||
except:
|
||||
print(format_exc(), file=stderr)
|
||||
return str(self.stack_holder[0])
|
||||
def format_stack(self):
|
||||
try:
|
||||
return stack_to_string(self.stack_holder[0])
|
||||
except:
|
||||
print(format_exc(), file=stderr)
|
||||
return str(self.stack_holder[0])
|
||||
|
||||
|
||||
def push(sender, item, notify, stack_name='stack.pickle'):
|
||||
'''
|
||||
Helper function to push an item onto the system stack with message.
|
||||
'''
|
||||
om = OpenMessage(sender, stack_name)
|
||||
notify(om)
|
||||
if om.status == SUCCESS:
|
||||
om.thing[0] = item, om.thing[0]
|
||||
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
|
||||
return om.status
|
||||
'''
|
||||
Helper function to push an item onto the system stack with message.
|
||||
'''
|
||||
om = OpenMessage(sender, stack_name)
|
||||
notify(om)
|
||||
if om.status == SUCCESS:
|
||||
om.thing[0] = item, om.thing[0]
|
||||
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
|
||||
return om.status
|
||||
|
||||
|
||||
def open_viewer_on_string(sender, content, notify):
|
||||
'''
|
||||
Helper function to open a text viewer on a string.
|
||||
Typically used to show tracebacks.
|
||||
'''
|
||||
push(sender, content, notify)
|
||||
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
|
||||
'''
|
||||
Helper function to open a text viewer on a string.
|
||||
Typically used to show tracebacks.
|
||||
'''
|
||||
push(sender, content, notify)
|
||||
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
|
||||
|
||||
|
||||
# main loop
|
||||
|
||||
|
||||
class TheLoop(object):
|
||||
'''
|
||||
The main loop manages tasks and the PyGame event queue
|
||||
and framerate clock.
|
||||
'''
|
||||
'''
|
||||
The main loop manages tasks and the PyGame event queue
|
||||
and framerate clock.
|
||||
'''
|
||||
|
||||
FRAME_RATE = 24
|
||||
FRAME_RATE = 24
|
||||
|
||||
def __init__(self, display, clock):
|
||||
self.display = display
|
||||
self.clock = clock
|
||||
self.tasks = {}
|
||||
self.running = False
|
||||
def __init__(self, display, clock):
|
||||
self.display = display
|
||||
self.clock = clock
|
||||
self.tasks = {}
|
||||
self.running = False
|
||||
|
||||
def install_task(self, F, milliseconds):
|
||||
'''
|
||||
Install a task to run every so many milliseconds.
|
||||
'''
|
||||
try:
|
||||
task_event_id = AVAILABLE_TASK_EVENTS.pop()
|
||||
except KeyError:
|
||||
raise RuntimeError('out of task ids')
|
||||
self.tasks[task_event_id] = F
|
||||
pygame.time.set_timer(task_event_id, milliseconds)
|
||||
return task_event_id
|
||||
def install_task(self, F, milliseconds):
|
||||
'''
|
||||
Install a task to run every so many milliseconds.
|
||||
'''
|
||||
try:
|
||||
task_event_id = AVAILABLE_TASK_EVENTS.pop()
|
||||
except KeyError:
|
||||
raise RuntimeError('out of task ids')
|
||||
self.tasks[task_event_id] = F
|
||||
pygame.time.set_timer(task_event_id, milliseconds)
|
||||
return task_event_id
|
||||
|
||||
def remove_task(self, task_event_id):
|
||||
'''
|
||||
Remove an installed task.
|
||||
'''
|
||||
assert task_event_id in self.tasks, repr(task_event_id)
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
del self.tasks[task_event_id]
|
||||
AVAILABLE_TASK_EVENTS.add(task_event_id)
|
||||
def remove_task(self, task_event_id):
|
||||
'''
|
||||
Remove an installed task.
|
||||
'''
|
||||
assert task_event_id in self.tasks, repr(task_event_id)
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
del self.tasks[task_event_id]
|
||||
AVAILABLE_TASK_EVENTS.add(task_event_id)
|
||||
|
||||
def __del__(self):
|
||||
# Best effort to cancel all running tasks.
|
||||
for task_event_id in self.tasks:
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
def __del__(self):
|
||||
# Best effort to cancel all running tasks.
|
||||
for task_event_id in self.tasks:
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
|
||||
def run_task(self, task_event_id):
|
||||
'''
|
||||
Give a task its time to shine.
|
||||
'''
|
||||
task = self.tasks[task_event_id]
|
||||
try:
|
||||
task()
|
||||
except:
|
||||
traceback = format_exc()
|
||||
self.remove_task(task_event_id)
|
||||
print(traceback, file=stderr)
|
||||
print('TASK removed due to ERROR', task, file=stderr)
|
||||
open_viewer_on_string(self, traceback, self.display.broadcast)
|
||||
def run_task(self, task_event_id):
|
||||
'''
|
||||
Give a task its time to shine.
|
||||
'''
|
||||
task = self.tasks[task_event_id]
|
||||
try:
|
||||
task()
|
||||
except:
|
||||
traceback = format_exc()
|
||||
self.remove_task(task_event_id)
|
||||
print(traceback, file=stderr)
|
||||
print('TASK removed due to ERROR', task, file=stderr)
|
||||
open_viewer_on_string(self, traceback, self.display.broadcast)
|
||||
|
||||
def loop(self):
|
||||
'''
|
||||
The actual main loop machinery.
|
||||
def loop(self):
|
||||
'''
|
||||
The actual main loop machinery.
|
||||
|
||||
Maintain a ``running`` flag, pump the PyGame event queue and
|
||||
handle the events (dispatching to the display), tick the clock.
|
||||
Maintain a ``running`` flag, pump the PyGame event queue and
|
||||
handle the events (dispatching to the display), tick the clock.
|
||||
|
||||
When the loop is exited (by clicking the window close button or
|
||||
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
|
||||
'''
|
||||
self.running = True
|
||||
while self.running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
self.running = False
|
||||
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
|
||||
self.running = False
|
||||
elif event.type in self.tasks:
|
||||
self.run_task(event.type)
|
||||
else:
|
||||
self.display.dispatch_event(event)
|
||||
pygame.display.update()
|
||||
self.clock.tick(self.FRAME_RATE)
|
||||
self.display.broadcast(ShutdownMessage(self))
|
||||
When the loop is exited (by clicking the window close button or
|
||||
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
|
||||
'''
|
||||
self.running = True
|
||||
while self.running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
self.running = False
|
||||
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
|
||||
self.running = False
|
||||
elif event.type in self.tasks:
|
||||
self.run_task(event.type)
|
||||
else:
|
||||
self.display.dispatch_event(event)
|
||||
pygame.display.update()
|
||||
self.clock.tick(self.FRAME_RATE)
|
||||
self.display.broadcast(ShutdownMessage(self))
|
||||
|
||||
@@ -3,17 +3,17 @@ import sys, traceback
|
||||
|
||||
# To enable "hot" reloading in the IDLE shell.
|
||||
for name in 'core main display viewer text_viewer stack_viewer persist_task'.split():
|
||||
try:
|
||||
del sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
del sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
from . import main
|
||||
|
||||
try:
|
||||
A = A # (screen, clock, pt), three things that we DON'T want to recreate
|
||||
# each time we restart main().
|
||||
A = A # (screen, clock, pt), three things that we DON'T want to recreate
|
||||
# each time we restart main().
|
||||
except NameError:
|
||||
A = main.init()
|
||||
A = main.init()
|
||||
|
||||
d = main.main(*A)
|
||||
|
||||
+412
-412
@@ -38,473 +38,473 @@ from sys import stderr
|
||||
from traceback import format_exc
|
||||
import pygame
|
||||
from .core import (
|
||||
open_viewer_on_string,
|
||||
GREY,
|
||||
MOUSE_EVENTS,
|
||||
)
|
||||
open_viewer_on_string,
|
||||
GREY,
|
||||
MOUSE_EVENTS,
|
||||
)
|
||||
from .viewer import Viewer
|
||||
from joy.vui import text_viewer
|
||||
|
||||
|
||||
class Display(object):
|
||||
'''
|
||||
Manage tracks and viewers on a screen (Pygame surface.)
|
||||
'''
|
||||
Manage tracks and viewers on a screen (Pygame surface.)
|
||||
|
||||
The size and number of tracks are defined by passing in at least two
|
||||
ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one
|
||||
small one on the left and two larger ones of the same size, each four
|
||||
times wider than the left one.
|
||||
The size and number of tracks are defined by passing in at least two
|
||||
ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one
|
||||
small one on the left and two larger ones of the same size, each four
|
||||
times wider than the left one.
|
||||
|
||||
All tracks take up the whole height of the display screen. Tracks
|
||||
manage zero or more Viewers. When you "grow" a viewer a new track is
|
||||
created that overlays or hides one or two existing tracks, and when
|
||||
the last viewer in an overlay track is closed the track closes too
|
||||
and reveals the hidden tracks (and their viewers, if any.)
|
||||
All tracks take up the whole height of the display screen. Tracks
|
||||
manage zero or more Viewers. When you "grow" a viewer a new track is
|
||||
created that overlays or hides one or two existing tracks, and when
|
||||
the last viewer in an overlay track is closed the track closes too
|
||||
and reveals the hidden tracks (and their viewers, if any.)
|
||||
|
||||
In order to facilitate command underlining while mouse dragging the
|
||||
lookup parameter must be a function that accepts a string and returns
|
||||
a Boolean indicating whether that string is a valid Joy function name.
|
||||
Typically you pass in the __contains__ method of the Joy dict. This
|
||||
is a case of breaking "loose coupling" to gain efficiency, as otherwise
|
||||
we would have to e.g. send some sort of lookup message to the
|
||||
World context object, going through the whole Display.broadcast()
|
||||
machinery, etc. Not something you want to do on each MOUSEMOTION
|
||||
event.
|
||||
'''
|
||||
In order to facilitate command underlining while mouse dragging the
|
||||
lookup parameter must be a function that accepts a string and returns
|
||||
a Boolean indicating whether that string is a valid Joy function name.
|
||||
Typically you pass in the __contains__ method of the Joy dict. This
|
||||
is a case of breaking "loose coupling" to gain efficiency, as otherwise
|
||||
we would have to e.g. send some sort of lookup message to the
|
||||
World context object, going through the whole Display.broadcast()
|
||||
machinery, etc. Not something you want to do on each MOUSEMOTION
|
||||
event.
|
||||
'''
|
||||
|
||||
def __init__(self, screen, lookup, *track_ratios):
|
||||
self.screen = screen
|
||||
self.w, self.h = screen.get_width(), screen.get_height()
|
||||
self.lookup = lookup
|
||||
self.focused_viewer = None
|
||||
self.tracks = [] # (x, track)
|
||||
self.handlers = [] # Non-viewers that should receive messages.
|
||||
# Create the tracks.
|
||||
if not track_ratios: track_ratios = 1, 4
|
||||
x, total = 0, sum(track_ratios)
|
||||
for ratio in track_ratios[:-1]:
|
||||
track_width = old_div(self.w * ratio, total)
|
||||
assert track_width >= 10 # minimum width 10 pixels
|
||||
self._open_track(x, track_width)
|
||||
x += track_width
|
||||
self._open_track(x, self.w - x)
|
||||
def __init__(self, screen, lookup, *track_ratios):
|
||||
self.screen = screen
|
||||
self.w, self.h = screen.get_width(), screen.get_height()
|
||||
self.lookup = lookup
|
||||
self.focused_viewer = None
|
||||
self.tracks = [] # (x, track)
|
||||
self.handlers = [] # Non-viewers that should receive messages.
|
||||
# Create the tracks.
|
||||
if not track_ratios: track_ratios = 1, 4
|
||||
x, total = 0, sum(track_ratios)
|
||||
for ratio in track_ratios[:-1]:
|
||||
track_width = old_div(self.w * ratio, total)
|
||||
assert track_width >= 10 # minimum width 10 pixels
|
||||
self._open_track(x, track_width)
|
||||
x += track_width
|
||||
self._open_track(x, self.w - x)
|
||||
|
||||
def _open_track(self, x, w):
|
||||
'''Helper function to create the pygame surface and Track.'''
|
||||
track_surface = self.screen.subsurface((x, 0, w, self.h))
|
||||
self.tracks.append((x, Track(track_surface)))
|
||||
def _open_track(self, x, w):
|
||||
'''Helper function to create the pygame surface and Track.'''
|
||||
track_surface = self.screen.subsurface((x, 0, w, self.h))
|
||||
self.tracks.append((x, Track(track_surface)))
|
||||
|
||||
def open_viewer(self, x, y, class_):
|
||||
'''
|
||||
Open a viewer of class_ at the x, y location on the display,
|
||||
return the viewer.
|
||||
'''
|
||||
track = self._track_at(x)[0]
|
||||
V = track.open_viewer(y, class_)
|
||||
V.focus(self)
|
||||
return V
|
||||
def open_viewer(self, x, y, class_):
|
||||
'''
|
||||
Open a viewer of class_ at the x, y location on the display,
|
||||
return the viewer.
|
||||
'''
|
||||
track = self._track_at(x)[0]
|
||||
V = track.open_viewer(y, class_)
|
||||
V.focus(self)
|
||||
return V
|
||||
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer.'''
|
||||
for x, track in self.tracks:
|
||||
if track.close_viewer(viewer):
|
||||
if not track.viewers and track.hiding:
|
||||
i = self.tracks.index((x, track))
|
||||
self.tracks[i:i + 1] = track.hiding
|
||||
assert sorted(self.tracks) == self.tracks
|
||||
for _, exposed_track in track.hiding:
|
||||
exposed_track.redraw()
|
||||
if viewer is self.focused_viewer:
|
||||
self.focused_viewer = None
|
||||
break
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer.'''
|
||||
for x, track in self.tracks:
|
||||
if track.close_viewer(viewer):
|
||||
if not track.viewers and track.hiding:
|
||||
i = self.tracks.index((x, track))
|
||||
self.tracks[i:i + 1] = track.hiding
|
||||
assert sorted(self.tracks) == self.tracks
|
||||
for _, exposed_track in track.hiding:
|
||||
exposed_track.redraw()
|
||||
if viewer is self.focused_viewer:
|
||||
self.focused_viewer = None
|
||||
break
|
||||
|
||||
def change_viewer(self, viewer, y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
def change_viewer(self, viewer, y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
if track.change_viewer(viewer, y, relative):
|
||||
break
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
if track.change_viewer(viewer, y, relative):
|
||||
break
|
||||
|
||||
def grow_viewer(self, viewer):
|
||||
'''
|
||||
Cause the viewer to take up its whole track or, if it does
|
||||
already, take up another track, up to the whole screen.
|
||||
def grow_viewer(self, viewer):
|
||||
'''
|
||||
Cause the viewer to take up its whole track or, if it does
|
||||
already, take up another track, up to the whole screen.
|
||||
|
||||
This is the inverse of closing a viewer. "Growing" a viewer
|
||||
actually creates a new copy and a new track to hold it. The old
|
||||
tracks and viewers are retained, and they get restored when the
|
||||
covering track closes, which happens automatically when the last
|
||||
viewer in the covering track is closed.
|
||||
'''
|
||||
for x, track in self.tracks:
|
||||
for _, V in track.viewers:
|
||||
if V is viewer:
|
||||
return self._grow_viewer(x, track, viewer)
|
||||
This is the inverse of closing a viewer. "Growing" a viewer
|
||||
actually creates a new copy and a new track to hold it. The old
|
||||
tracks and viewers are retained, and they get restored when the
|
||||
covering track closes, which happens automatically when the last
|
||||
viewer in the covering track is closed.
|
||||
'''
|
||||
for x, track in self.tracks:
|
||||
for _, V in track.viewers:
|
||||
if V is viewer:
|
||||
return self._grow_viewer(x, track, viewer)
|
||||
|
||||
def _grow_viewer(self, x, track, viewer):
|
||||
'''Helper function to "grow" a viewer.'''
|
||||
new_viewer = None
|
||||
def _grow_viewer(self, x, track, viewer):
|
||||
'''Helper function to "grow" a viewer.'''
|
||||
new_viewer = None
|
||||
|
||||
if viewer.h < self.h:
|
||||
# replace the track with a new track that contains
|
||||
# a copy of the viewer at full height.
|
||||
new_track = Track(track.surface) # Reuse it, why not?
|
||||
new_viewer = copy(viewer)
|
||||
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = [(x, track)]
|
||||
self.tracks[self.tracks.index((x, track))] = x, new_track
|
||||
if viewer.h < self.h:
|
||||
# replace the track with a new track that contains
|
||||
# a copy of the viewer at full height.
|
||||
new_track = Track(track.surface) # Reuse it, why not?
|
||||
new_viewer = copy(viewer)
|
||||
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = [(x, track)]
|
||||
self.tracks[self.tracks.index((x, track))] = x, new_track
|
||||
|
||||
elif viewer.w < self.w:
|
||||
# replace two tracks
|
||||
i = self.tracks.index((x, track))
|
||||
try: # prefer the one on the right
|
||||
xx, xtrack = self.tracks[i + 1]
|
||||
except IndexError:
|
||||
i -= 1 # okay, the one on the left
|
||||
xx, xtrack = self.tracks[i]
|
||||
hiding = [(xx, xtrack), (x, track)]
|
||||
else:
|
||||
hiding = [(x, track), (xx, xtrack)]
|
||||
# We know there has to be at least one other track because it
|
||||
# there weren't then that implies that the one track takes up
|
||||
# the whole display screen (the only way you can get just one
|
||||
# track is by growing a viewer to cover the whole screen.)
|
||||
# Ergo, viewer.w == self.w, so this branch doesn't run.
|
||||
new_x = min(x, xx)
|
||||
new_w = track.w + xtrack.w
|
||||
r = new_x, 0, new_w, self.h
|
||||
new_track = Track(self.screen.subsurface(r))
|
||||
new_viewer = copy(viewer)
|
||||
r = 0, 0, new_w, self.h
|
||||
new_viewer.resurface(new_track.surface.subsurface(r))
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = hiding
|
||||
self.tracks[i:i + 2] = [(new_x, new_track)]
|
||||
new_viewer.draw()
|
||||
elif viewer.w < self.w:
|
||||
# replace two tracks
|
||||
i = self.tracks.index((x, track))
|
||||
try: # prefer the one on the right
|
||||
xx, xtrack = self.tracks[i + 1]
|
||||
except IndexError:
|
||||
i -= 1 # okay, the one on the left
|
||||
xx, xtrack = self.tracks[i]
|
||||
hiding = [(xx, xtrack), (x, track)]
|
||||
else:
|
||||
hiding = [(x, track), (xx, xtrack)]
|
||||
# We know there has to be at least one other track because it
|
||||
# there weren't then that implies that the one track takes up
|
||||
# the whole display screen (the only way you can get just one
|
||||
# track is by growing a viewer to cover the whole screen.)
|
||||
# Ergo, viewer.w == self.w, so this branch doesn't run.
|
||||
new_x = min(x, xx)
|
||||
new_w = track.w + xtrack.w
|
||||
r = new_x, 0, new_w, self.h
|
||||
new_track = Track(self.screen.subsurface(r))
|
||||
new_viewer = copy(viewer)
|
||||
r = 0, 0, new_w, self.h
|
||||
new_viewer.resurface(new_track.surface.subsurface(r))
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = hiding
|
||||
self.tracks[i:i + 2] = [(new_x, new_track)]
|
||||
new_viewer.draw()
|
||||
|
||||
return new_viewer
|
||||
return new_viewer
|
||||
|
||||
def _move_viewer(self, to, rel_y, viewer, _x, y):
|
||||
'''
|
||||
Helper function to move (really copy) a viewer to a new location.
|
||||
'''
|
||||
h = to.split(rel_y)
|
||||
new_viewer = copy(viewer)
|
||||
if not isinstance(to, Track):
|
||||
to = next(T for _, T in self.tracks
|
||||
for _, V in T.viewers
|
||||
if V is to)
|
||||
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
|
||||
to.viewers.append((y, new_viewer))
|
||||
to.viewers.sort() # bisect.insort() would be overkill here.
|
||||
new_viewer.draw()
|
||||
self.close_viewer(viewer)
|
||||
def _move_viewer(self, to, rel_y, viewer, _x, y):
|
||||
'''
|
||||
Helper function to move (really copy) a viewer to a new location.
|
||||
'''
|
||||
h = to.split(rel_y)
|
||||
new_viewer = copy(viewer)
|
||||
if not isinstance(to, Track):
|
||||
to = next(T for _, T in self.tracks
|
||||
for _, V in T.viewers
|
||||
if V is to)
|
||||
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
|
||||
to.viewers.append((y, new_viewer))
|
||||
to.viewers.sort() # bisect.insort() would be overkill here.
|
||||
new_viewer.draw()
|
||||
self.close_viewer(viewer)
|
||||
|
||||
def _track_at(self, x):
|
||||
'''
|
||||
Return the track at x along with the track-relative x coordinate,
|
||||
raise ValueError if x is off-screen.
|
||||
'''
|
||||
for track_x, track in self.tracks:
|
||||
if x < track_x + track.w:
|
||||
return track, x - track_x
|
||||
raise ValueError('x outside display: %r' % (x,))
|
||||
def _track_at(self, x):
|
||||
'''
|
||||
Return the track at x along with the track-relative x coordinate,
|
||||
raise ValueError if x is off-screen.
|
||||
'''
|
||||
for track_x, track in self.tracks:
|
||||
if x < track_x + track.w:
|
||||
return track, x - track_x
|
||||
raise ValueError('x outside display: %r' % (x,))
|
||||
|
||||
def at(self, x, y):
|
||||
'''
|
||||
Return the viewer (which can be a Track) at the x, y location,
|
||||
along with the relative-to-viewer-surface x and y coordinates.
|
||||
If there is no viewer at the location the Track will be returned
|
||||
instead.
|
||||
'''
|
||||
track, x = self._track_at(x)
|
||||
viewer, y = track.viewer_at(y)
|
||||
return viewer, x, y
|
||||
def at(self, x, y):
|
||||
'''
|
||||
Return the viewer (which can be a Track) at the x, y location,
|
||||
along with the relative-to-viewer-surface x and y coordinates.
|
||||
If there is no viewer at the location the Track will be returned
|
||||
instead.
|
||||
'''
|
||||
track, x = self._track_at(x)
|
||||
viewer, y = track.viewer_at(y)
|
||||
return viewer, x, y
|
||||
|
||||
def iter_viewers(self):
|
||||
'''
|
||||
Iterate through all viewers yielding (viewer, x, y) three-tuples.
|
||||
The x and y coordinates are screen pixels of the top-left corner
|
||||
of the viewer.
|
||||
'''
|
||||
for x, T in self.tracks:
|
||||
for y, V in T.viewers:
|
||||
yield V, x, y
|
||||
def iter_viewers(self):
|
||||
'''
|
||||
Iterate through all viewers yielding (viewer, x, y) three-tuples.
|
||||
The x and y coordinates are screen pixels of the top-left corner
|
||||
of the viewer.
|
||||
'''
|
||||
for x, T in self.tracks:
|
||||
for y, V in T.viewers:
|
||||
yield V, x, y
|
||||
|
||||
def done_resizing(self):
|
||||
'''
|
||||
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
|
||||
update the display when done resizing a viewer.
|
||||
'''
|
||||
for _, track in self.tracks: # This should be done by a Message?
|
||||
if track.resizing_viewer:
|
||||
track.resizing_viewer.draw()
|
||||
track.resizing_viewer = None
|
||||
break
|
||||
def done_resizing(self):
|
||||
'''
|
||||
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
|
||||
update the display when done resizing a viewer.
|
||||
'''
|
||||
for _, track in self.tracks: # This should be done by a Message?
|
||||
if track.resizing_viewer:
|
||||
track.resizing_viewer.draw()
|
||||
track.resizing_viewer = None
|
||||
break
|
||||
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers (except the sender) and all
|
||||
registered handlers.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.broadcast(message)
|
||||
for handler in self.handlers:
|
||||
handler(message)
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers (except the sender) and all
|
||||
registered handlers.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.broadcast(message)
|
||||
for handler in self.handlers:
|
||||
handler(message)
|
||||
|
||||
def redraw(self):
|
||||
'''
|
||||
Redraw all tracks (which will redraw all viewers.)
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.redraw()
|
||||
def redraw(self):
|
||||
'''
|
||||
Redraw all tracks (which will redraw all viewers.)
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.redraw()
|
||||
|
||||
def focus(self, viewer):
|
||||
'''
|
||||
Set system focus to a given viewer (or no viewer if a track.)
|
||||
'''
|
||||
if isinstance(viewer, Track):
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = None
|
||||
elif viewer is not self.focused_viewer:
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = viewer
|
||||
viewer.focus(self)
|
||||
def focus(self, viewer):
|
||||
'''
|
||||
Set system focus to a given viewer (or no viewer if a track.)
|
||||
'''
|
||||
if isinstance(viewer, Track):
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = None
|
||||
elif viewer is not self.focused_viewer:
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = viewer
|
||||
viewer.focus(self)
|
||||
|
||||
def dispatch_event(self, event):
|
||||
'''
|
||||
Display event handling.
|
||||
'''
|
||||
try:
|
||||
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
|
||||
self._keyboard_event(event)
|
||||
elif event.type in MOUSE_EVENTS:
|
||||
self._mouse_event(event)
|
||||
else:
|
||||
print((
|
||||
'received event %s Use pygame.event.set_allowed().'
|
||||
% pygame.event.event_name(event.type)
|
||||
), file=stderr)
|
||||
# Catch all exceptions and open a viewer.
|
||||
except:
|
||||
err = format_exc()
|
||||
print(err, file=stderr) # To be safe just print it right away.
|
||||
open_viewer_on_string(self, err, self.broadcast)
|
||||
def dispatch_event(self, event):
|
||||
'''
|
||||
Display event handling.
|
||||
'''
|
||||
try:
|
||||
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
|
||||
self._keyboard_event(event)
|
||||
elif event.type in MOUSE_EVENTS:
|
||||
self._mouse_event(event)
|
||||
else:
|
||||
print((
|
||||
'received event %s Use pygame.event.set_allowed().'
|
||||
% pygame.event.event_name(event.type)
|
||||
), file=stderr)
|
||||
# Catch all exceptions and open a viewer.
|
||||
except:
|
||||
err = format_exc()
|
||||
print(err, file=stderr) # To be safe just print it right away.
|
||||
open_viewer_on_string(self, err, self.broadcast)
|
||||
|
||||
def _keyboard_event(self, event):
|
||||
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
|
||||
# At least on my keyboard the break/pause key sends K_PAUSE.
|
||||
# The main use of this is to open a TextViewer if you
|
||||
# accidentally close all the viewers, so you can recover.
|
||||
raise KeyboardInterrupt('break')
|
||||
if not self.focused_viewer:
|
||||
return
|
||||
if event.type == pygame.KEYUP:
|
||||
self.focused_viewer.key_up(self, event.key, event.mod)
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
self.focused_viewer.key_down(
|
||||
self, event.unicode, event.key, event.mod)
|
||||
# This is not UnicodeType. TODO does this need to be fixed?
|
||||
# self, event.str, event.key, event.mod)
|
||||
def _keyboard_event(self, event):
|
||||
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
|
||||
# At least on my keyboard the break/pause key sends K_PAUSE.
|
||||
# The main use of this is to open a TextViewer if you
|
||||
# accidentally close all the viewers, so you can recover.
|
||||
raise KeyboardInterrupt('break')
|
||||
if not self.focused_viewer:
|
||||
return
|
||||
if event.type == pygame.KEYUP:
|
||||
self.focused_viewer.key_up(self, event.key, event.mod)
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
self.focused_viewer.key_down(
|
||||
self, event.unicode, event.key, event.mod)
|
||||
# This is not UnicodeType. TODO does this need to be fixed?
|
||||
# self, event.str, event.key, event.mod)
|
||||
|
||||
def _mouse_event(self, event):
|
||||
V, x, y = self.at(*event.pos)
|
||||
def _mouse_event(self, event):
|
||||
V, x, y = self.at(*event.pos)
|
||||
|
||||
if event.type == pygame.MOUSEMOTION:
|
||||
if not isinstance(V, Track):
|
||||
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
|
||||
if event.type == pygame.MOUSEMOTION:
|
||||
if not isinstance(V, Track):
|
||||
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
|
||||
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
self.focus(V)
|
||||
V.mouse_down(self, x, y, event.button)
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
self.focus(V)
|
||||
V.mouse_down(self, x, y, event.button)
|
||||
|
||||
else:
|
||||
assert event.type == pygame.MOUSEBUTTONUP
|
||||
else:
|
||||
assert event.type == pygame.MOUSEBUTTONUP
|
||||
|
||||
# Check for moving viewer.
|
||||
if (event.button == 2
|
||||
and self.focused_viewer
|
||||
and V is not self.focused_viewer
|
||||
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
|
||||
):
|
||||
self._move_viewer(V, y, self.focused_viewer, *event.pos)
|
||||
# Check for moving viewer.
|
||||
if (event.button == 2
|
||||
and self.focused_viewer
|
||||
and V is not self.focused_viewer
|
||||
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
|
||||
):
|
||||
self._move_viewer(V, y, self.focused_viewer, *event.pos)
|
||||
|
||||
else:
|
||||
V.mouse_up(self, x, y, event.button)
|
||||
else:
|
||||
V.mouse_up(self, x, y, event.button)
|
||||
|
||||
def init_text(self, pt, x, y, filename):
|
||||
'''
|
||||
Open and return a ``TextViewer`` on a given file (which must be present
|
||||
in the ``JOYHOME`` directory.)
|
||||
'''
|
||||
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
|
||||
viewer.content_id, viewer.lines = pt.open(filename)
|
||||
viewer.draw()
|
||||
return viewer
|
||||
def init_text(self, pt, x, y, filename):
|
||||
'''
|
||||
Open and return a ``TextViewer`` on a given file (which must be present
|
||||
in the ``JOYHOME`` directory.)
|
||||
'''
|
||||
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
|
||||
viewer.content_id, viewer.lines = pt.open(filename)
|
||||
viewer.draw()
|
||||
return viewer
|
||||
|
||||
|
||||
class Track(Viewer):
|
||||
'''
|
||||
Manage a vertical strip of the display, and the viewers on it.
|
||||
'''
|
||||
'''
|
||||
Manage a vertical strip of the display, and the viewers on it.
|
||||
'''
|
||||
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.viewers = [] # (y, viewer)
|
||||
self.hiding = None
|
||||
self.resizing_viewer = None
|
||||
self.draw()
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.viewers = [] # (y, viewer)
|
||||
self.hiding = None
|
||||
self.resizing_viewer = None
|
||||
self.draw()
|
||||
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the Track at the y coordinate and return the height
|
||||
available for a new viewer. Tracks manage a vertical strip of
|
||||
the display screen so they don't resize their surface when split.
|
||||
'''
|
||||
h = self.viewers[0][0] if self.viewers else self.h
|
||||
assert h > y
|
||||
return h - y
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the Track at the y coordinate and return the height
|
||||
available for a new viewer. Tracks manage a vertical strip of
|
||||
the display screen so they don't resize their surface when split.
|
||||
'''
|
||||
h = self.viewers[0][0] if self.viewers else self.h
|
||||
assert h > y
|
||||
return h - y
|
||||
|
||||
def draw(self, rect=None):
|
||||
'''Draw the track onto its surface, clearing all content.
|
||||
def draw(self, rect=None):
|
||||
'''Draw the track onto its surface, clearing all content.
|
||||
|
||||
If rect is passed only draw to that area. This supports e.g.
|
||||
closing a viewer that then exposes part of the track.
|
||||
'''
|
||||
self.surface.fill(GREY, rect=rect)
|
||||
If rect is passed only draw to that area. This supports e.g.
|
||||
closing a viewer that then exposes part of the track.
|
||||
'''
|
||||
self.surface.fill(GREY, rect=rect)
|
||||
|
||||
def viewer_at(self, y):
|
||||
'''
|
||||
Return the viewer at y along with the viewer-relative y coordinate,
|
||||
if there's no viewer at y return this track and y.
|
||||
'''
|
||||
for viewer_y, viewer in self.viewers:
|
||||
if viewer_y < y <= viewer_y + viewer.h:
|
||||
return viewer, y - viewer_y
|
||||
return self, y
|
||||
def viewer_at(self, y):
|
||||
'''
|
||||
Return the viewer at y along with the viewer-relative y coordinate,
|
||||
if there's no viewer at y return this track and y.
|
||||
'''
|
||||
for viewer_y, viewer in self.viewers:
|
||||
if viewer_y < y <= viewer_y + viewer.h:
|
||||
return viewer, y - viewer_y
|
||||
return self, y
|
||||
|
||||
def open_viewer(self, y, class_):
|
||||
'''Open and return a viewer of class at y.'''
|
||||
# Todo: if y coincides with some other viewer's y replace it.
|
||||
viewer, viewer_y = self.viewer_at(y)
|
||||
h = viewer.split(viewer_y)
|
||||
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
|
||||
new_viewer.draw()
|
||||
self.viewers.append((y, new_viewer))
|
||||
self.viewers.sort() # Could use bisect module but how many
|
||||
# viewers will you ever have?
|
||||
return new_viewer
|
||||
def open_viewer(self, y, class_):
|
||||
'''Open and return a viewer of class at y.'''
|
||||
# Todo: if y coincides with some other viewer's y replace it.
|
||||
viewer, viewer_y = self.viewer_at(y)
|
||||
h = viewer.split(viewer_y)
|
||||
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
|
||||
new_viewer.draw()
|
||||
self.viewers.append((y, new_viewer))
|
||||
self.viewers.sort() # Could use bisect module but how many
|
||||
# viewers will you ever have?
|
||||
return new_viewer
|
||||
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer, reuse the freed space.'''
|
||||
for y, V in self.viewers:
|
||||
if V is viewer:
|
||||
self._close_viewer(y, V)
|
||||
return True
|
||||
return False
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer, reuse the freed space.'''
|
||||
for y, V in self.viewers:
|
||||
if V is viewer:
|
||||
self._close_viewer(y, V)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _close_viewer(self, y, viewer):
|
||||
'''Helper function to do the actual closing.'''
|
||||
i = self.viewers.index((y, viewer))
|
||||
del self.viewers[i]
|
||||
if i: # The previous viewer gets the space.
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
self._grow_by(previous_viewer, previous_y, viewer.h)
|
||||
else: # This track gets the space.
|
||||
self.draw((0, y, self.w, viewer.surface.get_height()))
|
||||
viewer.close()
|
||||
def _close_viewer(self, y, viewer):
|
||||
'''Helper function to do the actual closing.'''
|
||||
i = self.viewers.index((y, viewer))
|
||||
del self.viewers[i]
|
||||
if i: # The previous viewer gets the space.
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
self._grow_by(previous_viewer, previous_y, viewer.h)
|
||||
else: # This track gets the space.
|
||||
self.draw((0, y, self.w, viewer.surface.get_height()))
|
||||
viewer.close()
|
||||
|
||||
def _grow_by(self, viewer, y, h):
|
||||
'''Grow a viewer (located at y) by height h.
|
||||
def _grow_by(self, viewer, y, h):
|
||||
'''Grow a viewer (located at y) by height h.
|
||||
|
||||
This might seem like it should be a method of the viewer, but
|
||||
the viewer knows nothing of its own y location on the screen nor
|
||||
the parent track's surface (to make a new subsurface) so it has
|
||||
to be a method of the track, which has both.
|
||||
'''
|
||||
h = viewer.surface.get_height() + h
|
||||
try:
|
||||
surface = self.surface.subsurface((0, y, self.w, h))
|
||||
except ValueError: # subsurface rectangle outside surface area
|
||||
pass
|
||||
else:
|
||||
viewer.resurface(surface)
|
||||
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
|
||||
viewer.draw()
|
||||
This might seem like it should be a method of the viewer, but
|
||||
the viewer knows nothing of its own y location on the screen nor
|
||||
the parent track's surface (to make a new subsurface) so it has
|
||||
to be a method of the track, which has both.
|
||||
'''
|
||||
h = viewer.surface.get_height() + h
|
||||
try:
|
||||
surface = self.surface.subsurface((0, y, self.w, h))
|
||||
except ValueError: # subsurface rectangle outside surface area
|
||||
pass
|
||||
else:
|
||||
viewer.resurface(surface)
|
||||
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
|
||||
viewer.draw()
|
||||
|
||||
def change_viewer(self, viewer, new_y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
def change_viewer(self, viewer, new_y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for old_y, V in self.viewers:
|
||||
if V is viewer:
|
||||
if relative: new_y += old_y
|
||||
if new_y != old_y: self._change_viewer(new_y, old_y, V)
|
||||
return True
|
||||
return False
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for old_y, V in self.viewers:
|
||||
if V is viewer:
|
||||
if relative: new_y += old_y
|
||||
if new_y != old_y: self._change_viewer(new_y, old_y, V)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _change_viewer(self, new_y, old_y, viewer):
|
||||
new_y = max(0, min(self.h, new_y))
|
||||
i = self.viewers.index((old_y, viewer))
|
||||
if new_y < old_y: # Enlarge self, shrink upper neighbor.
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
if new_y - previous_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
previous_viewer.resizing = 1
|
||||
h = previous_viewer.split(new_y - previous_y)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
h = old_y - new_y
|
||||
self._grow_by(viewer, new_y, h)
|
||||
def _change_viewer(self, new_y, old_y, viewer):
|
||||
new_y = max(0, min(self.h, new_y))
|
||||
i = self.viewers.index((old_y, viewer))
|
||||
if new_y < old_y: # Enlarge self, shrink upper neighbor.
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
if new_y - previous_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
previous_viewer.resizing = 1
|
||||
h = previous_viewer.split(new_y - previous_y)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
h = old_y - new_y
|
||||
self._grow_by(viewer, new_y, h)
|
||||
|
||||
else: # Shink self, enlarge upper neighbor.
|
||||
# Enforce invariant.
|
||||
try:
|
||||
h, _ = self.viewers[i + 1]
|
||||
except IndexError: # No next viewer.
|
||||
h = self.h
|
||||
if h - new_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
else: # Shink self, enlarge upper neighbor.
|
||||
# Enforce invariant.
|
||||
try:
|
||||
h, _ = self.viewers[i + 1]
|
||||
except IndexError: # No next viewer.
|
||||
h = self.h
|
||||
if h - new_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
|
||||
# Change the viewer and adjust the upper viewer or track.
|
||||
h = new_y - old_y
|
||||
self._grow_by(viewer, new_y, -h) # grow by negative height!
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
previous_viewer.resizing = 1
|
||||
self._grow_by(previous_viewer, previous_y, h)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
self.draw((0, old_y, self.w, h))
|
||||
# Change the viewer and adjust the upper viewer or track.
|
||||
h = new_y - old_y
|
||||
self._grow_by(viewer, new_y, -h) # grow by negative height!
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
previous_viewer.resizing = 1
|
||||
self._grow_by(previous_viewer, previous_y, h)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
self.draw((0, old_y, self.w, h))
|
||||
|
||||
self.viewers[i] = new_y, viewer
|
||||
# self.viewers.sort() # Not necessary, invariant holds.
|
||||
assert sorted(self.viewers) == self.viewers
|
||||
self.viewers[i] = new_y, viewer
|
||||
# self.viewers.sort() # Not necessary, invariant holds.
|
||||
assert sorted(self.viewers) == self.viewers
|
||||
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers on this track (except the sender.)
|
||||
'''
|
||||
for _, viewer in self.viewers:
|
||||
if viewer is not message.sender:
|
||||
viewer.handle(message)
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers on this track (except the sender.)
|
||||
'''
|
||||
for _, viewer in self.viewers:
|
||||
if viewer is not message.sender:
|
||||
viewer.handle(message)
|
||||
|
||||
def redraw(self):
|
||||
'''Redraw the track and all of its viewers.'''
|
||||
self.draw()
|
||||
for _, viewer in self.viewers:
|
||||
viewer.draw()
|
||||
def redraw(self):
|
||||
'''Redraw the track and all of its viewers.'''
|
||||
self.draw()
|
||||
for _, viewer in self.viewers:
|
||||
viewer.draw()
|
||||
|
||||
@@ -25,9 +25,9 @@ import base64, zlib
|
||||
|
||||
|
||||
def create(fn='Iosevka12.BMP'):
|
||||
with open(fn, 'rb') as f:
|
||||
data = f.read()
|
||||
return base64.encodestring(zlib.compress(data))
|
||||
with open(fn, 'rb') as f:
|
||||
data = f.read()
|
||||
return base64.encodestring(zlib.compress(data))
|
||||
|
||||
|
||||
data = StringIO(zlib.decompress(base64.decodestring('''\
|
||||
@@ -186,4 +186,4 @@ lnalXc/9SsNb2vUirzS8pV0v8gJv/w/2vRht''')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(create())
|
||||
print(create())
|
||||
|
||||
+11
-11
@@ -24,8 +24,8 @@ JOY_HOME directory.
|
||||
These contents are kept in this Python module as a base64-encoded zip
|
||||
file, so you can just do, e.g.:
|
||||
|
||||
import init_joy_home
|
||||
init_joy_home.initialize(JOY_HOME)
|
||||
import init_joy_home
|
||||
init_joy_home.initialize(JOY_HOME)
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
@@ -35,17 +35,17 @@ import base64, os, io, zipfile
|
||||
|
||||
|
||||
def initialize(joy_home):
|
||||
Z.extractall(joy_home)
|
||||
Z.extractall(joy_home)
|
||||
|
||||
|
||||
def create_data(from_dir='./default_joy_home'):
|
||||
f = io.StringIO()
|
||||
z = zipfile.ZipFile(f, mode='w')
|
||||
for fn in os.listdir(from_dir):
|
||||
from_fn = os.path.join(from_dir, fn)
|
||||
z.write(from_fn, fn)
|
||||
z.close()
|
||||
return base64.encodestring(f.getvalue())
|
||||
f = io.StringIO()
|
||||
z = zipfile.ZipFile(f, mode='w')
|
||||
for fn in os.listdir(from_dir):
|
||||
from_fn = os.path.join(from_dir, fn)
|
||||
z.write(from_fn, fn)
|
||||
z.close()
|
||||
return base64.encodestring(f.getvalue())
|
||||
|
||||
|
||||
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
||||
@@ -275,4 +275,4 @@ c3RhY2sucGlja2xlUEsFBgAAAAAGAAYAUwEAACcwAAAAAA==''')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(create_data())
|
||||
print(create_data())
|
||||
|
||||
+103
-103
@@ -41,139 +41,139 @@ FULLSCREEN = '-f' in sys.argv
|
||||
|
||||
JOY_HOME = os.environ.get('JOY_HOME')
|
||||
if JOY_HOME is None:
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
if not os.path.isabs(JOY_HOME):
|
||||
raise ValueError('what directory?')
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
if not os.path.isabs(JOY_HOME):
|
||||
raise ValueError('what directory?')
|
||||
|
||||
|
||||
def load_definitions(pt, dictionary):
|
||||
'''Load definitions from ``definitions.txt``.'''
|
||||
lines = pt.open('definitions.txt')[1]
|
||||
for line in lines:
|
||||
if '==' in line:
|
||||
DefinitionWrapper.add_def(line, dictionary)
|
||||
'''Load definitions from ``definitions.txt``.'''
|
||||
lines = pt.open('definitions.txt')[1]
|
||||
for line in lines:
|
||||
if '==' in line:
|
||||
DefinitionWrapper.add_def(line, dictionary)
|
||||
|
||||
|
||||
def load_primitives(home, name_space):
|
||||
'''Load primitives from ``library.py``.'''
|
||||
fn = os.path.join(home, 'library.py')
|
||||
if os.path.exists(fn):
|
||||
execfile(fn, name_space)
|
||||
'''Load primitives from ``library.py``.'''
|
||||
fn = os.path.join(home, 'library.py')
|
||||
if os.path.exists(fn):
|
||||
execfile(fn, name_space)
|
||||
|
||||
|
||||
def init():
|
||||
'''
|
||||
Initialize the system.
|
||||
|
||||
* Init PyGame
|
||||
* Create main window
|
||||
* Start the PyGame clock
|
||||
* Set the event mask
|
||||
* Create the PersistTask
|
||||
'''
|
||||
Initialize the system.
|
||||
|
||||
* Init PyGame
|
||||
* Create main window
|
||||
* Start the PyGame clock
|
||||
* Set the event mask
|
||||
* Create the PersistTask
|
||||
|
||||
'''
|
||||
print('Initializing Pygame...')
|
||||
pygame.init()
|
||||
print('Creating window...')
|
||||
if FULLSCREEN:
|
||||
screen = pygame.display.set_mode()
|
||||
else:
|
||||
screen = pygame.display.set_mode((1024, 768))
|
||||
clock = pygame.time.Clock()
|
||||
pygame.event.set_allowed(None)
|
||||
pygame.event.set_allowed(core.ALLOWED_EVENTS)
|
||||
pt = persist_task.PersistTask(JOY_HOME)
|
||||
return screen, clock, pt
|
||||
'''
|
||||
print('Initializing Pygame...')
|
||||
pygame.init()
|
||||
print('Creating window...')
|
||||
if FULLSCREEN:
|
||||
screen = pygame.display.set_mode()
|
||||
else:
|
||||
screen = pygame.display.set_mode((1024, 768))
|
||||
clock = pygame.time.Clock()
|
||||
pygame.event.set_allowed(None)
|
||||
pygame.event.set_allowed(core.ALLOWED_EVENTS)
|
||||
pt = persist_task.PersistTask(JOY_HOME)
|
||||
return screen, clock, pt
|
||||
|
||||
|
||||
def init_context(screen, clock, pt):
|
||||
'''
|
||||
More initialization
|
||||
'''
|
||||
More initialization
|
||||
|
||||
* Create the Joy dictionary
|
||||
* Create the Display
|
||||
* Open the log, menu, and scratch text viewers, and the stack pickle
|
||||
* Start the main loop
|
||||
* Create the World object
|
||||
* Register PersistTask and World message handlers with the Display
|
||||
* Load user function definitions.
|
||||
* Create the Joy dictionary
|
||||
* Create the Display
|
||||
* Open the log, menu, and scratch text viewers, and the stack pickle
|
||||
* Start the main loop
|
||||
* Create the World object
|
||||
* Register PersistTask and World message handlers with the Display
|
||||
* Load user function definitions.
|
||||
|
||||
'''
|
||||
D = initialize()
|
||||
d = display.Display(
|
||||
screen,
|
||||
D.__contains__,
|
||||
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
|
||||
)
|
||||
log = d.init_text(pt, 0, 0, 'log.txt')
|
||||
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
|
||||
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
|
||||
loop = core.TheLoop(d, clock)
|
||||
stack_id, stack_holder = pt.open('stack.pickle')
|
||||
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
|
||||
loop.install_task(pt.task_run, 10000) # save files every ten seconds
|
||||
d.handlers.append(pt.handle)
|
||||
d.handlers.append(world.handle)
|
||||
load_definitions(pt, D)
|
||||
return locals()
|
||||
'''
|
||||
D = initialize()
|
||||
d = display.Display(
|
||||
screen,
|
||||
D.__contains__,
|
||||
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
|
||||
)
|
||||
log = d.init_text(pt, 0, 0, 'log.txt')
|
||||
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
|
||||
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
|
||||
loop = core.TheLoop(d, clock)
|
||||
stack_id, stack_holder = pt.open('stack.pickle')
|
||||
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
|
||||
loop.install_task(pt.task_run, 10000) # save files every ten seconds
|
||||
d.handlers.append(pt.handle)
|
||||
d.handlers.append(world.handle)
|
||||
load_definitions(pt, D)
|
||||
return locals()
|
||||
|
||||
|
||||
def error_guard(loop, n=10):
|
||||
'''
|
||||
Run a loop function, retry for ``n`` exceptions.
|
||||
Prints tracebacks on ``sys.stderr``.
|
||||
'''
|
||||
error_count = 0
|
||||
while error_count < n:
|
||||
try:
|
||||
loop()
|
||||
break
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
error_count += 1
|
||||
'''
|
||||
Run a loop function, retry for ``n`` exceptions.
|
||||
Prints tracebacks on ``sys.stderr``.
|
||||
'''
|
||||
error_count = 0
|
||||
while error_count < n:
|
||||
try:
|
||||
loop()
|
||||
break
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
error_count += 1
|
||||
|
||||
|
||||
class FileFaker(object):
|
||||
'''Pretends to be a file object but writes to log instead.'''
|
||||
'''Pretends to be a file object but writes to log instead.'''
|
||||
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
|
||||
def write(self, text):
|
||||
'''Write text to log.'''
|
||||
self.log.append(text)
|
||||
def write(self, text):
|
||||
'''Write text to log.'''
|
||||
self.log.append(text)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
def main(screen, clock, pt):
|
||||
'''
|
||||
Main function.
|
||||
'''
|
||||
Main function.
|
||||
|
||||
* Call ``init_context()``
|
||||
* Load primitives
|
||||
* Create an ``evaluate`` function that lets you just eval some Python code
|
||||
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
|
||||
* Start the main loop.
|
||||
'''
|
||||
name_space = init_context(screen, clock, pt)
|
||||
load_primitives(pt.home, name_space.copy())
|
||||
* Call ``init_context()``
|
||||
* Load primitives
|
||||
* Create an ``evaluate`` function that lets you just eval some Python code
|
||||
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
|
||||
* Start the main loop.
|
||||
'''
|
||||
name_space = init_context(screen, clock, pt)
|
||||
load_primitives(pt.home, name_space.copy())
|
||||
|
||||
@SimpleFunctionWrapper
|
||||
def evaluate(stack):
|
||||
'''Evaluate the Python code text on the top of the stack.'''
|
||||
code, stack = stack
|
||||
exec(code, name_space.copy())
|
||||
return stack
|
||||
@SimpleFunctionWrapper
|
||||
def evaluate(stack):
|
||||
'''Evaluate the Python code text on the top of the stack.'''
|
||||
code, stack = stack
|
||||
exec(code, name_space.copy())
|
||||
return stack
|
||||
|
||||
name_space['D']['evaluate'] = evaluate
|
||||
name_space['D']['evaluate'] = evaluate
|
||||
|
||||
|
||||
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
|
||||
try:
|
||||
error_guard(name_space['loop'].loop)
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
|
||||
try:
|
||||
error_guard(name_space['loop'].loop)
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
|
||||
return name_space['d']
|
||||
return name_space['d']
|
||||
|
||||
+197
-197
@@ -36,239 +36,239 @@ from joy.vui import core, init_joy_home
|
||||
|
||||
|
||||
def open_repo(repo_dir=None, initialize=False):
|
||||
'''
|
||||
Open, or create, and return a Dulwich git repo object for the given
|
||||
directory. If the dir path doesn't exist it will be created. If it
|
||||
does exist but isn't a repo the result depends on the ``initialize``
|
||||
argument. If it is ``False`` (the default) a ``NotGitRepository``
|
||||
exception is raised, otherwise ``git init`` is effected in the dir.
|
||||
'''
|
||||
if not os.path.exists(repo_dir):
|
||||
os.makedirs(repo_dir, 0o700)
|
||||
return init_repo(repo_dir)
|
||||
try:
|
||||
return Repo(repo_dir)
|
||||
except NotGitRepository:
|
||||
if initialize:
|
||||
return init_repo(repo_dir)
|
||||
raise
|
||||
'''
|
||||
Open, or create, and return a Dulwich git repo object for the given
|
||||
directory. If the dir path doesn't exist it will be created. If it
|
||||
does exist but isn't a repo the result depends on the ``initialize``
|
||||
argument. If it is ``False`` (the default) a ``NotGitRepository``
|
||||
exception is raised, otherwise ``git init`` is effected in the dir.
|
||||
'''
|
||||
if not os.path.exists(repo_dir):
|
||||
os.makedirs(repo_dir, 0o700)
|
||||
return init_repo(repo_dir)
|
||||
try:
|
||||
return Repo(repo_dir)
|
||||
except NotGitRepository:
|
||||
if initialize:
|
||||
return init_repo(repo_dir)
|
||||
raise
|
||||
|
||||
|
||||
def init_repo(repo_dir):
|
||||
'''
|
||||
Initialize a git repository in the directory. Stage and commit all
|
||||
files (toplevel, not those in subdirectories if any) in the dir.
|
||||
'''
|
||||
repo = Repo.init(repo_dir)
|
||||
init_joy_home.initialize(repo_dir)
|
||||
repo.stage([
|
||||
fn
|
||||
for fn in os.listdir(repo_dir)
|
||||
if os.path.isfile(os.path.join(repo_dir, fn))
|
||||
])
|
||||
repo.do_commit('Initial commit.', committer=core.COMMITTER)
|
||||
return repo
|
||||
'''
|
||||
Initialize a git repository in the directory. Stage and commit all
|
||||
files (toplevel, not those in subdirectories if any) in the dir.
|
||||
'''
|
||||
repo = Repo.init(repo_dir)
|
||||
init_joy_home.initialize(repo_dir)
|
||||
repo.stage([
|
||||
fn
|
||||
for fn in os.listdir(repo_dir)
|
||||
if os.path.isfile(os.path.join(repo_dir, fn))
|
||||
])
|
||||
repo.do_commit('Initial commit.', committer=core.COMMITTER)
|
||||
return repo
|
||||
|
||||
|
||||
def make_repo_relative_path_maker(repo):
|
||||
'''
|
||||
Helper function to return a function that returns a path given a path,
|
||||
that's relative to the repository.
|
||||
'''
|
||||
c = repo.controldir()
|
||||
def repo_relative_path(path):
|
||||
return os.path.relpath(path, os.path.commonprefix((c, path)))
|
||||
return repo_relative_path
|
||||
'''
|
||||
Helper function to return a function that returns a path given a path,
|
||||
that's relative to the repository.
|
||||
'''
|
||||
c = repo.controldir()
|
||||
def repo_relative_path(path):
|
||||
return os.path.relpath(path, os.path.commonprefix((c, path)))
|
||||
return repo_relative_path
|
||||
|
||||
|
||||
class Resource(object):
|
||||
'''
|
||||
Handle the content of a text files as a list of lines, deal with
|
||||
saving it and staging the changes to a repo.
|
||||
'''
|
||||
'''
|
||||
Handle the content of a text files as a list of lines, deal with
|
||||
saving it and staging the changes to a repo.
|
||||
'''
|
||||
|
||||
def __init__(self, filename, repo_relative_filename, thing=None):
|
||||
self.filename = filename
|
||||
self.repo_relative_filename = repo_relative_filename
|
||||
self.thing = thing or self._from_file(open(filename))
|
||||
def __init__(self, filename, repo_relative_filename, thing=None):
|
||||
self.filename = filename
|
||||
self.repo_relative_filename = repo_relative_filename
|
||||
self.thing = thing or self._from_file(open(filename))
|
||||
|
||||
def _from_file(self, f):
|
||||
return f.read().splitlines()
|
||||
def _from_file(self, f):
|
||||
return f.read().splitlines()
|
||||
|
||||
def _to_file(self, f):
|
||||
for line in self.thing:
|
||||
print(line, file=f)
|
||||
def _to_file(self, f):
|
||||
for line in self.thing:
|
||||
print(line, file=f)
|
||||
|
||||
def persist(self, repo):
|
||||
'''
|
||||
Save the lines to the file and stage the file in the repo.
|
||||
'''
|
||||
with open(self.filename, 'w') as f:
|
||||
os.chmod(self.filename, 0o600)
|
||||
self._to_file(f)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
# For goodness's sake, write it to the disk already!
|
||||
repo.stage([self.repo_relative_filename])
|
||||
def persist(self, repo):
|
||||
'''
|
||||
Save the lines to the file and stage the file in the repo.
|
||||
'''
|
||||
with open(self.filename, 'w') as f:
|
||||
os.chmod(self.filename, 0o600)
|
||||
self._to_file(f)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
# For goodness's sake, write it to the disk already!
|
||||
repo.stage([self.repo_relative_filename])
|
||||
|
||||
|
||||
class PickledResource(Resource):
|
||||
'''
|
||||
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
|
||||
'''
|
||||
'''
|
||||
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
|
||||
'''
|
||||
|
||||
def _from_file(self, f):
|
||||
return [pickle.load(f)]
|
||||
def _from_file(self, f):
|
||||
return [pickle.load(f)]
|
||||
|
||||
def _to_file(self, f):
|
||||
pickle.dump(self.thing[0], f, protocol=2)
|
||||
def _to_file(self, f):
|
||||
pickle.dump(self.thing[0], f, protocol=2)
|
||||
|
||||
|
||||
class PersistTask(object):
|
||||
'''
|
||||
This class deals with saving changes to the git repo.
|
||||
'''
|
||||
'''
|
||||
This class deals with saving changes to the git repo.
|
||||
'''
|
||||
|
||||
LIMIT = 10
|
||||
MAX_SAVE = 10
|
||||
LIMIT = 10
|
||||
MAX_SAVE = 10
|
||||
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
self.repo = open_repo(home)
|
||||
self._r = make_repo_relative_path_maker(self.repo)
|
||||
self.counter = Counter()
|
||||
self.store = {}
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
self.repo = open_repo(home)
|
||||
self._r = make_repo_relative_path_maker(self.repo)
|
||||
self.counter = Counter()
|
||||
self.store = {}
|
||||
|
||||
def open(self, name):
|
||||
'''
|
||||
Look up the named file in home and return its content_id and data.
|
||||
'''
|
||||
fn = os.path.join(self.home, name)
|
||||
content_id = name # hash(fn)
|
||||
try:
|
||||
resource = self.store[content_id]
|
||||
except KeyError:
|
||||
R = PickledResource if name.endswith('.pickle') else Resource
|
||||
resource = self.store[content_id] = R(fn, self._r(fn))
|
||||
return content_id, resource.thing
|
||||
def open(self, name):
|
||||
'''
|
||||
Look up the named file in home and return its content_id and data.
|
||||
'''
|
||||
fn = os.path.join(self.home, name)
|
||||
content_id = name # hash(fn)
|
||||
try:
|
||||
resource = self.store[content_id]
|
||||
except KeyError:
|
||||
R = PickledResource if name.endswith('.pickle') else Resource
|
||||
resource = self.store[content_id] = R(fn, self._r(fn))
|
||||
return content_id, resource.thing
|
||||
|
||||
def handle(self, message):
|
||||
'''
|
||||
Handle messages, dispatch to ``handle_FOO()`` methods.
|
||||
'''
|
||||
if isinstance(message, core.OpenMessage):
|
||||
self.handle_open(message)
|
||||
elif isinstance(message, core.ModifyMessage):
|
||||
self.handle_modify(message)
|
||||
elif isinstance(message, core.PersistMessage):
|
||||
self.handle_persist(message)
|
||||
elif isinstance(message, core.ShutdownMessage):
|
||||
for content_id in self.counter:
|
||||
self.store[content_id].persist(self.repo)
|
||||
self.commit('shutdown')
|
||||
def handle(self, message):
|
||||
'''
|
||||
Handle messages, dispatch to ``handle_FOO()`` methods.
|
||||
'''
|
||||
if isinstance(message, core.OpenMessage):
|
||||
self.handle_open(message)
|
||||
elif isinstance(message, core.ModifyMessage):
|
||||
self.handle_modify(message)
|
||||
elif isinstance(message, core.PersistMessage):
|
||||
self.handle_persist(message)
|
||||
elif isinstance(message, core.ShutdownMessage):
|
||||
for content_id in self.counter:
|
||||
self.store[content_id].persist(self.repo)
|
||||
self.commit('shutdown')
|
||||
|
||||
def handle_open(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
message.content_id, message.thing = self.open(message.name)
|
||||
except:
|
||||
message.traceback = traceback.format_exc()
|
||||
message.status = core.ERROR
|
||||
else:
|
||||
message.status = core.SUCCESS
|
||||
def handle_open(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
message.content_id, message.thing = self.open(message.name)
|
||||
except:
|
||||
message.traceback = traceback.format_exc()
|
||||
message.status = core.ERROR
|
||||
else:
|
||||
message.status = core.SUCCESS
|
||||
|
||||
def handle_modify(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
content_id = message.details['content_id']
|
||||
except KeyError:
|
||||
return
|
||||
if not content_id:
|
||||
return
|
||||
self.counter[content_id] += 1
|
||||
if self.counter[content_id] > self.LIMIT:
|
||||
self.persist(content_id)
|
||||
self.commit('due to activity')
|
||||
def handle_modify(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
content_id = message.details['content_id']
|
||||
except KeyError:
|
||||
return
|
||||
if not content_id:
|
||||
return
|
||||
self.counter[content_id] += 1
|
||||
if self.counter[content_id] > self.LIMIT:
|
||||
self.persist(content_id)
|
||||
self.commit('due to activity')
|
||||
|
||||
def handle_persist(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
resource = self.store[message.content_id]
|
||||
except KeyError:
|
||||
resource = self.handle_persist_new(message)
|
||||
resource.persist(self.repo)
|
||||
self.commit('by request from %r' % (message.sender,))
|
||||
def handle_persist(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
resource = self.store[message.content_id]
|
||||
except KeyError:
|
||||
resource = self.handle_persist_new(message)
|
||||
resource.persist(self.repo)
|
||||
self.commit('by request from %r' % (message.sender,))
|
||||
|
||||
def handle_persist_new(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
name = message.content_id
|
||||
check_filename(name)
|
||||
fn = os.path.join(self.home, name)
|
||||
thing = message.details['thing']
|
||||
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
|
||||
resource = self.store[name] = R(fn, self._r(fn), thing)
|
||||
return resource
|
||||
def handle_persist_new(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
name = message.content_id
|
||||
check_filename(name)
|
||||
fn = os.path.join(self.home, name)
|
||||
thing = message.details['thing']
|
||||
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
|
||||
resource = self.store[name] = R(fn, self._r(fn), thing)
|
||||
return resource
|
||||
|
||||
def persist(self, content_id):
|
||||
'''
|
||||
Persist a resource.
|
||||
'''
|
||||
del self.counter[content_id]
|
||||
self.store[content_id].persist(self.repo)
|
||||
def persist(self, content_id):
|
||||
'''
|
||||
Persist a resource.
|
||||
'''
|
||||
del self.counter[content_id]
|
||||
self.store[content_id].persist(self.repo)
|
||||
|
||||
def task_run(self):
|
||||
'''
|
||||
Stage any outstanding changes.
|
||||
'''
|
||||
if not self.counter:
|
||||
return
|
||||
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
|
||||
self.persist(content_id)
|
||||
self.commit()
|
||||
def task_run(self):
|
||||
'''
|
||||
Stage any outstanding changes.
|
||||
'''
|
||||
if not self.counter:
|
||||
return
|
||||
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
|
||||
self.persist(content_id)
|
||||
self.commit()
|
||||
|
||||
def commit(self, message='auto-commit'):
|
||||
'''
|
||||
Commit.
|
||||
'''
|
||||
return self.repo.do_commit(message, committer=core.COMMITTER)
|
||||
def commit(self, message='auto-commit'):
|
||||
'''
|
||||
Commit.
|
||||
'''
|
||||
return self.repo.do_commit(message, committer=core.COMMITTER)
|
||||
|
||||
def scan(self):
|
||||
'''
|
||||
Return a sorted list of all the files in the home dir.
|
||||
'''
|
||||
return sorted([
|
||||
fn
|
||||
for fn in os.listdir(self.home)
|
||||
if os.path.isfile(os.path.join(self.home, fn))
|
||||
])
|
||||
def scan(self):
|
||||
'''
|
||||
Return a sorted list of all the files in the home dir.
|
||||
'''
|
||||
return sorted([
|
||||
fn
|
||||
for fn in os.listdir(self.home)
|
||||
if os.path.isfile(os.path.join(self.home, fn))
|
||||
])
|
||||
|
||||
|
||||
def check_filename(name):
|
||||
'''
|
||||
Sanity checks for filename.
|
||||
'''
|
||||
# TODO: improve this...
|
||||
if len(name) > 64:
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
left, dot, right = name.partition('.')
|
||||
if not left.isalnum() or dot and not right.isalnum():
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
'''
|
||||
Sanity checks for filename.
|
||||
'''
|
||||
# TODO: improve this...
|
||||
if len(name) > 64:
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
left, dot, right = name.partition('.')
|
||||
if not left.isalnum() or dot and not right.isalnum():
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
pt = PersistTask(JOY_HOME)
|
||||
content_id, thing = pt.open('stack.pickle')
|
||||
pt.persist(content_id)
|
||||
print(pt.counter)
|
||||
mm = core.ModifyMessage(None, None, content_id=content_id)
|
||||
pt.handle(mm)
|
||||
print(pt.counter)
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
pt = PersistTask(JOY_HOME)
|
||||
content_id, thing = pt.open('stack.pickle')
|
||||
pt.persist(content_id)
|
||||
print(pt.counter)
|
||||
mm = core.ModifyMessage(None, None, content_id=content_id)
|
||||
pt.handle(mm)
|
||||
print(pt.counter)
|
||||
|
||||
+33
-33
@@ -32,44 +32,44 @@ MAX_WIDTH = 64
|
||||
|
||||
|
||||
def fsi(item):
|
||||
'''Format Stack Item'''
|
||||
if isinstance(item, tuple):
|
||||
res = '[%s]' % expression_to_string(item)
|
||||
elif isinstance(item, str):
|
||||
res = '"%s"' % item
|
||||
else:
|
||||
res = str(item)
|
||||
if len(res) > MAX_WIDTH:
|
||||
return res[:MAX_WIDTH - 3] + '...'
|
||||
return res
|
||||
'''Format Stack Item'''
|
||||
if isinstance(item, tuple):
|
||||
res = '[%s]' % expression_to_string(item)
|
||||
elif isinstance(item, str):
|
||||
res = '"%s"' % item
|
||||
else:
|
||||
res = str(item)
|
||||
if len(res) > MAX_WIDTH:
|
||||
return res[:MAX_WIDTH - 3] + '...'
|
||||
return res
|
||||
|
||||
|
||||
class StackViewer(text_viewer.TextViewer):
|
||||
|
||||
def __init__(self, surface):
|
||||
super(StackViewer, self).__init__(surface)
|
||||
self.stack_holder = None
|
||||
self.content_id = 'stack viewer'
|
||||
def __init__(self, surface):
|
||||
super(StackViewer, self).__init__(surface)
|
||||
self.stack_holder = None
|
||||
self.content_id = 'stack viewer'
|
||||
|
||||
def _attach(self, display):
|
||||
if self.stack_holder:
|
||||
return
|
||||
om = core.OpenMessage(self, 'stack.pickle')
|
||||
display.broadcast(om)
|
||||
if om.status != core.SUCCESS:
|
||||
raise RuntimeError('stack unavailable')
|
||||
self.stack_holder = om.thing
|
||||
def _attach(self, display):
|
||||
if self.stack_holder:
|
||||
return
|
||||
om = core.OpenMessage(self, 'stack.pickle')
|
||||
display.broadcast(om)
|
||||
if om.status != core.SUCCESS:
|
||||
raise RuntimeError('stack unavailable')
|
||||
self.stack_holder = om.thing
|
||||
|
||||
def _update(self):
|
||||
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
|
||||
def _update(self):
|
||||
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
|
||||
|
||||
def focus(self, display):
|
||||
self._attach(display)
|
||||
super(StackViewer, self).focus(display)
|
||||
def focus(self, display):
|
||||
self._attach(display)
|
||||
super(StackViewer, self).focus(display)
|
||||
|
||||
def handle(self, message):
|
||||
if (isinstance(message, core.ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._update()
|
||||
self.draw_body()
|
||||
def handle(self, message):
|
||||
if (isinstance(message, core.ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._update()
|
||||
self.draw_body()
|
||||
|
||||
+565
-565
File diff suppressed because it is too large
Load Diff
+161
-161
@@ -32,208 +32,208 @@ from joy.vui.core import BACKGROUND, FOREGROUND
|
||||
|
||||
|
||||
class Viewer(object):
|
||||
'''
|
||||
Base Viewer class
|
||||
'''
|
||||
'''
|
||||
Base Viewer class
|
||||
'''
|
||||
|
||||
MINIMUM_HEIGHT = 11
|
||||
MINIMUM_HEIGHT = 11
|
||||
|
||||
def __init__(self, surface):
|
||||
self.resurface(surface)
|
||||
self.last_touch = 0, 0
|
||||
def __init__(self, surface):
|
||||
self.resurface(surface)
|
||||
self.last_touch = 0, 0
|
||||
|
||||
def resurface(self, surface):
|
||||
self.w, self.h = surface.get_width(), surface.get_height()
|
||||
self.surface = surface
|
||||
def resurface(self, surface):
|
||||
self.w, self.h = surface.get_width(), surface.get_height()
|
||||
self.surface = surface
|
||||
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the viewer at the y coordinate (which is relative to the
|
||||
viewer's surface and must be inside it somewhere) and return the
|
||||
remaining height. The upper part of the viewer remains (and gets
|
||||
redrawn on a new surface) and the lower space is now available
|
||||
for e.g. a new viewer.
|
||||
'''
|
||||
assert y >= self.MINIMUM_HEIGHT
|
||||
new_viewer_h = self.h - y
|
||||
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
|
||||
if y <= self.last_touch[1]: self.last_touch = 0, 0
|
||||
self.draw()
|
||||
return new_viewer_h
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the viewer at the y coordinate (which is relative to the
|
||||
viewer's surface and must be inside it somewhere) and return the
|
||||
remaining height. The upper part of the viewer remains (and gets
|
||||
redrawn on a new surface) and the lower space is now available
|
||||
for e.g. a new viewer.
|
||||
'''
|
||||
assert y >= self.MINIMUM_HEIGHT
|
||||
new_viewer_h = self.h - y
|
||||
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
|
||||
if y <= self.last_touch[1]: self.last_touch = 0, 0
|
||||
self.draw()
|
||||
return new_viewer_h
|
||||
|
||||
def handle(self, message):
|
||||
assert self is not message.sender
|
||||
pass
|
||||
def handle(self, message):
|
||||
assert self is not message.sender
|
||||
pass
|
||||
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
self.surface.fill(BACKGROUND)
|
||||
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
|
||||
# Right-hand side.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
|
||||
# Between header and body.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
|
||||
# Bottom.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
self.surface.fill(BACKGROUND)
|
||||
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
|
||||
# Right-hand side.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
|
||||
# Between header and body.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
|
||||
# Bottom.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
|
||||
|
||||
def close(self):
|
||||
'''Close the viewer and release any resources, etc...'''
|
||||
def close(self):
|
||||
'''Close the viewer and release any resources, etc...'''
|
||||
|
||||
def focus(self, display):
|
||||
pass
|
||||
def focus(self, display):
|
||||
pass
|
||||
|
||||
def unfocus(self):
|
||||
pass
|
||||
def unfocus(self):
|
||||
pass
|
||||
|
||||
# Event handling.
|
||||
# Event handling.
|
||||
|
||||
def mouse_down(self, display, x, y, button):
|
||||
self.last_touch = x, y
|
||||
def mouse_down(self, display, x, y, button):
|
||||
self.last_touch = x, y
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
pass
|
||||
def mouse_up(self, display, x, y, button):
|
||||
pass
|
||||
|
||||
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
|
||||
pass
|
||||
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
|
||||
pass
|
||||
|
||||
def key_up(self, display, key, mod):
|
||||
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
def key_up(self, display, key, mod):
|
||||
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
|
||||
def key_down(self, display, uch, key, mod):
|
||||
pass
|
||||
def key_down(self, display, uch, key, mod):
|
||||
pass
|
||||
|
||||
|
||||
class MenuViewer(Viewer):
|
||||
|
||||
'''
|
||||
MenuViewer class
|
||||
'''
|
||||
'''
|
||||
MenuViewer class
|
||||
'''
|
||||
|
||||
MINIMUM_HEIGHT = 26
|
||||
MINIMUM_HEIGHT = 26
|
||||
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.resizing = 0
|
||||
self.bg = 100, 150, 100
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.resizing = 0
|
||||
self.bg = 100, 150, 100
|
||||
|
||||
def resurface(self, surface):
|
||||
Viewer.resurface(self, surface)
|
||||
n = self.MINIMUM_HEIGHT - 2
|
||||
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
|
||||
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
|
||||
self.body_rect = pygame.rect.Rect(
|
||||
0, self.MINIMUM_HEIGHT + 1,
|
||||
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
|
||||
def resurface(self, surface):
|
||||
Viewer.resurface(self, surface)
|
||||
n = self.MINIMUM_HEIGHT - 2
|
||||
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
|
||||
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
|
||||
self.body_rect = pygame.rect.Rect(
|
||||
0, self.MINIMUM_HEIGHT + 1,
|
||||
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
|
||||
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
Viewer.draw(self)
|
||||
if not self.resizing:
|
||||
self.draw_menu()
|
||||
self.draw_body()
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
Viewer.draw(self)
|
||||
if not self.resizing:
|
||||
self.draw_menu()
|
||||
self.draw_body()
|
||||
|
||||
def draw_menu(self):
|
||||
# menu buttons
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
|
||||
def draw_menu(self):
|
||||
# menu buttons
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
|
||||
|
||||
def draw_body(self):
|
||||
self.surface.fill(self.bg, self.body_rect)
|
||||
def draw_body(self):
|
||||
self.surface.fill(self.bg, self.body_rect)
|
||||
|
||||
def mouse_down(self, display, x, y, button):
|
||||
Viewer.mouse_down(self, display, x, y, button)
|
||||
if y <= self.MINIMUM_HEIGHT:
|
||||
self.menu_click(display, x, y, button)
|
||||
else:
|
||||
bx, by = self.body_rect.topleft
|
||||
self.body_click(display, x - bx, y - by, button)
|
||||
def mouse_down(self, display, x, y, button):
|
||||
Viewer.mouse_down(self, display, x, y, button)
|
||||
if y <= self.MINIMUM_HEIGHT:
|
||||
self.menu_click(display, x, y, button)
|
||||
else:
|
||||
bx, by = self.body_rect.topleft
|
||||
self.body_click(display, x - bx, y - by, button)
|
||||
|
||||
def body_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.draw_an_a(x, y)
|
||||
def body_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.draw_an_a(x, y)
|
||||
|
||||
def menu_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.resizing = 1
|
||||
elif button == 3:
|
||||
if self.close_rect.collidepoint(x, y):
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
elif self.grow_rect.collidepoint(x, y):
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
def menu_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.resizing = 1
|
||||
elif button == 3:
|
||||
if self.close_rect.collidepoint(x, y):
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
elif self.grow_rect.collidepoint(x, y):
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
def mouse_up(self, display, x, y, button):
|
||||
|
||||
if button == 1 and self.resizing:
|
||||
if self.resizing == 2:
|
||||
self.resizing = 0
|
||||
self.draw()
|
||||
display.done_resizing()
|
||||
self.resizing = 0
|
||||
return True
|
||||
if button == 1 and self.resizing:
|
||||
if self.resizing == 2:
|
||||
self.resizing = 0
|
||||
self.draw()
|
||||
display.done_resizing()
|
||||
self.resizing = 0
|
||||
return True
|
||||
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if self.resizing and button0:
|
||||
self.resizing = 2
|
||||
display.change_viewer(self, rel_y, relative=True)
|
||||
return True
|
||||
else:
|
||||
self.resizing = 0
|
||||
#self.draw_an_a(x, y)
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if self.resizing and button0:
|
||||
self.resizing = 2
|
||||
display.change_viewer(self, rel_y, relative=True)
|
||||
return True
|
||||
else:
|
||||
self.resizing = 0
|
||||
#self.draw_an_a(x, y)
|
||||
|
||||
def key_up(self, display, key, mod):
|
||||
if Viewer.key_up(self, display, key, mod):
|
||||
return True
|
||||
def key_up(self, display, key, mod):
|
||||
if Viewer.key_up(self, display, key, mod):
|
||||
return True
|
||||
|
||||
def draw_an_a(self, x, y):
|
||||
# Draw a crude letter A.
|
||||
lw, lh = 10, 14
|
||||
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
|
||||
except ValueError: return
|
||||
draw_a(surface, blend=1)
|
||||
def draw_an_a(self, x, y):
|
||||
# Draw a crude letter A.
|
||||
lw, lh = 10, 14
|
||||
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
|
||||
except ValueError: return
|
||||
draw_a(surface, blend=1)
|
||||
|
||||
|
||||
class SomeViewer(MenuViewer):
|
||||
|
||||
def __init__(self, surface):
|
||||
MenuViewer.__init__(self, surface)
|
||||
def __init__(self, surface):
|
||||
MenuViewer.__init__(self, surface)
|
||||
|
||||
def resurface(self, surface):
|
||||
MenuViewer.resurface(self, surface)
|
||||
def resurface(self, surface):
|
||||
MenuViewer.resurface(self, surface)
|
||||
|
||||
def draw_menu(self):
|
||||
MenuViewer.draw_menu(self)
|
||||
def draw_menu(self):
|
||||
MenuViewer.draw_menu(self)
|
||||
|
||||
def draw_body(self):
|
||||
pass
|
||||
def draw_body(self):
|
||||
pass
|
||||
|
||||
def body_click(self, display, x, y, button):
|
||||
pass
|
||||
def body_click(self, display, x, y, button):
|
||||
pass
|
||||
|
||||
def menu_click(self, display, x, y, button):
|
||||
if MenuViewer.menu_click(self, display, x, y, button):
|
||||
return True
|
||||
def menu_click(self, display, x, y, button):
|
||||
if MenuViewer.menu_click(self, display, x, y, button):
|
||||
return True
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
if MenuViewer.mouse_up(self, display, x, y, button):
|
||||
return True
|
||||
def mouse_up(self, display, x, y, button):
|
||||
if MenuViewer.mouse_up(self, display, x, y, button):
|
||||
return True
|
||||
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
|
||||
button0, button1, button2):
|
||||
return True
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
|
||||
button0, button1, button2):
|
||||
return True
|
||||
|
||||
def key_down(self, display, uch, key, mod):
|
||||
try:
|
||||
print(chr(key), end=' ')
|
||||
except ValueError:
|
||||
pass
|
||||
def key_down(self, display, uch, key, mod):
|
||||
try:
|
||||
print(chr(key), end=' ')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# Note that Oberon book says that if you split at the exact top of a viewer
|
||||
@@ -243,7 +243,7 @@ class SomeViewer(MenuViewer):
|
||||
|
||||
|
||||
def draw_a(surface, color=FOREGROUND, blend=False):
|
||||
w, h = surface.get_width() - 2, surface.get_height() - 2
|
||||
pygame.draw.aalines(surface, color, False, (
|
||||
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
|
||||
), blend)
|
||||
w, h = surface.get_width() - 2, surface.get_height() - 2
|
||||
pygame.draw.aalines(surface, color, False, (
|
||||
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
|
||||
), blend)
|
||||
|
||||
Reference in New Issue
Block a user