401 lines
8.3 KiB
Python
401 lines
8.3 KiB
Python
|
# GL STDWIN
|
||
|
#
|
||
|
# See stdwingl for a convenient hack to use this instead of built-in stdwin
|
||
|
# without modifying your application, except for one line in the main file.
|
||
|
#
|
||
|
# Intrinsic differences with built-in stdwin (hard or impossible to fix):
|
||
|
# - Need to call w.close() to close a window !!!
|
||
|
# - Need to call m.close() to remove a menu !!!
|
||
|
# - Doesn't enforce the existence of at most one drawing object
|
||
|
# - No textedit package
|
||
|
# - No X11 selections
|
||
|
#
|
||
|
# Not yet implemented:
|
||
|
# - shade drawing
|
||
|
# - elliptical arc drawing (need to play with transformation)
|
||
|
# - more than one mouse button
|
||
|
# - scroll bars (need to redo viewport handling to get this)
|
||
|
# - partial redraws
|
||
|
# - dialog boxes
|
||
|
# - timer events
|
||
|
# - cursors
|
||
|
#
|
||
|
# Extra features:
|
||
|
# - color (for now, you need to know the colormap index)
|
||
|
|
||
|
|
||
|
import gl
|
||
|
import fm
|
||
|
from GL import *
|
||
|
from DEVICE import *
|
||
|
from stdwinevents import *
|
||
|
|
||
|
|
||
|
# Customizable constants
|
||
|
#
|
||
|
DEF_FONT = 'Times-Roman' # Default font
|
||
|
DEF_SIZE = 12 # Default font size (points)
|
||
|
MASK = 20 # Viewport minus scrmask
|
||
|
|
||
|
|
||
|
# A structure to hold global variables
|
||
|
#
|
||
|
class Struct: pass
|
||
|
G = Struct()
|
||
|
#
|
||
|
G.queue = [] # Pending STDWIN events
|
||
|
G.drawqueue = [] # Windows that need WE_REDRAW
|
||
|
G.windowmap = {} # Map window id to window object
|
||
|
G.windowmap['0'] = None # For convenience
|
||
|
G.focus = None # Input focus
|
||
|
G.fg = BLACK # Foreground color
|
||
|
G.bg = WHITE # Background color
|
||
|
G.def_size = 0, 0 # Default window size
|
||
|
G.def_pos = 0, 0 # Default window position
|
||
|
#
|
||
|
G.size = DEF_SIZE
|
||
|
G.font = fm.findfont(DEF_FONT).scalefont(G.size)
|
||
|
|
||
|
|
||
|
# Initialize GL
|
||
|
#
|
||
|
gl.foreground()
|
||
|
gl.noport()
|
||
|
dummygid = gl.winopen('')
|
||
|
|
||
|
# Ask for all sorts of events
|
||
|
#
|
||
|
# Both REDRAW (= resize and/or redraw!) and INPUTCHANGE are implicitly queued
|
||
|
#qdevice(REDRAW)
|
||
|
#qdevice(INPUTCHANGE)
|
||
|
#
|
||
|
# Keyboard
|
||
|
gl.qdevice(KEYBD)
|
||
|
gl.qdevice(LEFTARROWKEY)
|
||
|
gl.qdevice(RIGHTARROWKEY)
|
||
|
gl.qdevice(UPARROWKEY)
|
||
|
gl.qdevice(DOWNARROWKEY)
|
||
|
gl.qdevice(LEFTALTKEY)
|
||
|
gl.qdevice(RIGHTALTKEY)
|
||
|
#
|
||
|
# Mouse
|
||
|
gl.qdevice(LEFTMOUSE)
|
||
|
#gl.qdevice(MIDDLEMOUSE)
|
||
|
gl.qdevice(RIGHTMOUSE) # Menu button
|
||
|
# NB MOUSEX, MOUSEY events are queued on button down
|
||
|
#
|
||
|
# Window close requests
|
||
|
gl.qdevice(WINQUIT)
|
||
|
gl.qdevice(WINSHUT)
|
||
|
#
|
||
|
# These aren't needed
|
||
|
#gl.qdevice(TIMER0)
|
||
|
#gl.qdevice(WINFREEZE)
|
||
|
#gl.qdevice(WINTHAW)
|
||
|
#gl.qdevice(REDRAWICONIC)
|
||
|
|
||
|
|
||
|
# STDWIN: create a new window
|
||
|
#
|
||
|
def open(title):
|
||
|
h, v = G.def_pos
|
||
|
width, height = G.def_size
|
||
|
if h > 0 or v > 0:
|
||
|
# Choose arbitrary defaults
|
||
|
if h < 0: h = 10
|
||
|
if v < 0: v = 30
|
||
|
if width <= 0: width = 400
|
||
|
if height <= 0: height = 300
|
||
|
gl.prefposition(h, h+width, 1024-v, 1024-v-height)
|
||
|
elif width > 0 or height > 0:
|
||
|
if width <= 0: width = 400
|
||
|
if height <= 0: height = 300
|
||
|
gl.prefsize(width, height)
|
||
|
from glstdwwin import WindowObject
|
||
|
win = WindowObject()._init(title)
|
||
|
G.windowmap[`win._gid`] = win
|
||
|
return win
|
||
|
|
||
|
|
||
|
# STDWIN: set default initial window position (0 means use default)
|
||
|
#
|
||
|
def setdefwinpos(h, v):
|
||
|
G.def_pos = h, v
|
||
|
|
||
|
|
||
|
# STDWIN: set default window size (0 means use default)
|
||
|
#
|
||
|
def setdefwinsize(width, height):
|
||
|
G.def_size = width, height
|
||
|
|
||
|
|
||
|
# STDWIN: beep or ring the bell
|
||
|
#
|
||
|
def fleep():
|
||
|
gl.ringbell()
|
||
|
|
||
|
|
||
|
# STDWIN: set default foreground color
|
||
|
#
|
||
|
def setfgcolor(color):
|
||
|
G.fg = color
|
||
|
|
||
|
|
||
|
# STDWIN: set default background color
|
||
|
#
|
||
|
def setbgcolor(color):
|
||
|
G.bg = color
|
||
|
|
||
|
|
||
|
# STDWIN: get default foreground color
|
||
|
#
|
||
|
def getfgcolor():
|
||
|
return G.fgcolor
|
||
|
|
||
|
|
||
|
# STDWIN: get default background color
|
||
|
#
|
||
|
def getbgcolor():
|
||
|
return G.bgcolor
|
||
|
|
||
|
|
||
|
# Table mapping characters to key codes
|
||
|
#
|
||
|
key2code = key = {}
|
||
|
key['A'] = AKEY
|
||
|
key['B'] = BKEY
|
||
|
key['C'] = CKEY
|
||
|
key['D'] = DKEY
|
||
|
key['E'] = EKEY
|
||
|
key['F'] = FKEY
|
||
|
key['G'] = GKEY
|
||
|
key['H'] = HKEY
|
||
|
key['I'] = IKEY
|
||
|
key['J'] = JKEY
|
||
|
key['K'] = KKEY
|
||
|
key['L'] = LKEY
|
||
|
key['M'] = MKEY
|
||
|
key['N'] = NKEY
|
||
|
key['O'] = OKEY
|
||
|
key['P'] = PKEY
|
||
|
key['Q'] = QKEY
|
||
|
key['R'] = RKEY
|
||
|
key['S'] = SKEY
|
||
|
key['T'] = TKEY
|
||
|
key['U'] = UKEY
|
||
|
key['V'] = VKEY
|
||
|
key['W'] = WKEY
|
||
|
key['X'] = XKEY
|
||
|
key['Y'] = YKEY
|
||
|
key['Z'] = ZKEY
|
||
|
key['0'] = ZEROKEY
|
||
|
key['1'] = ONEKEY
|
||
|
key['2'] = TWOKEY
|
||
|
key['3'] = THREEKEY
|
||
|
key['4'] = FOURKEY
|
||
|
key['5'] = FIVEKEY
|
||
|
key['6'] = SIXKEY
|
||
|
key['7'] = SEVENKEY
|
||
|
key['8'] = EIGHTKEY
|
||
|
key['9'] = NINEKEY
|
||
|
del key
|
||
|
#
|
||
|
code2key = {}
|
||
|
codelist = []
|
||
|
for key in key2code.keys():
|
||
|
code = key2code[key]
|
||
|
code2key[`code`] = key
|
||
|
codelist.append(code)
|
||
|
del key
|
||
|
|
||
|
|
||
|
# STDWIN: wait for the next event
|
||
|
#
|
||
|
commands = {}
|
||
|
commands['\r'] = WC_RETURN
|
||
|
commands['\b'] = WC_BACKSPACE
|
||
|
commands['\t'] = WC_TAB
|
||
|
#
|
||
|
def getevent():
|
||
|
while 1:
|
||
|
#
|
||
|
# Get next event from the processed queue, if any
|
||
|
#
|
||
|
if G.queue:
|
||
|
event = G.queue[0]
|
||
|
del G.queue[0]
|
||
|
#print 'getevent from queue -->', event
|
||
|
return event
|
||
|
#
|
||
|
# Get next event from the draw queue, if any,
|
||
|
# but only if there is nothing in the system queue.
|
||
|
#
|
||
|
if G.drawqueue and not gl.qtest():
|
||
|
win = G.drawqueue[0]
|
||
|
del G.drawqueue[0]
|
||
|
gl.winset(win._gid)
|
||
|
gl.color(win._bg)
|
||
|
gl.clear()
|
||
|
event = WE_DRAW, win, win._area
|
||
|
#print 'getevent from drawqueue -->', event
|
||
|
return event
|
||
|
#
|
||
|
# Get next event from system queue, blocking if necessary
|
||
|
# until one is available.
|
||
|
# Some cases immediately return the event, others do nothing
|
||
|
# or append one or more events to the processed queue.
|
||
|
#
|
||
|
dev, val = gl.qread()
|
||
|
#
|
||
|
if dev == REDRAW:
|
||
|
win = G.windowmap[`val`]
|
||
|
old_area = win._area
|
||
|
win._fixviewport()
|
||
|
win._needredraw()
|
||
|
if old_area <> win._area:
|
||
|
#print 'getevent --> WE_SIZE'
|
||
|
return WE_SIZE, win, None
|
||
|
elif dev == KEYBD:
|
||
|
if val == 3:
|
||
|
raise KeyboardInterrupt # Control-C in window
|
||
|
character = chr(val)
|
||
|
if commands.has_key(character):
|
||
|
return WE_COMMAND, G.focus, commands[character]
|
||
|
return WE_CHAR, G.focus, character
|
||
|
elif dev == LEFTARROWKEY:
|
||
|
if val:
|
||
|
return WE_COMMAND, G.focus, WC_LEFT
|
||
|
elif dev == RIGHTARROWKEY:
|
||
|
if val:
|
||
|
return WE_COMMAND, G.focus, WC_RIGHT
|
||
|
elif dev == UPARROWKEY:
|
||
|
if val:
|
||
|
return WE_COMMAND, G.focus, WC_UP
|
||
|
elif dev == DOWNARROWKEY:
|
||
|
if val:
|
||
|
return WE_COMMAND, G.focus, WC_DOWN
|
||
|
elif dev in (LEFTALTKEY, RIGHTALTKEY):
|
||
|
if val:
|
||
|
for code in codelist:
|
||
|
gl.qdevice(code)
|
||
|
else:
|
||
|
for code in codelist:
|
||
|
gl.unqdevice(code)
|
||
|
elif dev in codelist:
|
||
|
if val:
|
||
|
event = G.focus._doshortcut(code2key[`dev`])
|
||
|
if event:
|
||
|
return event
|
||
|
elif dev == LEFTMOUSE:
|
||
|
G.mousex = gl.getvaluator(MOUSEX)
|
||
|
G.mousey = gl.getvaluator(MOUSEY)
|
||
|
if val:
|
||
|
type = WE_MOUSE_DOWN
|
||
|
gl.qdevice(MOUSEX)
|
||
|
gl.qdevice(MOUSEY)
|
||
|
else:
|
||
|
type = WE_MOUSE_UP
|
||
|
gl.unqdevice(MOUSEX)
|
||
|
gl.unqdevice(MOUSEY)
|
||
|
return _mouseevent(type)
|
||
|
elif dev == MOUSEX:
|
||
|
G.mousex = val
|
||
|
return _mouseevent(WE_MOUSE_MOVE)
|
||
|
elif dev == MOUSEY:
|
||
|
G.mousey = val
|
||
|
return _mouseevent(WE_MOUSE_MOVE)
|
||
|
elif dev == RIGHTMOUSE: # Menu button press/release
|
||
|
if val: # Press
|
||
|
event = G.focus._domenu()
|
||
|
if event:
|
||
|
return event
|
||
|
elif dev == INPUTCHANGE:
|
||
|
if G.focus:
|
||
|
G.queue.append(WE_DEACTIVATE, G.focus, None)
|
||
|
G.focus = G.windowmap[`val`]
|
||
|
if G.focus:
|
||
|
G.queue.append(WE_ACTIVATE, G.focus, None)
|
||
|
elif dev in (WINSHUT, WINQUIT):
|
||
|
return WE_CLOSE, G.windowmap[`val`], None
|
||
|
else:
|
||
|
print '*** qread() --> dev:', dev, 'val:', val
|
||
|
|
||
|
# Helper routine to construct a mouse (up, move or down) event
|
||
|
#
|
||
|
def _mouseevent(type):
|
||
|
gl.winset(G.focus._gid)
|
||
|
orgx, orgy = gl.getorigin()
|
||
|
sizex, sizey = gl.getsize()
|
||
|
x = G.mousex - orgx
|
||
|
y = G.mousey - orgy
|
||
|
return type, G.focus, ((x, sizey-y), 1, 0, 0)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# STDWIN: text measuring functions
|
||
|
|
||
|
def baseline():
|
||
|
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
|
||
|
height, nglyphs) = G.font.getfontinfo()
|
||
|
return height - yorig
|
||
|
|
||
|
def lineheight():
|
||
|
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
|
||
|
height, nglyphs) = G.font.getfontinfo()
|
||
|
return height
|
||
|
|
||
|
def textbreak(string, width):
|
||
|
# XXX Slooooow!
|
||
|
n = len(string)
|
||
|
nwidth = textwidth(string[:n])
|
||
|
while nwidth > width:
|
||
|
n = n-1
|
||
|
nwidth = textwidth(string[:n])
|
||
|
return n
|
||
|
|
||
|
def textwidth(string):
|
||
|
return G.font.getstrwidth(string)
|
||
|
|
||
|
|
||
|
# STDWIN: set default font and size
|
||
|
|
||
|
def setfont(fontname):
|
||
|
G.font = fm.findfont(fontname).scalefont(G.size)
|
||
|
|
||
|
def setsize(size):
|
||
|
ratio = float(size) / float(G.size)
|
||
|
G.size = size
|
||
|
G.font = G.font.scalefont(ratio)
|
||
|
|
||
|
|
||
|
# Utility functions
|
||
|
|
||
|
# Exclusive-or of two BYTES
|
||
|
#
|
||
|
def xor(x, y):
|
||
|
a = bits(x)
|
||
|
b = bits(y)
|
||
|
c = [0, 0, 0, 0, 0, 0, 0, 0]
|
||
|
for i in range(8):
|
||
|
c[i] = (a[i] + b[i]) % 2
|
||
|
return stib(c)
|
||
|
|
||
|
# Return the bits of a byte as a list of 8 integers
|
||
|
#
|
||
|
def bits(x):
|
||
|
b = [0, 0, 0, 0, 0, 0, 0, 0]
|
||
|
for i in range(8):
|
||
|
x, b[i] = divmod(x, 2)
|
||
|
return b
|
||
|
|
||
|
# Convert a list of 8 integers (0|1) to a byte
|
||
|
#
|
||
|
def stib(b):
|
||
|
x = 0
|
||
|
shift = 1
|
||
|
for i in range(8):
|
||
|
x = x + b[i]*shift
|
||
|
shift = shift*2
|
||
|
return x
|