PyGUI should now work without installation

This commit is contained in:
Skyler 2012-01-05 20:06:00 -05:00
parent a1b07a33c1
commit b1c83def71
298 changed files with 24226 additions and 0 deletions

356
GUI/Cocoa/Application.py Normal file
View File

@ -0,0 +1,356 @@
#------------------------------------------------------------------------------
#
# Python GUI - Application class - PyObjC
#
#------------------------------------------------------------------------------
import os, sys, traceback
import objc
from Foundation import NSObject, NSBundle, NSDefaultRunLoopMode, NSData, NSDate
import AppKit
from AppKit import NSApplication, NSResponder, NSScreen, NSMenu, NSMenuItem, \
NSKeyDown, NSKeyUp, NSMouseMoved, NSLeftMouseDown, NSSystemDefined, \
NSCommandKeyMask, NSPasteboard, NSStringPboardType, NSModalPanelRunLoopMode
NSAnyEventMask = 0xffffffff
from GUI import Globals, GApplications
from GUI import application, export
from GUI.GApplications import Application as GApplication
from GUI import Event
#------------------------------------------------------------------------------
Globals.ns_screen_height = None
Globals.ns_last_mouse_moved_event = None
Globals.pending_exception = None
Globals.ns_application = None
ns_distant_future = NSDate.distantFuture()
#------------------------------------------------------------------------------
class Application(GApplication):
# _ns_app _PyGui_NSApplication
# _ns_pasteboard NSPasteboard
# _ns_key_window Window
_ns_menubar_update_pending = False
_ns_files_opened = False
_ns_using_clargs = False
_ns_menus_updated = False
def __init__(self, **kwds):
self._ns_app = Globals.ns_application
self._ns_app.pygui_app = self
self._ns_pasteboard = NSPasteboard.generalPasteboard()
self._ns_key_window = None
GApplication.__init__(self, **kwds)
self.ns_init_application_name()
def destroy(self):
del self.menus[:]
import Windows
Windows._ns_zombie_window = None
self._ns_app.pygui_app = None
self._ns_app = None
self._ns_pasteboard = None
GApplication.destroy(self)
def set_menus(self, menu_list):
GApplication.set_menus(self, menu_list)
self._update_menubar()
def _update_menubar(self):
ns_app = self._ns_app
ns_menubar = NSMenu.alloc().initWithTitle_("")
menu_list = self._effective_menus()
for menu in menu_list:
ns_item = NSMenuItem.alloc()
ns_item.initWithTitle_action_keyEquivalent_(menu.title, '', "")
ns_menubar.addItem_(ns_item)
ns_menu = menu._ns_menu
# An NSMenu can only be a submenu of one menu at a time, so
# remove it from the old menubar if necessary.
old_supermenu = ns_menu.supermenu()
if old_supermenu:
i = old_supermenu.indexOfItemWithSubmenu_(ns_menu)
old_supermenu.removeItemAtIndex_(i)
ns_menubar.setSubmenu_forItem_(ns_menu, ns_item)
# The menu you pass to setAppleMenu_ must *also* be a member of the
# main menu.
ns_app.setMainMenu_(ns_menubar)
if menu_list:
ns_app_menu = menu_list[0]._ns_menu
ns_app.setAppleMenu_(ns_app_menu)
def handle_next_event(self, modal_window = None):
ns_app = self._ns_app
if modal_window:
ns_mode = NSModalPanelRunLoopMode
ns_modal_window = modal_window._ns_window
else:
ns_mode = NSDefaultRunLoopMode
ns_modal_window = None
ns_event = ns_app.nextEventMatchingMask_untilDate_inMode_dequeue_(
NSAnyEventMask, ns_distant_future, ns_mode, True)
if ns_event:
ns_window = ns_event.window()
if not ns_window or not ns_modal_window or ns_window == ns_modal_window:
ns_app.sendEvent_(ns_event)
def get_target_window(self):
# NSApplication.keyWindow() isn't reliable enough. We keep track
# of the key window ourselves.
return self._ns_key_window
def zero_windows_allowed(self):
return 1
def query_clipboard(self):
pb = self._ns_pasteboard
pb_types = pb.types()
return NSStringPboardType in pb_types
def get_clipboard(self):
pb = self._ns_pasteboard
ns_data = pb.dataForType_(NSStringPboardType)
if ns_data:
return ns_data.bytes().tobytes()
def set_clipboard(self, data):
ns_data = NSData.dataWithBytes_length_(data, len(data))
pb = self._ns_pasteboard
pb.clearContents()
pb.setData_forType_(ns_data, NSStringPboardType)
def setup_menus(self, m):
m.hide_app_cmd.enabled = True
m.hide_other_apps_cmd.enabled = True
m.show_all_apps_cmd.enabled = True
if not self._ns_app.modalWindow():
GApplication.setup_menus(self, m)
def process_args(self, args):
# Note: When using py2app, argv_emulation should be disabled.
if args and args[0].startswith("-psn"):
# Launched from MacOSX Finder -- wait for file open/app launch messages
pass
else:
# Not launched from Finder or using argv emulation
self._ns_using_clargs = True
GApplication.process_args(self, args)
def run(self, fast_exit = True):
try:
GApplication.run(self)
except (KeyboardInterrupt, SystemExit):
pass
except:
traceback.print_exc()
# A py2app bundled application seems to crash on exit if we don't
# bail out really quickly here (Python 2.3, PyObjC 1.3.7, py2app 0.2.1,
# MacOSX 10.4.4)
if fast_exit:
os._exit(0)
def event_loop(self):
self._ns_app.run()
def _quit(self):
self._quit_flag = True
self._ns_app.stop_(self._ns_app)
def hide_app_cmd(self):
self._ns_app.hide_(self)
def hide_other_apps_cmd(self):
self._ns_app.hideOtherApplications_(self)
def show_all_apps_cmd(self):
self._ns_app.unhideAllApplications_(self)
def ns_process_key_event(self, ns_event):
# Perform menu setup before command-key events.
# Send non-command key events to associated window if any,
# otherwise pass them to the pygui application. This is necessary
# because otherwise there is no way of receiving key events when
# there are no windows.
if ns_event.modifierFlags() & NSCommandKeyMask:
NSApplication.sendEvent_(self._ns_app, ns_event)
else:
ns_window = ns_event.window()
if ns_window:
ns_window.sendEvent_(ns_event)
else:
event = Event(ns_event)
self.handle(event.kind, event)
def ns_menu_needs_update(self, ns_menu):
try:
if not self._ns_menus_updated:
self._perform_menu_setup()
self._ns_menus_updated = True
except Exception:
self.report_exception()
def ns_init_application_name(self):
# Arrange for the application name to be used as the title
# of the application menu.
ns_bundle = NSBundle.mainBundle()
if ns_bundle:
ns_info = ns_bundle.localizedInfoDictionary()
if not ns_info:
ns_info = ns_bundle.infoDictionary()
if ns_info:
if ns_info['CFBundleName'] == "Python":
#print "GUI.Application: NSBundle infoDictionary =", ns_info ###
ns_info['CFBundleName'] = Globals.application_name
return
#------------------------------------------------------------------------------
_ns_key_event_mask = AppKit.NSKeyDownMask | AppKit.NSKeyUpMask
#------------------------------------------------------------------------------
class _PyGui_NSApplication(NSApplication):
pygui_app = None
def sendEvent_(self, ns_event):
# Perform special processing of key events.
# Perform menu setup when menu bar is clicked.
# Remember the most recent mouse-moved event to use as the
# location of event types which do not have a location themselves.
if Globals.pending_exception:
raise_pending_exception()
ns_type = ns_event.type()
self.pygui_app._ns_menus_updated = False
if (1 << ns_type) & _ns_key_event_mask:
self.pygui_app.ns_process_key_event(ns_event)
else:
if ns_type == NSMouseMoved:
Globals.ns_last_mouse_moved_event = ns_event
ns_window = ns_event.window()
if ns_window:
ns_view = ns_window.contentView().hitTest_(ns_event.locationInWindow())
if ns_view:
ns_view.mouseMoved_(ns_event)
else:
NSApplication.sendEvent_(self, ns_event)
def menuNeedsUpdate_(self, ns_menu):
self.pygui_app.ns_menu_needs_update(ns_menu)
def menuSelection_(self, ns_menu_item):
try:
command = ns_menu_item.representedObject()
index = ns_menu_item.tag()
if index >= 0:
dispatch_to_app(self, command, index)
else:
dispatch_to_app(self, command)
except:
self.pygui_app.report_error()
def validateMenuItem_(self, item):
return False
def undo_(self, sender):
dispatch_to_app(self, 'undo_cmd')
def redo_(self, sender):
dispatch_to_app(self, 'redo_cmd')
def cut_(self, sender):
dispatch_to_app(self, 'cut_cmd')
def copy_(self, sender):
dispatch_to_app(self, 'copy_cmd')
def paste_(self, sender):
dispatch_to_app(self, 'paste_cmd')
def clear_(self, sender):
dispatch_to_app(self, 'clear_cmd')
def selectAll_(self, sender):
dispatch_to_app(self, 'select_all_cmd')
def application_openFile_(self, ns_app, path):
app = self.pygui_app
if app._ns_using_clargs:
return True
# Bizarrely, argv[0] gets passed to application_openFile_ under
# some circumstances. We don't want to try to open it!
if path == sys.argv[0]:
return True
app._ns_files_opened = True
try:
app.open_path(path)
return True
except Exception, e:
app.report_error()
return False
def applicationDidFinishLaunching_(self, notification):
app = self.pygui_app
if app._ns_using_clargs:
return
try:
if not app._ns_files_opened:
app.open_app()
except Exception, e:
app.report_error()
return False
export(Application)
#------------------------------------------------------------------------------
def raise_pending_exception():
exc_type, exc_value, exc_tb = Globals.pending_exception
Globals.pending_exception = None
raise exc_type, exc_value, exc_tb
def create_ns_application():
ns_app = _PyGui_NSApplication.sharedApplication()
ns_app.setDelegate_(ns_app)
Globals.ns_application = ns_app
def dispatch_to_app(ns_app, *args):
app = ns_app.pygui_app
if app:
app.dispatch(*args)
Globals.ns_screen_height = NSScreen.mainScreen().frame().size.height
create_ns_application()
#------------------------------------------------------------------------------
# Disable this for now, since MachSignals.signal segfaults. :-(
#
#def _install_sigint_handler():
# print "_install_sigint_handler" ###
# from Foundation import NSRunLoop
# run_loop = NSRunLoop.currentRunLoop()
# if not run_loop:
# print "...No current run loop" ###
# sys.exit(1) ###
# MachSignals.signal(signal.SIGINT, _sigint_handler)
# #from PyObjCTools.AppHelper import installMachInterrupt
# #installMachInterrupt()
# print "...done" ###
#
#def _sigint_handler(signum):
# print "_sigint_handler" ###
# raise KeyboardInterrupt
#def _install_sigint_handler():
# import signal
# signal.signal(signal.SIGINT, _raise_keyboard_interrupt)
#
#def _raise_keyboard_interrupt(signum, frame):
# raise KeyboardInterrupt
#_install_sigint_handler()

View File

@ -0,0 +1,29 @@
#
# Python GUI - Basic alert functions - Cocoa
#
from AppKit import \
NSRunAlertPanel, NSRunCriticalAlertPanel, NSRunInformationalAlertPanel
def alert(kind, prompt, ok_label, **kwds):
alert_n(kind, prompt, ok_label, None, None)
def alert2(kind, prompt, yes_label, no_label, **kwds):
return alert_n(kind, prompt, yes_label, no_label, None)
def alert3(kind, prompt, yes_label, no_label, other_label, **kwds):
return alert_n(kind, prompt, yes_label, no_label, other_label)
def alert_n(kind, prompt, label1, label2, label3):
splat = prompt.split("\n", 1)
title = splat[0]
if len(splat) > 1:
msg = splat[1]
else:
msg = ""
if kind == 'caution':
return NSRunCriticalAlertPanel(title, msg, label1, label2, label3)
elif kind == 'note':
return NSRunInformationalAlertPanel(title, msg, label1, label2, label3)
else:
return NSRunAlertPanel(title, msg, label1, label2, label3)

View File

@ -0,0 +1,63 @@
#
# Python GUI - File selection dialogs - Cocoa
#
from AppKit import NSOpenPanel, NSSavePanel, NSOKButton
from GUI.Files import FileRef
from GUI import application
#------------------------------------------------------------------
def _request_old(prompt, default_dir, file_types, dir, multiple):
ns_panel = NSOpenPanel.openPanel()
if prompt.endswith(":"):
prompt = prompt[:-1]
ns_panel.setTitle_(prompt)
ns_panel.setCanChooseFiles_(not dir)
ns_panel.setCanChooseDirectories_(dir)
ns_panel.setAllowsMultipleSelection_(multiple)
if default_dir:
ns_dir = default_dir.path
else:
ns_dir = None
if file_types:
ns_types = []
for type in file_types:
ns_types.extend(type._ns_file_types())
else:
ns_types = None
result = ns_panel.runModalForDirectory_file_types_(ns_dir, None, ns_types)
if result == NSOKButton:
if multiple:
return [FileRef(path = path) for path in ns_panel.filenames()]
else:
return FileRef(path = ns_panel.filename())
else:
return None
#------------------------------------------------------------------
def _request_new(prompt, default_dir, default_name, file_type, dir):
ns_panel = NSSavePanel.savePanel()
#if prompt.endswith(":"):
# prompt = prompt[:-1]
#if prompt.lower().endswith(" as"):
# prompt = prompt[:-3]
#ns_panel.setTitle_(prompt)
#print "_request_new: setting label to", repr(prompt) ###
ns_panel.setNameFieldLabel_(prompt)
if default_dir:
ns_dir = default_dir.path
else:
ns_dir = None
if file_type:
suffix = file_type.suffix
if suffix:
ns_panel.setCanSelectHiddenExtension_(True)
if not file_type.mac_type or file_type.mac_force_suffix:
ns_panel.setRequiredFileType_(suffix)
result = ns_panel.runModalForDirectory_file_(ns_dir, default_name)
if result == NSOKButton:
return FileRef(path = ns_panel.filename())
else:
return None

45
GUI/Cocoa/Button.py Normal file
View File

@ -0,0 +1,45 @@
#
# Python GUI - Buttons - PyObjC version
#
import AppKit
from GUI import export
from GUI.StdFonts import system_font
from GUI.ButtonBasedControls import ButtonBasedControl
from GUI.GButtons import Button as GButton
_style_to_ns_key_equivalent = {
'default': "\x0d",
'cancel': "\x1b",
}
_ns_key_equivalent_to_style = {
"\x0d": 'default',
"\x1b": 'cancel',
}
class Button(ButtonBasedControl, GButton):
def __init__(self, title = "New Button", font = system_font, **kwds):
ns_button = self._create_ns_button(title = title, font = font,
ns_button_type = AppKit.NSMomentaryLight,
ns_bezel_style = AppKit.NSRoundedBezelStyle,
padding = (10, 2)
)
GButton.__init__(self, _ns_view = ns_button, **kwds)
def get_style(self):
ns_key = self._ns_view.getKeyEquivalent()
return _ns_key_equivalent_to_style.get(ns_key, 'normal')
def set_style(self, style):
ns_key = _style_to_ns_key_equivalent.get(style, "")
self._ns_view.setKeyEquivalent_(ns_key)
def activate(self):
self._ns_view.performClick_(None)
# def key_down(self, e): ###
# print "Button.key_down:", e ###
export(Button)

View File

@ -0,0 +1,86 @@
#------------------------------------------------------------------------------
#
# Python GUI - PyObjC version
#
# Mixin class for controls based on NSButton
#
#------------------------------------------------------------------------------
from Foundation import NSMutableDictionary, NSAttributedString
from AppKit import NSMutableParagraphStyle, NSFontAttributeName, \
NSForegroundColorAttributeName, NSParagraphStyleAttributeName, \
NSButton
from GUI.Utils import NSMultiClass, PyGUI_NS_EventHandler, \
ns_set_action, ns_size_to_fit
from GUI import Control
from GUI.StdColors import black
#------------------------------------------------------------------------------
class ButtonBasedControl(object):
_ns_handle_mouse = True
_color = None
def _create_ns_button(self, title, font, ns_button_type, ns_bezel_style,
padding = (0, 0)):
ns_button = PyGUI_NSButton.alloc().init()
ns_button.pygui_component = self
ns_button.setButtonType_(ns_button_type)
ns_button.setBezelStyle_(ns_bezel_style)
ns_button.setTitle_(title)
ns_button.setFont_(font._ns_font)
num_lines = title.count("\n") + 1
ns_size_to_fit(ns_button, padding = padding,
height = font.line_height * num_lines + 5)
ns_set_action(ns_button, 'doAction:')
return ns_button
def set_title(self, title):
Control.set_title(self, title)
self._ns_update_attributed_title()
def set_font(self, font):
Control.set_font(self, font)
self._ns_update_attributed_title()
def set_just(self, just):
Control.set_just(self, just)
self._ns_update_attributed_title()
def get_color(self):
if self._color:
return self._color
else:
return black
def set_color(self, color):
self._color = color
self._ns_update_attributed_title()
# There is no direct way of setting the text colour of the title;
# it must be done using an attributed string. But when doing
# this, the attributes must include the font and alignment
# as well. So when using a custom color, we construct a new
# attributed string whenever the title, font, alignment or color
# is changed.
def _ns_update_attributed_title(self):
if self._color:
ns_button = self._ns_view
ns_attrs = NSMutableDictionary.alloc().init()
ns_attrs[NSFontAttributeName] = ns_button.font()
ns_attrs[NSForegroundColorAttributeName] = self._color._ns_color
ns_parstyle = NSMutableParagraphStyle.alloc().init()
ns_parstyle.setAlignment_(ns_button.alignment())
ns_attrs[NSParagraphStyleAttributeName] = ns_parstyle
ns_attstr = NSAttributedString.alloc().initWithString_attributes_(
ns_button.title(), ns_attrs)
ns_button.setAttributedTitle_(ns_attstr)
#------------------------------------------------------------------------------
class PyGUI_NSButton(NSButton, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']

321
GUI/Cocoa/Canvas.py Normal file
View File

@ -0,0 +1,321 @@
#
# Python GUI - Drawing - PyObjC
#
from array import array
from Foundation import NSPoint, NSMakeRect, NSString
from AppKit import NSGraphicsContext, NSBezierPath, NSEvenOddWindingRule, \
NSFontAttributeName, NSForegroundColorAttributeName, \
NSCompositeCopy, NSCompositeSourceOver, NSAffineTransform
from GUI import export
from GUI.StdColors import black, white
from GUI.GCanvases import Canvas as GCanvas
import math
class Canvas(GCanvas):
def __init__(self):
self._ns_path = NSBezierPath.bezierPath()
self._ns_path.setWindingRule_(NSEvenOddWindingRule)
self._stack = []
ctx = NSGraphicsContext.currentContext()
ctx.setCompositingOperation_(NSCompositeSourceOver)
GCanvas.__init__(self)
self._printing = not ctx.isDrawingToScreen()
self.initgraphics()
self.transformstack = [[]]
def get_pencolor(self):
return self._pencolor
def set_pencolor(self, c):
self._pencolor = c
def get_fillcolor(self):
return self._fillcolor
def set_fillcolor(self, c):
self._fillcolor = c
def get_textcolor(self):
return self._textcolor
def set_textcolor(self, c):
self._textcolor = c
def get_backcolor(self):
return self._backcolor
def set_backcolor(self, c):
self._backcolor = c
def get_pensize(self):
return self._pensize
def set_pensize(self, d):
self._pensize = d
self._ns_path.setLineWidth_(d)
def get_font(self):
return self._font
def set_font(self, f):
self._font = f
def get_current_point(self):
return self._ns_path.currentPoint()
def newpath(self):
self._ns_path.removeAllPoints()
#for i in range(len(self.transformstack)):
#j = self.transformstack.pop()
#transforms = {"translate":self.translate,"rotate":self.rotate,"scale":self.scale}
#transforms[j[0]](*j[1:])
def moveto(self, x, y):
x, y = self._transform(x, y)
self._ns_path.moveToPoint_((x, y))
def rmoveto(self, dx, dy):
self._ns_path.relativeMoveToPoint_((dx, dy))
def lineto(self, x, y):
x, y = self._transform(x, y)
self._ns_path.lineToPoint_((x, y))
def rlineto(self, dx, dy):
self._ns_path.relativeLineToPoint_((dx, dy))
def curveto(self, cp1, cp2, ep):
cp1 = self._transform(*cp1)
cp2 = self._transform(*cp2)
ep = self._transform(*ep)
self._ns_path.curveToPoint_controlPoint1_controlPoint2_(
ep, cp1, cp2)
def rcurveto(self, cp1, cp2, ep):
self._ns_path.relativeCurveToPoint_controlPoint1_controlPoint2_(
ep, cp1, cp2)
def arc(self, c, r, a0, a1):
c = self._transform(*c)
self._ns_path.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_(
c, r, a0, a1)
def rect(self, rect):
try:
rect = self._transform(rect[0],rect[1]) + self._transform(rect[2], rect[3])
except:
print "Error here - line 113"
self._ns_path.appendBezierPathWithRect_(_ns_rect(rect))
def oval(self, rect):
rect = (self._transform(*rect[0]), self._transform(*rect[1]))
self._ns_path.appendBezierPathWithOvalInRect(_ns_rect(rect))
def lines(self, points):
# Due to a memory leak in PyObjC 2.3, we need to be very careful
# about the type of object that we pass to appendBezierPathWithPoints_count_.
# If 'points' is a numpy array, we convert it to an array.array of type 'f',
# else we fall back on iterating over the points in Python.
# ns = self._ns_path
# ns.moveToPoint_(points[0])
# ns.appendBezierPathWithPoints_count_(points, len(points))
try:
p = points.flat
except AttributeError:
GCanvas.lines(self, points)
else:
a = array('f', p)
ns = self._ns_path
ns.moveToPoint_(points[0])
ns.appendBezierPathWithPoints_count_(a, len(points))
def poly(self, points):
# ns = self._ns_path
# ns.moveToPoint_(points[0])
# ns.appendBezierPathWithPoints_count_(points, len(points))
# ns.closePath()
self.lines(points)
self.closepath()
def closepath(self):
self._ns_path.closePath()
def clip(self):
ns = self._ns_path
ns.addClip()
def rectclip(self, (l, t, r, b)):
ns_rect = NSMakeRect(l, t, r - l, b - t)
NSBezierPath.clipRect_(ns_rect)
def gsave(self):
self._stack.append((
self._pencolor, self._fillcolor, self._textcolor, self._backcolor,
self._pensize, self._font))
self.transformstack.append([])
NSGraphicsContext.currentContext().saveGraphicsState()
def grestore(self):
(self._pencolor, self._fillcolor, self._textcolor, self._backcolor,
self._pensize, self._font) = self._stack.pop()
self.transformstack.pop()
NSGraphicsContext.currentContext().restoreGraphicsState()
def stroke(self):
ns = self._ns_path
self._pencolor._ns_color.set()
ns.stroke()
def fill(self):
ns = self._ns_path
self._fillcolor._ns_color.set()
ns.fill()
def erase(self):
ns = self._ns_path
self._backcolor._ns_color.set()
ctx = NSGraphicsContext.currentContext()
ctx.setCompositingOperation_(NSCompositeCopy)
ns.fill()
ctx.setCompositingOperation_(NSCompositeSourceOver)
def fill_stroke(self):
ns = self._ns_path
self._pencolor._ns_color.set()
ns.stroke()
self._fillcolor._ns_color.set()
ns.fill()
def show_text(self, text):
x, y = self._ns_path.currentPoint()
font = self._font
ns_font = font._ns_font
ns_color = self._textcolor._ns_color
ns_string = NSString.stringWithString_(text)
ns_attrs = {
NSFontAttributeName: ns_font,
NSForegroundColorAttributeName: ns_color,
}
# print "Canvas.show_text:", repr(text) ###
# print "family:", ns_font.familyName() ###
# print "size:", ns_font.pointSize() ###
# print "ascender:", ns_font.ascender() ###
# print "descender:", ns_font.descender() ###
# print "capHeight:", ns_font.capHeight() ###
# print "leading:", ns_font.leading() ###
# print "matrix:", ns_font.matrix() ###
# print "defaultLineHeightForFont:", ns_font.defaultLineHeightForFont() ###
h = ns_font.defaultLineHeightForFont()
d = -ns_font.descender()
dy = h - d
if ns_font.familyName() == "Courier New":
dy += ns_font.pointSize() * 0.229167
ns_point = NSPoint(x, y - dy)
#print "drawing at:", ns_point ###
ns_string.drawAtPoint_withAttributes_(ns_point, ns_attrs)
dx = ns_font.widthOfString_(ns_string)
#self._ns_path.relativeMoveToPoint_(NSPoint(x + dx, y))
self._ns_path.relativeMoveToPoint_((dx, 0))
def _ns_frame_rect(self, (l, t, r, b)):
p = self._pensize
q = 0.5 * p
return NSMakeRect(l + q, t + q, r - l - p, b - t - p)
def stroke_rect(self, r):
self._pencolor._ns_color.set()
NSBezierPath.setDefaultLineWidth_(self._pensize)
NSBezierPath.strokeRect_(_ns_rect(r))
def frame_rect(self, r):
self._pencolor._ns_color.set()
NSBezierPath.setDefaultLineWidth_(self._pensize)
NSBezierPath.strokeRect_(self._ns_frame_rect(r))
def fill_rect(self, r):
self._fillcolor._ns_color.set()
NSBezierPath.fillRect_(_ns_rect(r))
def erase_rect(self, r):
self._backcolor._ns_color.set()
NSBezierPath.fillRect_(_ns_rect(r))
def _ns_oval_path(self, ns_rect):
ns_path = NSBezierPath.bezierPathWithOvalInRect_(ns_rect)
ns_path.setLineWidth_(self._pensize)
return ns_path
def stroke_oval(self, r):
self._pencolor._ns_color.set()
self._ns_oval_path(_ns_rect(r)).stroke()
def frame_oval(self, r):
self._pencolor._ns_color.set()
self._ns_oval_path(self._ns_frame_rect(r)).stroke()
def fill_oval(self, r):
self._fillcolor._ns_color.set()
self._ns_oval_path(_ns_rect(r)).fill()
def erase_oval(self, r):
self._backcolor._ns_color.set()
self._ns_oval_path(_ns_rect(r)).fill()
def _ns_arc_path(self, c, r, sa, ea):
ns_path = NSBezierPath.bezierPath()
ns_path.setLineWidth_(self._pensize)
ns_path.\
appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_(
c, r, sa, ea)
return ns_path
def stroke_arc(self, center, radius, start_angle, arc_angle):
ns_path = self._ns_arc_path(center, radius, start_angle, arc_angle)
self._pencolor._ns_color.set()
ns_path.stroke()
def frame_arc(self, center, radius, start_angle, arc_angle):
r = radius - 0.5 * self._pensize
ns_path = self._ns_arc_path(center, r, start_angle, arc_angle)
self._pencolor._ns_color.set()
ns_path.stroke()
def translate(self, dx, dy):
matrix = NSAffineTransform.transform()
matrix.translateXBy_yBy_(dx, dy)
self._ns_path.transformUsingAffineTransform_(matrix)
self.transformstack[-1].append(["translate",dx,dy])
def rotate(self, rotation):
matrix = NSAffineTransform.transform()
matrix.rotateByDegrees_(rotation)
self._ns_path.transformUsingAffineTransform_(matrix)
self.transformstack[-1].append(["rotate",rotation])
def scale(self, sx, sy):
matrix = NSAffineTransform.transform()
matrix.scaleXBy_yBy_(sx, sy)
self._ns_path.transformUsingAffineTransform_(matrix)
self.transformstack[-1].append(["scale",sx,sy])
def _transform(self, x, y):
for i in self.transformstack: #reversed(self.transformstack):
for j in i: # reversed(i):
if j[0]=="translate":
x = x+j[1]
y = y+j[2]
elif j[0]=="rotate":
x = x*math.cos(j[1])-y*math.sin(j[1])
y = x*math.sin(j[1])+y*math.cos(j[1])
elif j[0]=="scale":
x = x*j[1]
y = y*j[2]
return x, y
def _ns_rect((l, t, r, b)):
return NSMakeRect(l, t, r - l, b - t)
export(Canvas)

53
GUI/Cocoa/CheckBox.py Normal file
View File

@ -0,0 +1,53 @@
#
# Python GUI - Check boxes - PyObjC
#
import AppKit
from AppKit import NSOnState, NSOffState, NSMixedState
from GUI import export
from GUI.Actions import Action
from GUI.StdFonts import system_font
from GUI.ButtonBasedControls import ButtonBasedControl
from GUI.GCheckBoxes import CheckBox as GCheckBox
class CheckBox(ButtonBasedControl, GCheckBox):
_ns_mixed = False
def __init__(self, title = "New Check Box", font = system_font, **kwds):
ns_button = self._create_ns_button(title = title, font = font,
ns_button_type = AppKit.NSSwitchButton,
ns_bezel_style = AppKit.NSRoundedBezelStyle)
#if mixed:
# self._ns_mixed = True
# ns_button.setAllowsMixedState_(True)
GCheckBox.__init__(self, _ns_view = ns_button, **kwds)
def get_mixed(self):
return self._ns_view.allowsMixedState()
def set_mixed(self, x):
self._ns_view.setAllowsMixedState_(x)
def get_on(self):
state = self._ns_view.state()
if state == NSMixedState:
return 'mixed'
else:
return state <> NSOffState
def set_on(self, v):
if v == 'mixed' and self.mixed:
state = NSMixedState
elif v:
state = NSOnState
else:
state = NSOffState
self._ns_view.setState_(state)
def do_action(self):
if not self._auto_toggle:
self.on = not self.on
Action.do_action(self)
export(CheckBox)

47
GUI/Cocoa/Color.py Normal file
View File

@ -0,0 +1,47 @@
#
# Python GUI - Colors - PyObjC
#
from AppKit import NSColor, NSCalibratedRGBColorSpace
from GUI import export
from GUI.GColors import Color as GColor
NSColor.setIgnoresAlpha_(False)
class Color(GColor):
def _from_ns_color(cls, ns_color):
color = cls.__new__(cls)
color._ns_color = ns_color.colorUsingColorSpaceName_(
NSCalibratedRGBColorSpace)
return color
_from_ns_color = classmethod(_from_ns_color)
def __init__(self, red, green, blue, alpha = 1.0):
self._ns_color = NSColor.colorWithCalibratedRed_green_blue_alpha_(
red, green, blue, alpha)
def get_red(self):
return self._ns_color.redComponent()
def get_green(self):
return self._ns_color.greenComponent()
def get_blue(self):
return self._ns_color.blueComponent()
def get_alpha(self):
return self._ns_color.alphaComponent()
def get_rgb(self):
return self.get_rgba()[:3]
def get_rgba(self):
m = self._ns_color.getRed_green_blue_alpha_
try:
return m()
except TypeError:
return m(None, None, None, None)
export(Color)

11
GUI/Cocoa/Colors.py Normal file
View File

@ -0,0 +1,11 @@
#
# Python GUI - Color constants and functions - Cocoa
#
from AppKit import NSColor
from GUI import Color
rgb = Color
selection_forecolor = Color._from_ns_color(NSColor.selectedTextColor())
selection_backcolor = Color._from_ns_color(NSColor.selectedTextBackgroundColor())

128
GUI/Cocoa/Component.py Normal file
View File

@ -0,0 +1,128 @@
#
# Python GUI - Components - PyObjC
#
from Foundation import NSRect, NSPoint, NSSize, NSObject
from GUI import export
from GUI import Globals, application
from GUI import Event
from GUI.GComponents import Component as GComponent
#------------------------------------------------------------------------------
Globals._ns_view_to_component = {} # Mapping from NSView to corresponding Component
#------------------------------------------------------------------------------
class Component(GComponent):
_has_local_coords = True
_generic_tabbing = False
_ns_pass_mouse_events_to_platform = False
_ns_handle_mouse = False
_ns_accept_first_responder = False
def __init__(self, _ns_view, _ns_inner_view = None, _ns_responder = None,
_ns_set_autoresizing_mask = True, **kwds):
self._ns_view = _ns_view
if not _ns_inner_view:
_ns_inner_view = _ns_view
self._ns_inner_view = _ns_inner_view
self._ns_responder = _ns_responder or _ns_inner_view
Globals._ns_view_to_component[_ns_view] = self
GComponent.__init__(self, **kwds)
def destroy(self):
#print "Component.destroy:", self ###
GComponent.destroy(self)
_ns_view = self._ns_view
if _ns_view in Globals._ns_view_to_component:
#print "Component.destroy: removing", _ns_view, "from mapping" ###
del Globals._ns_view_to_component[_ns_view]
#print "Component.destroy: breaking link to", self._ns_view ###
self._ns_view = None
#if self._ns_inner_view: print "Component.destroy: breaking inner link to", self._ns_inner_view ###
self._ns_inner_view = None
self._ns_responder = None
def get_bounds(self):
(l, t), (w, h) = self._ns_view.frame()
return (l, t, l + w, t + h)
def set_bounds(self, (l, t, r, b)):
ns = self._ns_view
w0, h0 = ns.frame().size
w1 = r - l
h1 = b - t
ns_frame = ((l, t), (w1, h1))
old_ns_frame = ns.frame()
ns.setFrame_(ns_frame)
sv = ns.superview()
if sv:
sv.setNeedsDisplayInRect_(old_ns_frame)
sv.setNeedsDisplayInRect_(ns_frame)
if w0 != w1 or h0 != h1:
self._resized((w1 - w0, h1 - h0))
def become_target(self):
ns_view = self._ns_view
ns_window = ns_view.window()
if ns_window:
self._ns_accept_first_responder = True
ns_window.makeFirstResponder_(ns_view)
self._ns_accept_first_responder = False
def _ns_pass_to_platform(self, event, method_name):
#print "Component._ns_pass_to_platform:", self ###
h = self._ns_responder
b = h.__class__.__bases__[0]
m = getattr(b, method_name)
#print "...ns responder =", object.__repr__(h) ###
#print "...ns base class =", b ###
#print "...ns method =", m ###
m(h, event._ns_event)
def mouse_down(self, event):
if self._ns_handle_mouse:
self._ns_pass_to_platform(event, ns_mouse_down_methods[event.button])
def mouse_drag(self, event):
if self._ns_handle_mouse:
self._ns_pass_to_platform(event, 'mouseDragged_')
def mouse_up(self, event):
if self._ns_handle_mouse:
self._ns_pass_to_platform(event, ns_mouse_up_methods[event.button])
def mouse_move(self, event):
#self._ns_pass_to_platform(event, 'mouseMoved_')
pass
def mouse_enter(self, event):
#self._ns_pass_to_platform(event, 'mouseEntered_')
pass
def mouse_leave(self, event):
#self._ns_pass_to_platform(event, 'mouseExited_')
pass
def key_down(self, event):
#print "Component.key_down:", repr(event.char), "for", self ###
self._ns_pass_to_platform(event, 'keyDown_')
def key_up(self, event):
self._ns_pass_to_platform(event, 'keyUp_')
#------------------------------------------------------------------------------
ns_mouse_down_methods = {
'left': 'mouseDown_', 'middle': 'otherMouseDown_', 'right': 'rightMouseDown_'
}
ns_mouse_up_methods = {
'left': 'mouseUp_', 'middle': 'otherMouseUp_', 'right': 'rightMouseUp_'
}
#------------------------------------------------------------------------------
export(Component)

31
GUI/Cocoa/Container.py Normal file
View File

@ -0,0 +1,31 @@
#
# Python GUI - Containers - PyObjC version
#
from AppKit import NSView
from GUI.Utils import PyGUI_Flipped_NSView
from GUI import export
from GUI.GContainers import Container as GContainer
class Container(GContainer):
# _ns_inner_view NSView Containing NSView for subcomponents
# def __init__(self, _ns_view, **kwds):
# GContainer.__init__(self, _ns_view = _ns_view, **kwds)
# def destroy(self):
# #print "Container.destroy:", self ###
# GContainer.destroy(self)
# #print "Container.destroy: breaking inner link to", self._ns_inner_view ###
def _add(self, comp):
GContainer._add(self, comp)
self._ns_inner_view.addSubview_(comp._ns_view)
def _remove(self, comp):
GContainer._remove(self, comp)
comp._ns_view.removeFromSuperview()
#------------------------------------------------------------------------------
export(Container)

68
GUI/Cocoa/Control.py Normal file
View File

@ -0,0 +1,68 @@
#
# Python GUI - Controls - PyObjC
#
from math import ceil
from Foundation import NSSize
import AppKit
from GUI import export
from GUI import StdColors
from GUI import Color
from GUI import Font
from GUI.GControls import Control as GControl
_ns_alignment_from_just = {
'left': AppKit.NSLeftTextAlignment,
'center': AppKit.NSCenterTextAlignment,
'centre': AppKit.NSCenterTextAlignment,
'right': AppKit.NSRightTextAlignment,
'flush': AppKit.NSJustifiedTextAlignment,
'': AppKit.NSNaturalTextAlignment,
}
_ns_alignment_to_just = {
AppKit.NSLeftTextAlignment: 'left',
AppKit.NSCenterTextAlignment: 'center',
AppKit.NSRightTextAlignment: 'right',
AppKit.NSJustifiedTextAlignment: 'flush',
AppKit.NSNaturalTextAlignment: '',
}
class Control(GControl):
#_vertical_padding = 5
def get_title(self):
return self._ns_cell().title()
def set_title(self, v):
self._ns_cell().setTitle_(v)
def get_enabled(self):
return self._ns_cell().enabled()
def set_enabled(self, v):
self._ns_cell().setEnabled_(v)
def get_color(self):
return StdColors.black
def set_color(self, v):
pass
def get_font(self):
return Font._from_ns_font(self._ns_cell().font())
def set_font(self, f):
self._ns_cell().setFont_(f._ns_font)
def get_just(self):
return _ns_alignment_to_just[self._ns_cell().alignment()]
def set_just(self, v):
self._ns_cell().setAlignment_(_ns_alignment_from_just[v])
def _ns_cell(self):
return self._ns_inner_view.cell()
export(Control)

27
GUI/Cocoa/Cursor.py Normal file
View File

@ -0,0 +1,27 @@
#
# Python GUI - Cursors - Cocoa
#
from AppKit import NSCursor
from GUI import export
from GUI.GCursors import Cursor as GCursor
class Cursor(GCursor):
#
# _ns_cursor NSCursor
def _from_ns_cursor(cls, ns_cursor):
cursor = cls.__new__(cls)
cursor._ns_cursor = ns_cursor
return cursor
_from_ns_cursor = classmethod(_from_ns_cursor)
def _init_from_image_and_hotspot(self, image, hotspot):
#print "Cursor._init_from_image_and_hotspot:", image, hotspot ###
ns_image = image._ns_image.copy()
ns_image.setFlipped_(False)
self._ns_cursor = NSCursor.alloc().initWithImage_hotSpot_(
ns_image, hotspot)
export(Cursor)

17
GUI/Cocoa/Dialog.py Normal file
View File

@ -0,0 +1,17 @@
#
# Python GUI - Dialogs - Cocoa
#
from GUI import export
from GUI.GDialogs import Dialog #as GDialog
#class Dialog(GDialog):
#
# _default_keys = ['\r']
# _cancel_keys = ['\x1b']
#
# def key_down(self, event):
# # Cocoa already takes care of default/cancel button activation
# self.pass_to_next_handler('key_down', event)
export(Dialog)

View File

@ -0,0 +1,86 @@
#
# Python GUI - DrawableContainers - PyObjC
#
from Foundation import NSMakeRect
from AppKit import NSView, NSScrollView, NSColor
from GUI import export
from GUI.Utils import PyGUI_Flipped_NSView
from GUI import Canvas
from GUI.Geometry import rect_to_ns_rect
from GUI.Utils import NSMultiClass, PyGUI_NS_ViewBase
from GUI.GDrawableContainers import default_size, \
DrawableContainer as GDrawableContainer
ns_gray = NSColor.grayColor()
class DrawableContainer(GDrawableContainer):
def __init__(self, **kwds):
width, height = default_size
ns_frame = NSMakeRect(0, 0, width, height)
ns_inner_view = PyGUI_User_NSView.alloc().initWithFrame_(ns_frame)
if self._ns_scrollable:
ns_view = NSScrollView.alloc().initWithFrame_(ns_frame)
ns_view.setDocumentView_(ns_inner_view)
ns_view.setBackgroundColor_(ns_gray)
else:
ns_view = ns_inner_view
ns_inner_view.pygui_component = self
GDrawableContainer.__init__(self, _ns_view = ns_view, _ns_inner_view = ns_inner_view)
self.set(**kwds)
def destroy(self):
#print "View.destroy:", self ###
ns_inner_view = self._ns_inner_view
GDrawableContainer.destroy(self)
if ns_inner_view:
#print "View.destroy: breaking back link from", ns_inner_view ###
ns_inner_view.pygui_component = None
def get_background_color(self):
ns_view = self._ns_inner_view
if ns_view.drawsBackground():
return Color._from_ns_color(ns_view.backgroundColor())
def set_background_color(self, x):
ns_view = self._ns_inner_view
if x:
ns_view.setBackgroundColor_(x._ns_color)
ns_view.setDrawsBackground_(True)
else:
ns_view.setDrawsBackground_(False)
def invalidate(self):
self._ns_inner_view.setNeedsDisplay_(True)
def invalidate_rect(self, r):
self._ns_inner_view.setNeedsDisplayInRect_(rect_to_ns_rect(r))
def with_canvas(self, proc):
ns_view = self._ns_view
ns_view.lockFocus()
proc(Canvas())
ns_view.unlockFocus()
def update(self):
self._ns_view.displayIfNeeded()
def track_mouse(self):
return self._ns_track_mouse(self._ns_inner_view)
#------------------------------------------------------------------------------
class PyGUI_User_NSView(PyGUI_Flipped_NSView, PyGUI_NS_ViewBase):
#
# pygui_component View
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
def drawRect_(self, ns_rect):
(l, t), (w, h) = ns_rect
rect = (l, t, l + w, t + h)
self.pygui_component.draw(Canvas(), rect)
export(DrawableContainer)

View File

@ -0,0 +1,62 @@
#
# PyGUI - Edit command handling - Cocoa
#
from AppKit import NSMenuItem
from GUI import export
class EditCmdHandler(object):
# Mixin for Components whose _ns_responder handles the
# standard editing commands.
def setup_menus(self, m):
def validate(cmd_name, ns_selector):
ns_menu_item = NSMenuItem.alloc().\
initWithTitle_action_keyEquivalent_("", ns_selector, "")
m[cmd_name].enabled = ns_target.validateMenuItem_(ns_menu_item)
ns_target = self.window._ns_window
if ns_target:
validate('undo_cmd', 'undo:')
validate('redo_cmd', 'redo:')
ns_target = self._ns_edit_cmd_target()
if ns_target:
validate('cut_cmd', 'cut:')
validate('copy_cmd', 'copy:')
validate('paste_cmd', 'paste:')
validate('clear_cmd', 'delete:')
validate('select_all_cmd', 'selectAll:')
def undo_cmd(self):
ns_window = self.window._ns_window
if ns_window:
ns_window.undo_(None)
def redo_cmd(self):
ns_window = self.window._ns_window
if ns_window:
ns_window.redo_(None)
def cut_cmd(self):
self._ns_edit_cmd('cut_')
def copy_cmd(self):
self._ns_edit_cmd('copy_')
def paste_cmd(self):
self._ns_edit_cmd('paste_')
def clear_cmd(self):
self._ns_edit_cmd('delete_')
def select_all_cmd(self):
self._ns_edit_cmd('selectAll_')
def _ns_edit_cmd(self, ns_method_name):
ns_target = self._ns_edit_cmd_target()
if ns_target:
getattr(ns_target, ns_method_name)(None)
def _ns_edit_cmd_target(self):
return self._ns_responder
export(EditCmdHandler)

167
GUI/Cocoa/Event.py Normal file
View File

@ -0,0 +1,167 @@
#
# Python GUI - Events - PyObjC version
#
import AppKit
from AppKit import NSEvent, \
NSShiftKeyMask, NSControlKeyMask, NSCommandKeyMask, NSAlternateKeyMask
from GUI import export
from GUI import Globals
from GUI.GEvents import Event as GEvent
_ns_event_type_to_kind = {
AppKit.NSLeftMouseDown: 'mouse_down',
AppKit.NSLeftMouseUp: 'mouse_up',
AppKit.NSRightMouseDown: 'mouse_down',
AppKit.NSRightMouseUp: 'mouse_up',
AppKit.NSOtherMouseDown: 'mouse_down',
AppKit.NSOtherMouseUp: 'mouse_up',
AppKit.NSMouseMoved: 'mouse_move',
AppKit.NSLeftMouseDragged: 'mouse_drag',
AppKit.NSRightMouseDragged: 'mouse_drag',
AppKit.NSOtherMouseDragged: 'mouse_drag',
AppKit.NSMouseEntered: 'mouse_enter',
AppKit.NSMouseExited: 'mouse_leave',
AppKit.NSKeyDown: 'key_down',
AppKit.NSKeyUp: 'key_up',
AppKit.NSFlagsChanged: 'flags_changed',
AppKit.NSAppKitDefined: 'app_kit_defined',
AppKit.NSSystemDefined: 'system_defined',
AppKit.NSApplicationDefined: 'application_defined',
AppKit.NSPeriodic: 'periodic',
AppKit.NSCursorUpdate: 'cursor_update',
}
_ns_event_type_to_button = {
AppKit.NSLeftMouseDown: 'left',
AppKit.NSLeftMouseUp: 'left',
AppKit.NSRightMouseDown: 'right',
AppKit.NSRightMouseUp: 'right',
AppKit.NSOtherMouseDown: 'middle',
AppKit.NSOtherMouseUp: 'middle',
AppKit.NSLeftMouseDragged: 'left',
AppKit.NSRightMouseDragged: 'right',
AppKit.NSOtherMouseDragged: 'middle',
}
_ns_keycode_to_keyname = {
AppKit.NSUpArrowFunctionKey: 'up_arrow',
AppKit.NSDownArrowFunctionKey: 'down_arrow',
AppKit.NSLeftArrowFunctionKey: 'left_arrow',
AppKit.NSRightArrowFunctionKey: 'right_arrow',
AppKit.NSF1FunctionKey: 'f1',
AppKit.NSF2FunctionKey: 'f2',
AppKit.NSF3FunctionKey: 'f3',
AppKit.NSF4FunctionKey: 'f4',
AppKit.NSF5FunctionKey: 'f5',
AppKit.NSF6FunctionKey: 'f6',
AppKit.NSF7FunctionKey: 'f7',
AppKit.NSF8FunctionKey: 'f8',
AppKit.NSF9FunctionKey: 'f9',
AppKit.NSF10FunctionKey: 'f10',
AppKit.NSF11FunctionKey: 'f11',
AppKit.NSF12FunctionKey: 'f12',
AppKit.NSF13FunctionKey: 'f13',
AppKit.NSF14FunctionKey: 'f14',
AppKit.NSF15FunctionKey : 'f15',
AppKit.NSDeleteFunctionKey: 'delete',
AppKit.NSHomeFunctionKey: 'home',
AppKit.NSEndFunctionKey: 'end',
AppKit.NSPageUpFunctionKey: 'page_up',
AppKit.NSPageDownFunctionKey: 'page_down',
AppKit.NSClearLineFunctionKey: 'clear',
#AppKit.NSHelpFunctionKey: 'help',
AppKit.NSHelpFunctionKey: 'insert',
"\r": 'return',
"\x03": 'enter',
}
_mouse_events = [
'mouse_down', 'mouse_drag', 'mouse_up',
'mouse_move', 'mouse_enter', 'mouse_exit'
]
_key_events = [
'key_down', 'key_up'
]
_ns_screen_height = None
class Event(GEvent):
"""Platform-dependent modifiers (boolean):
command The Macintosh Command key.
option The Macintosh Option key.
"""
global_position = (0, 0)
position = (0, 0)
button = ''
num_clicks = 0
char = ""
unichars = ""
key = ''
auto = False
delta = (0, 0)
def __init__(self, ns_event):
self._ns_event = ns_event
_ns_type = ns_event.type()
kind = _ns_event_type_to_kind[_ns_type]
self.kind = kind
self.time = ns_event.timestamp()
ns_window = ns_event.window()
is_mouse_event = kind in _mouse_events
if is_mouse_event:
ns_win_pos = ns_event.locationInWindow()
x, y = ns_window.convertBaseToScreen_(ns_win_pos)
else:
ns_last_mouse = Globals.ns_last_mouse_moved_event
if ns_last_mouse:
ns_window = ns_last_mouse.window()
if ns_window:
ns_win_pos = ns_last_mouse.locationInWindow()
x, y = ns_window.convertBaseToScreen_(ns_win_pos)
else:
x, y = ns_last_mouse.locationInWindow()
else:
x, y = NSEvent.mouseLocation()
h = Globals.ns_screen_height
self.global_position = (x, h - y)
if is_mouse_event:
self.button = _ns_event_type_to_button.get(_ns_type, '')
if kind == 'mouse_down':
self.num_clicks = ns_event.clickCount()
self.delta = (ns_event.deltaX(), ns_event.deltaY())
ns_flags = ns_event.modifierFlags()
self.shift = self.extend_contig = (ns_flags & NSShiftKeyMask) <> 0
self.control = (ns_flags & NSControlKeyMask) <> 0
self.command = self.extend_noncontig = (ns_flags & NSCommandKeyMask) <> 0
self.option = (ns_flags & NSAlternateKeyMask) <> 0
if kind in _key_events:
self.auto = ns_event.isARepeat()
ns_chars = ns_event.characters()
#print "Event.__init__: ns_chars =", repr(ns_chars) ###
self.unichars = ns_chars
if len(ns_chars) == 1:
if ns_chars == "\x19" and ns_event.keyCode() == 48:
self.char = "\t"
elif ns_chars == "\x7f":
self.char = "\x08"
elif ns_chars <= "\x7e":
self.char = str(ns_chars)
#else:
# self.char = ns_chars
ns_unmod = ns_event.charactersIgnoringModifiers()
key = _ns_keycode_to_keyname.get(ns_chars, '')
if not key and u"\x20" <= ns_unmod <= u"\x7e":
key = str(ns_unmod)
self.key = key
if key == 'enter':
self.char = "\r"
elif key == 'delete':
self.char = "\x7f"
def _platform_modifiers_str(self):
return " command:%s option:%s" % (self.command, self.option)
export(Event)

43
GUI/Cocoa/Files.py Normal file
View File

@ -0,0 +1,43 @@
#
# Python GUI - File references and types - Cocoa
#
from struct import unpack
from Foundation import NSFileTypeForHFSTypeCode, \
NSFileManager, NSFileHFSCreatorCode, NSFileHFSTypeCode
from GUI.GFiles import FileRef as GFileRef, DirRef, FileType as GFileType
class FileType(GFileType):
def _ns_file_types(self):
# Return list of Cocoa file type specifications matched
# by this file type.
result = []
mac_type = self._mac_type
if mac_type:
result.append(NSFileTypeForHFSTypeCode(mac_type))
suffix = self._suffix
if suffix:
result.append(suffix)
return result
class FileRef(GFileRef):
def _set_type(self, file_type):
creator = file_type.mac_creator
type = file_type.mac_type
if creator is not None or type is not None:
fm = NSFileManager.defaultManager()
attrs = {}
if creator is not None:
attrs[NSFileHFSCreatorCode] = four_char_code(creator)
if type is not None:
attrs[NSFileHFSTypeCode] = four_char_code(type)
#print "FileRef: Setting attributes of %r to %s" % ( ###
# self.path, attrs) ###
fm.changeFileAttributes_atPath_(attrs, self.path)
def four_char_code(chars):
return unpack(">L", chars)[0]

77
GUI/Cocoa/Font.py Normal file
View File

@ -0,0 +1,77 @@
#
# Python GUI - Fonts - PyObjC
#
import sys
from AppKit import NSFont, NSFontManager, NSBoldFontMask, NSItalicFontMask, \
NSLayoutManager
from GUI import export
from GUI.GFonts import Font as GFont
_ns_font_manager = NSFontManager.sharedFontManager()
_ns_layout_manager = NSLayoutManager.alloc().init()
class Font(GFont):
# _ns_font NSFont
def _from_ns_font(cls, ns_font):
font = cls.__new__(cls)
font._ns_font = ns_font
return font
_from_ns_font = classmethod(_from_ns_font)
def __init__(self, family, size = 12, style = []):
traits = 0
if 'bold' in style:
traits |= NSBoldFontMask
if 'italic' in style:
traits |= NSItalicFontMask
self._ns_font = _ns_font_manager.fontWithFamily_traits_weight_size_(
family, traits, 5, size)
if not self._ns_font:
import StdFonts
self._ns_font = StdFonts.application_font._ns_font
def get_family(self):
return self._ns_font.familyName()
def get_size(self):
return self._ns_font.pointSize()
def get_style(self):
style = []
traits = _ns_font_manager.traitsOfFont_(self._ns_font)
if traits & NSBoldFontMask:
style.append('bold')
if traits & NSItalicFontMask:
style.append('italic')
return style
def get_ascent(self):
return self._ns_font.ascender()
def get_descent(self):
return -self._ns_font.descender()
def get_height(self):
ns_font = self._ns_font
a = ns_font.ascender()
d = ns_font.descender()
return a - d
def get_cap_height(self):
return self._ns_font.capHeight()
def get_x_height(self):
return self._ns_font.xHeight()
def get_line_height(self):
# Adding 1 here to match what NSTextField seems to do
return _ns_layout_manager.defaultLineHeightForFont_(self._ns_font) + 1
def width(self, s, start = 0, end = sys.maxint):
return self._ns_font.widthOfString_(s[start:end])
export(Font)

25
GUI/Cocoa/Frame.py Normal file
View File

@ -0,0 +1,25 @@
#------------------------------------------------------------------------------
#
# Python GUI - Frames - Cocoa
#
#------------------------------------------------------------------------------
from GUI.GFrames import Frame as GFrame
from GUI import export
from GUI.Utils import NSMultiClass
from GUI.Utils import PyGUI_NS_EventHandler, PyGUI_Flipped_NSView
class Frame(GFrame):
def __init__(self, **kwds):
ns_view = PyGUI_Frame.alloc().initWithFrame_(((0, 0), (100, 100)))
ns_view.pygui_component = self
GFrame.__init__(self, _ns_view = ns_view, **kwds)
#------------------------------------------------------------------------------
class PyGUI_Frame(PyGUI_Flipped_NSView, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
export(Frame)

185
GUI/Cocoa/GL.py Normal file
View File

@ -0,0 +1,185 @@
#
# PyGUI - OpenGL View - Cocoa
#
__all__ = ['GLConfig', 'GLView', 'GLPixmap']
import AppKit
from Foundation import NSSize
from AppKit import NSOpenGLPixelFormat, NSOpenGLView, \
NSBitmapImageRep, NSCachedImageRep, NSImage, NSAlphaFirstBitmapFormat, \
NSFloatingPointSamplesBitmapFormat
from Foundation import NSMakeRect
from OpenGL.GL import glViewport, glFlush, glFinish, glReadPixels, \
GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, \
GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_FLOAT, \
glReadPixelsub, glTexImage2D, glPixelStorei, GL_UNPACK_ALIGNMENT
from OpenGL.GLU import gluBuild2DMipmaps
from GUI.GGLViews import GLView as GGLView
from GUI.GGLPixmaps import GLPixmap as GGLPixmap
from GUI.GGLConfig import GLConfig as GGLConfig
from GUI.GLContexts import GLContext
from GUI.GLTextures import Texture
from GUI.GLDisplayLists import DisplayList
from GUI.Utils import NSMultiClass, PyGUI_NS_ViewBase
#------------------------------------------------------------------------------
class GLConfig(GGLConfig):
# _ns_pixel_format NSOpenGLPixelFormat
def _ns_get_pixel_format(self, offscreen = False):
attrs = [AppKit.NSOpenGLPFAColorSize, self._color_size]
if self._double_buffer:
attrs += [AppKit.NSOpenGLPFADoubleBuffer]
if self._alpha:
attrs += [AppKit.NSOpenGLPFAAlphaSize, self._alpha_size]
if self._stereo:
attrs += [AppKit.NSOpenGLPFAStereo]
if self._aux_buffers:
attrs += [AppKit.NSOpenGLPFAAuxBuffers, self._aux_buffers]
if self._depth_buffer:
attrs += [AppKit.NSOpenGLPFADepthSize, self._depth_size]
if self._stencil_buffer:
attrs += [AppKit.NSOpenGLPFAStencilSize, self._stencil_size]
if self._accum_buffer:
attrs += [AppKit.NSOpenGLPFAAccumSize, self._accum_size]
if self._multisample:
attrs += [AppKit.NSOpenGLPFASampleBuffers, 1]
attrs += [AppKit.NSOpenGLPFASamples, self._samples_per_pixel]
if offscreen:
attrs += [AppKit.NSOpenGLPFAOffScreen]
attrs.append(0)
ns_pf = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrs)
if not ns_pf and self._double_buffer:
attrs.remove(AppKit.NSOpenGLPFADoubleBuffer)
ns_pf = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrs)
if not ns_pf:
raise GLConfigError
return ns_pf
def _ns_set_pixel_format(self, ns_pf):
def ns_attr(attr):
return ns_pf.getValues_forAttribute_forVirtualScreen_(attr, 0)[0]
self._ns_pixel_format = ns_pf
self._double_buffer = ns_attr(AppKit.NSOpenGLPFADoubleBuffer)
self._color_size = ns_attr(AppKit.NSOpenGLPFAColorSize)
self._alpha_size = ns_attr(AppKit.NSOpenGLPFAAlphaSize)
self._alpha = self._alpha_size > 0
self._stereo = ns_attr(AppKit.NSOpenGLPFAStereo)
self._aux_buffers = ns_attr(AppKit.NSOpenGLPFAAuxBuffers)
self._depth_size = ns_attr(AppKit.NSOpenGLPFADepthSize)
self._depth_buffer = self._depth_size > 0
self._stencil_size = ns_attr(AppKit.NSOpenGLPFAStencilSize)
self._stencil_buffer = self._stencil_size > 0
self._accum_size = ns_attr(AppKit.NSOpenGLPFAAccumSize)
self._accum_buffer = self._accum_size > 0
self._multisample = ns_attr(AppKit.NSOpenGLPFASampleBuffers) > 0
self._samples_per_pixel = ns_attr(AppKit.NSOpenGLPFASamples)
def supported(self, mode = 'both'):
try:
ns_pf = self._ns_get_pixel_format()
pf = GLConfig.__new__()
pf._ns_set_pixel_format(ns_pf)
return pf
except GLConfigError:
return None
#------------------------------------------------------------------------------
class GLView(GGLView):
# _ns_view NSOpenGLView
# _ns_context NSOpenGLContext
# _ns_flush function for flushing/swapping buffers
def __init__(self, config = None, share_group = None, **kwds):
pf = GLConfig._from_args(config, kwds)
ns_pf = pf._ns_get_pixel_format()
width, height = GGLView._default_size
ns_rect = NSMakeRect(0, 0, width, height)
ns_view = _PyGUI_NSOpenGLView.alloc().initWithFrame_pixelFormat_(
ns_rect, ns_pf)
ns_view.pygui_component = self
GGLView.__init__(self, _ns_view = ns_view)
GLContext.__init__(self, share_group = share_group, _ns_pixel_format = ns_pf)
ns_context = self._ns_context
ns_view.setOpenGLContext_(ns_context)
#ns_context.setView_(ns_view) # Docs say this is needed, but
# prints warning and seems to work without.
if pf.double_buffer:
self._ns_flush = ns_context.flushBuffer
else:
self._ns_flush = glFlush
self.set(**kwds)
self.with_context(self._init_context)
def destroy(self):
#print "GLView.destroy:", self ###
ns_view = self._ns_view
GGLView.destroy(self)
#print "GLView.destroy: breaking back link from", ns_view ###
ns_view.pygui_component = None
def invalidate(self):
self._ns_view.setNeedsDisplay_(True)
def update(self):
self._ns_view.displayIfNeeded()
def track_mouse(self):
return self._ns_track_mouse(self._ns_view)
#------------------------------------------------------------------------------
class GLPixmap(GGLPixmap):
def __init__(self, width, height, config = None, share_group = None, **kwds):
pf = GLConfig._from_args(config, kwds)
ns_pf = pf._ns_get_pixel_format()
ns_size = NSSize(width, height)
ns_cache = NSCachedImageRep.alloc().initWithSize_depth_separate_alpha_(
ns_size, 0, True, True)
ns_image = NSImage.alloc().initWithSize_(ns_size)
GLContext.__init__(self, share_group = share_group, _ns_pixel_format = ns_pf)
self._ns_context.setView_(ns_cache.window().contentView())
self._init_with_ns_image(ns_image, flipped = False)
self._ns_cache = ns_cache
self.with_context(self._init_context)
def _ns_flush(self):
glFlush()
width, height = self.size
pixels = glReadPixels(0, 0, int(width), int(height), GL_RGBA, GL_UNSIGNED_BYTE)
bytes_per_row = int(width) * 4
ns_new_bitmap = NSBitmapImageRep.alloc().\
initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
(pixels, "", "", "", ""), int(width), int(height), 8, 4, True, False, AppKit.NSDeviceRGBColorSpace, bytes_per_row, 0)
ns_image = NSImage.alloc().initWithSize_(NSSize(width, height))
ns_image.addRepresentation_(ns_new_bitmap)
ns_image.lockFocus()
ns_image.unlockFocus()
self._ns_image = ns_image
self._ns_bitmap_image_rep = ns_new_bitmap
#------------------------------------------------------------------------------
class _PyGUI_NSOpenGLView(NSOpenGLView, PyGUI_NS_ViewBase):
__metaclass__ = NSMultiClass
#
# pygui_component GLView
__slots__ = ['pygui_component']
def isFlipped(self):
return True
def reshape(self):
comp = self.pygui_component
if comp.window:
comp.with_context(comp._update_viewport)
def drawRect_(self, rect):
comp = self.pygui_component
comp.with_context(comp._render, flush = True)

35
GUI/Cocoa/GLContexts.py Normal file
View File

@ -0,0 +1,35 @@
#
# PyGUI - OpenGL Contexts - Cocoa
#
from AppKit import NSOpenGLContext
from GUI.GGLContexts import GLContext as GGLContext
class GLContext(GGLContext):
# _ns_context NSOpenGLContext
def __init__(self, share_group, _ns_pixel_format):
GGLContext.__init__(self, share_group)
shared_context = self._get_shared_context()
if shared_context:
ns_share = shared_context._ns_context
else:
ns_share = None
ns_context = NSOpenGLContext.alloc().initWithFormat_shareContext_(
_ns_pixel_format, ns_share)
self._ns_context = ns_context
def _with_context(self, proc, flush):
#print "GLContext._with_context: Entering context", self._ns_context ###
old_context = NSOpenGLContext.currentContext()
self._ns_context.makeCurrentContext()
try:
self._with_share_group(proc)
if flush:
self._ns_flush()
finally:
#print "GL: Restoring previous context" ###
if old_context:
old_context.makeCurrentContext()
else:
NSOpenGLContext.clearCurrentContext()

69
GUI/Cocoa/GLTextures.py Normal file
View File

@ -0,0 +1,69 @@
#
# PyGUI - OpenGL Textures - Cocoa
#
from AppKit import NSAlphaFirstBitmapFormat, NSFloatingPointSamplesBitmapFormat
from OpenGL import GL
from GUI.GGLTextures import Texture as GTexture
class Texture(GTexture):
def _gl_get_texture_data(self, image):
ns_rep = image._ns_bitmap_image_rep
if ns_rep.numberOfPlanes() <> 1:
raise ValueError("Cannot use planar image data as GL texture")
ns_format = ns_rep.bitmapFormat()
if ns_format & NSAlphaFirstBitmapFormat:
raise ValueError("Cannot use alpha-first image data as GL texture")
fp_samples = ns_format & NSFloatingPointSamplesBitmapFormat <> 0
bits_per_pixel = ns_rep.bitsPerPixel()
bytes_per_row = ns_rep.bytesPerRow()
samples_per_pixel = ns_rep.samplesPerPixel()
if bits_per_pixel % samples_per_pixel <> 0:
raise ValueError("Image data format not usable as GL texture")
bits_per_sample = bits_per_pixel / samples_per_pixel
try:
gl_format = format_map[samples_per_pixel]
gl_type = type_map[bits_per_sample, fp_samples]
except KeyError:
raise ValueError("Image data format not usable as GL texture")
data = ns_rep.bitmapData()
if 0:
print "GUI.GLTexture._gl_get_texture_data_and_format:" ###
print "format =", gl_format_map.get(gl_format) ###
print "type =", gl_type_map.get(gl_type) ###
print "data length =", len(data) ###
print repr(data[:16]) ###
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)
return gl_format, gl_type, str(data)
#------------------------------------------------------------------------------
format_map = {
3: GL.GL_RGB,
4: GL.GL_RGBA,
1: GL.GL_LUMINANCE,
2: GL.GL_LUMINANCE_ALPHA,
}
type_map = {
(8, 0): GL.GL_UNSIGNED_BYTE,
(16, 0): GL.GL_UNSIGNED_SHORT,
(32, 0): GL.GL_UNSIGNED_INT,
(32, 1): GL.GL_FLOAT,
}
gl_format_map = {
GL.GL_RGB: 'GL_RGB',
GL.GL_RGBA: 'GL_RGBA',
GL.GL_LUMINANCE: 'GL_LUMINANCE',
GL.GL_LUMINANCE_ALPHA: 'GL_LUMINANCE_ALPHA',
}
gl_type_map = {
GL.GL_UNSIGNED_BYTE: 'GL_UNSIGNED_BYTE',
GL.GL_UNSIGNED_SHORT: 'GL_UNSIGNED_SHORT',
GL.GL_UNSIGNED_INT: 'GL_UNSIGNED_INT',
GL.GL_FLOAT: 'GL_FLOAT',
}

12
GUI/Cocoa/Geometry.py Normal file
View File

@ -0,0 +1,12 @@
#
# Python GUI - Points and Rectangles - PyObjC
#
from Foundation import NSMakeRect
from GUI.GGeometry import *
def rect_to_ns_rect((l, t, r, b)):
return NSMakeRect(l, t, r - l, b - t)
def ns_rect_to_rect(((l, t), (w, h))):
return (l, t, l + w, t + h)

31
GUI/Cocoa/Image.py Normal file
View File

@ -0,0 +1,31 @@
#
# Python GUI - Images - Cocoa
#
from Foundation import NSData
from AppKit import NSImage, NSBitmapImageRep
from GUI import export
from GUI.GImages import Image as GImage
class Image(GImage):
# _ns_bitmap_image_rep
def _init_from_file(self, file):
#ns_image = NSImage.alloc().initWithContentsOfFile_(file)
#if not ns_image:
ns_data = NSData.dataWithContentsOfFile_(file)
if not ns_data:
raise EnvironmentError("Unable to read image file: %s" % file)
ns_rep = NSBitmapImageRep.imageRepWithData_(ns_data)
if not ns_rep:
raise ValueError("Unrecognised image file type: %s" % file)
ns_rep.setSize_((ns_rep.pixelsWide(), ns_rep.pixelsHigh()))
self._init_from_ns_rep(ns_rep)
def _init_from_ns_rep(self, ns_rep):
ns_image = NSImage.alloc().init()
ns_image.addRepresentation_(ns_rep)
self._ns_bitmap_image_rep = ns_rep
self._init_with_ns_image(ns_image, flipped = True)
export(Image)

33
GUI/Cocoa/ImageBase.py Normal file
View File

@ -0,0 +1,33 @@
#
# Python GUI - Common Image/Pixmap code - Cocoa
#
from AppKit import NSCompositeSourceOver
from GUI import export
from GUI.Geometry import rect_to_ns_rect
from GUI.GImageBases import ImageBase as GImageBase
class ImageBase(GImageBase):
#
# Code common to Image, Pixmap and GLPixmap classes
def _init_with_ns_image(self, ns_image, flipped):
ns_image.setFlipped_(flipped)
self._ns_image = ns_image
def get_size(self):
return tuple(self._ns_image.size())
def get_width(self):
return self._ns_image.size()[0]
def get_height(self):
return self._ns_image.size()[1]
def draw(self, canvas, src_rect, dst_rect):
ns_src_rect = rect_to_ns_rect(src_rect)
ns_dst_rect = rect_to_ns_rect(dst_rect)
self._ns_image.drawInRect_fromRect_operation_fraction_(
ns_dst_rect, ns_src_rect, NSCompositeSourceOver, 1.0)
export(ImageBase)

28
GUI/Cocoa/Label.py Normal file
View File

@ -0,0 +1,28 @@
#
# Python GUI - Labels - PyObjC
#
import AppKit
from AppKit import NSView
from GUI import export
from GUI.StdFonts import system_font
from GUI.TextFieldBasedControls import TextFieldBasedControl
from GUI.GLabels import Label as GLabel
ns_label_autoresizing_mask = (AppKit.NSViewWidthSizable
| AppKit.NSViewHeightSizable)
class Label(TextFieldBasedControl, GLabel):
def __init__(self, text = "New Label", font = system_font, **kwds):
ns_textfield = self._create_ns_textfield(editable = False,
text = text, font = font)
# width, height = ns_textfield.frame().size
# ns_view = NSView.alloc().initWithFrame_(((0, 0), (width, height + 5)))
# ns_view.addSubview_(ns_textfield)
# ns_textfield.setFrameOrigin_((0, 2))
# ns_textfield.setAutoresizingMask_(ns_label_autoresizing_mask)
ns_view = ns_textfield
GLabel.__init__(self, _ns_view = ns_view, _ns_inner_view = ns_textfield, **kwds)
export(Label)

48
GUI/Cocoa/ListButton.py Normal file
View File

@ -0,0 +1,48 @@
#--------------------------------------------------------------
#
# PyGUI - Pop-up list control - Cocoa
#
#--------------------------------------------------------------
from AppKit import NSPopUpButton
from GUI import export
from GUI.GListButtons import ListButton as GListButton
from GUI.Utils import NSMultiClass, PyGUI_NS_EventHandler, \
ns_set_action, ns_size_to_fit
class ListButton(GListButton):
_ns_handle_mouse = True
def __init__(self, **kwds):
titles, values = self._extract_initial_items(kwds)
self._titles = titles
self._values = values
frame = ((0, 0), (100, 20))
ns = PyGUI_NSPopUpButton.alloc().initWithFrame_pullsDown_(frame, False)
ns.pygui_component = self
ns_set_action(ns, 'doAction:')
self._ns_update_items(ns)
ns_size_to_fit(ns)
GListButton.__init__(self, _ns_view = ns, **kwds)
def _update_items(self):
self._ns_update_items(self._ns_view)
def _ns_update_items(self, ns):
ns.removeAllItems()
ns.addItemsWithTitles_(self._titles)
def _get_selected_index(self):
return self._ns_view.indexOfSelectedItem()
def _set_selected_index(self, i):
self._ns_view.selectItemAtIndex_(i)
#--------------------------------------------------------------
class PyGUI_NSPopUpButton(NSPopUpButton, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
export(ListButton)

67
GUI/Cocoa/Menu.py Normal file
View File

@ -0,0 +1,67 @@
#
# Python GUI - Menus - PyObjC
#
from AppKit import NSMenu, NSMenuItem, NSOnState, \
NSCommandKeyMask, NSShiftKeyMask, NSAlternateKeyMask
from GUI import export
from GUI import Globals
from GUI.GMenus import Menu as GMenu, MenuItem
#_ns_standard_actions = {
# 'undo_cmd': 'undo:',
# 'redo_cmd': 'redo:',
# 'cut_cmd': 'cut:',
# 'copy_cmd': 'copy:',
# 'paste_cmd': 'paste:',
# 'clear_cmd': 'clear:',
# 'select_all_cmd': 'selectAll:',
#}
class Menu(GMenu):
def __init__(self, title, items, **kwds):
#print "Menu: creating with items", items ###
GMenu.__init__(self, title, items, **kwds)
ns_menu = NSMenu.alloc().initWithTitle_(title)
ns_menu.setAutoenablesItems_(False)
ns_menu.setDelegate_(Globals.ns_application)
self._ns_menu = ns_menu
def _clear_platform_menu(self):
ns_menu = self._ns_menu
n = ns_menu.numberOfItems()
while n:
n -= 1
ns_menu.removeItemAtIndex_(n)
def _add_separator_to_platform_menu(self):
ns_item = NSMenuItem.separatorItem()
self._ns_menu.addItem_(ns_item)
def _add_item_to_platform_menu(self, item, name, command = None, index = None):
key = item._key or ""
if item._shift:
key = key.upper()
else:
key = key.lower()
ns_item = NSMenuItem.alloc()
#ns_action = _ns_standard_actions.get(command, 'menuSelection:')
ns_action = 'menuSelection:'
ns_item.initWithTitle_action_keyEquivalent_(name, ns_action, key)
ns_item.setEnabled_(item.enabled)
if item.checked:
ns_item.setState_(NSOnState)
ns_modifiers = NSCommandKeyMask
if item._option:
ns_modifiers |= NSAlternateKeyMask
ns_item.setKeyEquivalentModifierMask_(ns_modifiers)
ns_item.setRepresentedObject_(command)
if index is not None:
ns_tag = index
else:
ns_tag = -1
ns_item.setTag_(ns_tag)
self._ns_menu.addItem_(ns_item)
export(Menu)

50
GUI/Cocoa/Numerical.py Normal file
View File

@ -0,0 +1,50 @@
#--------------------------------------------------------------
#
# PyGUI - NumPy interface - Cocoa
#
#--------------------------------------------------------------
from AppKit import NSBitmapImageRep, \
NSAlphaNonpremultipliedBitmapFormat, NSCalibratedRGBColorSpace
from GUI import Image
# HACK! PyObjC 2.3 incorrectly wraps the following method, so we change the
# signature and pass the bitmap data in using ctypes.
NSBitmapImageRep.__dict__['initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_'].signature = '@52@0:4^v8i12i16i20i24c28c32@36I40i44i48'
import ctypes
planes_t = ctypes.c_void_p * 5
def image_from_ndarray(array, format, size = None):
"""
Creates an Image from a numpy ndarray object. The format
may be 'RGB' or 'RGBA'. If a size is specified, the array
will be implicitly reshaped to that size, otherwise the size
is inferred from the first two dimensions of the array.
"""
if array.itemsize <> 1:
raise ValueError("Color component size must be 1 byte")
if size is not None:
width, height = size
data_size = array.size
pixel_size = data_size // (width * height)
if pixel_size <> len(format):
raise ValueError("Array has wrong shape for specified size and format")
else:
height, width, pixel_size = array.shape
if pixel_size <> len(format):
raise ValueError("Array has wrong shape for specified format")
bps = 8
spp = pixel_size
alpha = format.endswith("A")
csp = NSCalibratedRGBColorSpace
bpp = bps * spp
bpr = width * pixel_size
fmt = NSAlphaNonpremultipliedBitmapFormat
ns_rep = NSBitmapImageRep.alloc()
planes = planes_t(array.ctypes.data, 0, 0, 0, 0)
ns_rep.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_(
ctypes.addressof(planes), width, height, bps, spp, alpha, False, csp, fmt, bpr, bpp)
image = Image.__new__(Image)
image._init_from_ns_rep(ns_rep)
image._data = array
return image

78
GUI/Cocoa/PIL.py Normal file
View File

@ -0,0 +1,78 @@
#--------------------------------------------------------------
#
# PyGUI - PIL interface - Cocoa
#
#--------------------------------------------------------------
import ctypes
from AppKit import NSBitmapImageRep, \
NSAlphaNonpremultipliedBitmapFormat, NSFloatingPointSamplesBitmapFormat, \
NSDeviceCMYKColorSpace, NSCalibratedRGBColorSpace
from GUI import Image
def hack_objc_sig():
#print "GUI[Cocoa].PIL: Hacking objc method signature" ###
# HACK! PyObjC 2.3 incorrectly wraps the following method, so we change the
# signature and pass the bitmap data in using ctypes.
NSBitmapImageRep.__dict__['initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_'].signature = '@52@0:4^v8i12i16i20i24c28c32@36I40i44i48'
planes_t = ctypes.c_char_p * 5
debug_pil = False
def image_from_pil_image(pil_image):
"""Creates an Image from a Python Imaging Library (PIL)
Image object."""
mode = pil_image.mode
w, h = pil_image.size
data = pil_image.tostring()
alpha = False
cmyk = False
floating = False
if mode == "1":
bps = 1; spp = 1
elif mode == "L":
bps = 8; spp = 1
elif mode == "RGB":
bps = 8; spp = 3
elif mode == "RGBA":
bps = 8; spp = 4; alpha = True
elif mode == "CMYK":
bps = 8; spp = 4; cmyk = True
elif mode == "I":
bps = 32; spp = 1
elif mode == "F":
bps = 32; spp = 1; floating = True
else:
raise ValueError("Unsupported PIL image mode '%s'" % mode)
if cmyk:
csp = NSDeviceCMYKColorSpace
else:
csp = NSCalibratedRGBColorSpace
fmt = NSAlphaNonpremultipliedBitmapFormat
if floating:
fmt |= NSFloatingPointSamplesBitmapFormat
bpp = bps * spp
bpr = w * ((bpp + 7) // 8)
if debug_pil:
print "GUI.PIL:"
print "image size =", (w, h)
print "data size =", len(data)
print "bits per sample =", bps
print "samples per pixel =", spp
print "bits per pixel =", bpp
print "bytes per row =", bpr
hack_objc_sig()
ns_rep = NSBitmapImageRep.alloc()
planes = planes_t(data, "", "", "", "")
ns_rep.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_(
ctypes.addressof(planes), w, h, bps, spp, alpha, False, csp, fmt, bpr, bpp)
# planes = (data, "", "", "", "")
# ns_rep.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
# planes, w, h, bps, spp, alpha, False, csp, bpr, bpp)
image = Image.__new__(Image)
image._init_from_ns_rep(ns_rep)
image._data = data
return image

80
GUI/Cocoa/Pixmap.py Normal file
View File

@ -0,0 +1,80 @@
#
# Python GUI - Pixmaps - Cocoa
#
from Foundation import NSSize
from AppKit import NSImage, NSCachedImageRep, NSBitmapImageRep, \
NSCalibratedRGBColorSpace, NSImageCacheNever, NSGraphicsContext, \
NSAffineTransform
from GUI import export
from GUI import Canvas
from GUI.GPixmaps import Pixmap as GPixmap
class Pixmap(GPixmap):
# _ns_bitmap_image_rep NSBitmapImageRep
def __init__(self, width, height):
GPixmap.__init__(self)
#ns_size = NSSize(width, height)
#ns_image = NSImage.alloc().initWithSize_(ns_size)
ns_image = NSImage.alloc().init()
ns_image.setCacheMode_(NSImageCacheNever)
row_bytes = 4 * width
ns_bitmap = NSBitmapImageRep.alloc().\
initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
None, width, height, 8, 4, True, False, NSCalibratedRGBColorSpace, row_bytes, 32)
ns_image.addRepresentation_(ns_bitmap)
ns_bitmap_context = NSGraphicsContext.graphicsContextWithBitmapImageRep_(ns_bitmap)
ns_graphics_context = FlippedNSGraphicsContext.alloc().initWithBase_(ns_bitmap_context)
ns_tr = NSAffineTransform.transform()
ns_tr.translateXBy_yBy_(0.0, height)
ns_tr.scaleXBy_yBy_(1.0, -1.0)
# Using __class__ to get +saveGraphicsState instead of -saveGraphicsState
NSGraphicsContext.__class__.saveGraphicsState()
try:
NSGraphicsContext.setCurrentContext_(ns_graphics_context)
ns_tr.concat()
finally:
NSGraphicsContext.__class__.restoreGraphicsState()
self._init_with_ns_image(ns_image, flipped = True) #False)
self._ns_bitmap_image_rep = ns_bitmap
self._ns_graphics_context = ns_graphics_context
def with_canvas(self, proc):
NSGraphicsContext.__class__.saveGraphicsState()
NSGraphicsContext.setCurrentContext_(self._ns_graphics_context)
try:
canvas = Canvas()
proc(canvas)
finally:
NSGraphicsContext.__class__.restoreGraphicsState()
class FlippedNSGraphicsContext(NSGraphicsContext):
def initWithBase_(self, base):
self.base = base
self.graphics_port = base.graphicsPort()
return self
def isFlipped(self):
return True
def graphicsPort(self):
return self.graphics_port
def isDrawingToScreen(self):
return self.base.isDrawingToScreen()
def setCompositingOperation_(self, x):
self.base.setCompositingOperation_(x)
def focusStack(self):
return self.base.focusStack()
def saveGraphicsState(self):
return self.base.saveGraphicsState()
def restoreGraphicsState(self):
return self.base.restoreGraphicsState()
export(Pixmap)

132
GUI/Cocoa/Printing.py Normal file
View File

@ -0,0 +1,132 @@
#------------------------------------------------------------------------------
#
# PyGUI - Printing - Cocoa
#
#------------------------------------------------------------------------------
from AppKit import NSPrintInfo, NSPageLayout, NSPrintOperation, \
NSKeyedArchiver, NSKeyedUnarchiver, NSData, NSAutoPagination, \
NSPortraitOrientation, NSLandscapeOrientation, NSOKButton
from GUI.GPrinting import PageSetup as GPageSetup, Printable as GPrintable
ns_to_generic_orientation = {
NSPortraitOrientation: 'portrait',
NSLandscapeOrientation: 'landscape',
}
generic_to_ns_orientation = {
'portrait': NSPortraitOrientation,
'landscape': NSLandscapeOrientation,
}
#------------------------------------------------------------------------------
class PageSetup(GPageSetup):
def __init__(self):
ns_pi = NSPrintInfo.sharedPrintInfo().copy()
ns_pi.setLeftMargin_(36)
ns_pi.setTopMargin_(36)
ns_pi.setRightMargin_(36)
ns_pi.setBottomMargin_(36)
ns_pi.setHorizontalPagination_(NSAutoPagination)
self._ns_print_info = ns_pi
def __getstate__(self):
state = GPageSetup.__getstate__(self)
data = NSKeyedArchiver.archivedDataWithRootObject_(self._ns_print_info)
state['_ns_print_info'] = data.bytes()
return state
def __setstate__(self, state):
bytes = state.pop('_ns_print_info', None)
if bytes:
data = NSData.dataWithBytes_length_(bytes, len(bytes))
self._ns_print_info = NSKeyedArchiver.unarchiveObjectWithData_(data)
else:
GPageSetup.__setstate__(self, state)
def copy(self, other):
result = PageSetup.__new__()
result._ns_print_info = other._ns_print_info.copy()
def get_paper_name(self):
return self._ns_print_info.paperName()
def set_paper_name(self, x):
self._ns_print_info.setPaperName_(x)
def get_paper_size(self):
return tuple(self._ns_print_info.paperSize())
def set_paper_size(self, x):
self._ns_print_info.setPaperSize_(x)
def get_paper_width(self):
return self.paper_size[0]
def set_paper_width(self, x):
self.paper_size = x, self.paper_height
def get_paper_height(self):
return self.paper_size[1]
def set_paper_height(self, x):
self.paper_size = self.paper_width, x
def get_left_margin(self):
return self._ns_print_info.leftMargin()
def set_get_left_margin(self, x):
self._ns_print_info.setLefMargin_(x)
def get_right_margin(self):
return self._ns_print_info.rightMargin()
def set_get_right_margin(self, x):
self._ns_print_info.setRightMargin_(x)
def get_top_margin(self):
return self._ns_print_info.topMargin()
def set_get_top_margin(self, x):
self._ns_print_info.setTopMargin_(x)
def get_bottom_margin(self):
return self._ns_print_info.bottomMargin()
def set_get_bottom_margin(self, x):
self._ns_print_info.setBottomMargin_(x)
def get_orientation(self):
return ns_to_generic_orientation[self._ns_print_info.orientation()]
def set_orientation(self, x):
nso = generic_to_ns_orientation.get(x, 'portrait')
self._ns_print_info.setOrientation_(nso)
def get_printable_rect(self):
l, b, w, h = self._ns_print_info.imageablePageBounds()
return (l, b - h, l + w, b)
def get_printer_name(self):
return self._ns_print_info.printer().name()
def set_printer_name(self, x):
self._ns_print_info.setPrinter_(NSPrinter.printerWithName_(x))
#------------------------------------------------------------------------------
class Printable(GPrintable):
def print_view(self, page_setup, prompt = True):
ns_op = NSPrintOperation.printOperationWithView_printInfo_(
self._ns_inner_view, page_setup._ns_print_info)
ns_op.setShowsPrintPanel_(prompt)
ns_op.runOperation()
#------------------------------------------------------------------------------
def present_page_setup_dialog(page_setup):
result = NSPageLayout.pageLayout().runModalWithPrintInfo_(page_setup._ns_print_info)
return result == NSOKButton

36
GUI/Cocoa/RadioButton.py Normal file
View File

@ -0,0 +1,36 @@
#
# Python GUI - Radio buttons - PyObjC
#
import AppKit
from AppKit import NSOnState, NSOffState
from GUI import export
from GUI.StdFonts import system_font
from GUI.ButtonBasedControls import ButtonBasedControl
from GUI.GRadioButtons import RadioButton as GRadioButton
class RadioButton(ButtonBasedControl, GRadioButton):
def __init__(self, title = "New Radio Button", font = system_font, **kwds):
ns_button = self._create_ns_button(title = title, font = font,
ns_button_type = AppKit.NSRadioButton,
ns_bezel_style = AppKit.NSRoundedBezelStyle)
GRadioButton.__init__(self, _ns_view = ns_button, **kwds)
def do_action(self):
if self._group:
self._group.value = self._value
else:
self._ns_view.setState_(NSOffState)
def _value_changed(self):
self._update()
def _update(self):
if self._group and self._value == self._group._value:
state = NSOnState
else:
state = NSOffState
self._ns_view.setState_(state)
export(RadioButton)

23
GUI/Cocoa/RadioGroup.py Normal file
View File

@ -0,0 +1,23 @@
#
# Python GUI - Radio groups - PyObjC
#
from GUI import export
from GUI.GRadioGroups import RadioGroup as GRadioGroup
class RadioGroup(GRadioGroup):
def __init__(self, items = [], **kwds):
GRadioGroup.__init__(self, items, **kwds)
def _item_added(self, item):
item._update()
def _item_removed(self, item):
pass
def _value_changed(self):
for item in self._items:
item._update()
export(RadioGroup)

View File

@ -0,0 +1,96 @@
#
# Python GUI - Scrollable Views - PyObjC
#
from Foundation import NSPoint, NSMakeRect
from AppKit import NSScrollView
from GUI import export
from GUI.GScrollableViews import ScrollableView as GScrollableView, \
default_extent, default_line_scroll_amount, default_scrolling
from GUI.Geometry import ns_rect_to_rect
class ScrollableView(GScrollableView):
_ns_scrollable = True
def __init__(self, extent = default_extent,
line_scroll_amount = default_line_scroll_amount,
scrolling = default_scrolling,
**kwds):
GScrollableView.__init__(self,
extent = extent, line_scroll_amount = line_scroll_amount,
scrolling = scrolling, **kwds)
def get_hscrolling(self):
return self._ns_view.hasHorizontalScroller()
def set_hscrolling(self, value):
self._ns_view.setHasHorizontalScroller_(value)
def get_vscrolling(self):
return self._ns_view.hasVerticalScroller()
def set_vscrolling(self, value):
self._ns_view.setHasVerticalScroller_(value)
# def get_extent(self):
# (l, t), (w, h) = self._ns_inner_view.bounds()
# return (l, t, l + w, t + h)
def get_extent(self):
return self._ns_inner_view.bounds().size
# def set_extent(self, (l, t, r, b)):
# w = r - l
# h = b - t
# ns_docview = self._ns_inner_view
# ns_docview.setFrame_(NSMakeRect(0, 0, w, h))
# ns_docview.setBounds_(NSMakeRect(l, t, w, h))
# self.invalidate()
def set_extent(self, (w, h)):
r = NSMakeRect(0, 0, w, h)
ns_docview = self._ns_inner_view
ns_docview.setFrame_(r)
ns_docview.setBounds_(r)
self.invalidate()
def get_content_size(self):
return self._ns_view.contentSize()
def set_content_size(self, size):
ns = self._ns_view
self.size = NSScrollView.\
frameSizeForContentSize_hasHorizontalScroller_hasVerticalScroller_borderType_(
size, ns.hasHorizontalScroller(), ns.hasVerticalScroller(), ns.borderType())
def get_scroll_offset(self):
ns_clip_view = self._ns_view.contentView()
x, y = ns_clip_view.bounds().origin
return x, y
def set_scroll_offset(self, (x, y)):
ns_view = self._ns_view
ns_clip_view = ns_view.contentView()
new_pt = ns_clip_view.constrainScrollPoint_(NSPoint(x, y))
ns_clip_view.scrollToPoint_(new_pt)
ns_view.reflectScrolledClipView_(ns_clip_view)
def get_line_scroll_amount(self):
ns_view = self._ns_view
x = ns_view.horizontalLineScroll()
y = ns_view.verticalLineScroll()
return x, y
def set_line_scroll_amount(self, (x, y)):
ns_view = self._ns_view
ns_view.setHorizontalLineScroll_(x)
ns_view.setVerticalLineScroll_(y)
ns_view.setHorizontalPageScroll_(x)
ns_view.setVerticalPageScroll_(y)
def viewed_rect(self):
ns_rect = self._ns_view.contentView().documentVisibleRect()
return ns_rect_to_rect(ns_rect)
export(ScrollableView)

85
GUI/Cocoa/Slider.py Normal file
View File

@ -0,0 +1,85 @@
#------------------------------------------------------------------------------
#
# Python GUI - Slider - Cocoa
#
#------------------------------------------------------------------------------
from AppKit import NSSlider
from GUI import export
from GUI.StdFonts import system_font
from GUI.Utils import NSMultiClass, PyGUI_NS_EventHandler, \
ns_set_action, ns_size_to_fit
from GUI.GSliders import Slider as GSlider
class Slider(GSlider):
_ns_handle_mouse = True
def __init__(self, orient = 'h', ticks = 0, **kwds):
length = 100
if ticks:
breadth = 30
else:
breadth = 22 # Same as default height of a text-containing control
if orient == 'h':
ns_frame = ((0, 0), (length, breadth))
elif orient == 'v':
ns_frame = ((0, 0), (breadth, length))
else:
raise ValueError("Invalid orientation, should be 'h' or 'v'")
ns_slider = PyGUI_NSSlider.alloc().initWithFrame_(ns_frame)
ns_slider.pygui_component = self
ns_set_action(ns_slider, 'doAction:')
GSlider.__init__(self, _ns_view = ns_slider, **kwds)
self.set_ticks(ticks)
self._last_value = None
def get_min_value(self):
return self._ns_view.minValue()
def set_min_value(self, x):
self._ns_view.setMinValue_(x)
def get_max_value(self):
return self._ns_view.maxValue()
def set_max_value(self, x):
self._ns_view.setMaxValue_(x)
def get_value(self):
return self._ns_view.doubleValue()
def set_value(self, x):
self._ns_view.setDoubleValue_(x)
def get_ticks(self):
return self._ns_view.numberOfTickMarks()
def set_ticks(self, x):
self._ns_view.setNumberOfTickMarks_(x)
def get_discrete(self):
return self._ns_view.allowsTickMarkValuesOnly()
def set_discrete(self, x):
self._ns_view.setAllowsTickMarkValuesOnly_(x)
def get_live(self):
return self._ns_view.isContinuous()
def set_live(self, x):
self._ns_view.setContinuous_(x)
def do_action(self):
value = self._ns_view.doubleValue()
if value <> self._last_value:
self._last_value = value
GSlider.do_action(self)
#------------------------------------------------------------------------------
class PyGUI_NSSlider(NSSlider, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
export(Slider)

56
GUI/Cocoa/StdCursors.py Normal file
View File

@ -0,0 +1,56 @@
#
# Python GUI - Standard Cursors - Cocoa
#
from AppKit import NSCursor
from GUI import Cursor
__all__ = [
'arrow',
'ibeam',
'crosshair',
'fist',
'hand',
'finger',
'invisible',
]
_empty_cursor = None
def _make_empty_cursor():
global _empty_cursor
if not _empty_cursor:
from AppKit import NSCursor, NSImage, NSBitmapImageRep, NSDeviceRGBColorSpace
from GUI import Cursor
import sys
if sys.version_info >= (3, 0):
b = bytes([0])
else:
b = "\x00"
d = b * 1024
ns_bitmap = NSBitmapImageRep.alloc().\
initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_\
((d, d, d, d, d), 16, 16, 8, 4, True, False, NSDeviceRGBColorSpace, 64, 32)
ns_image = NSImage.alloc().initWithSize_((16, 16))
ns_image.addRepresentation_(ns_bitmap)
ns_cursor = NSCursor.alloc().initWithImage_hotSpot_(ns_image, (0, 0))
_empty_cursor = Cursor._from_ns_cursor(ns_cursor)
_empty_cursor._data = d
return _empty_cursor
arrow = Cursor._from_ns_cursor(NSCursor.arrowCursor())
ibeam = Cursor._from_ns_cursor(NSCursor.IBeamCursor())
crosshair = Cursor._from_ns_cursor(NSCursor.crosshairCursor())
fist = Cursor._from_ns_cursor(NSCursor.closedHandCursor())
hand = Cursor._from_ns_cursor(NSCursor.openHandCursor())
finger = Cursor._from_ns_cursor(NSCursor.pointingHandCursor())
invisible = _make_empty_cursor()
mac_poof = Cursor._from_ns_cursor(NSCursor.disappearingItemCursor())
del NSCursor
del Cursor
del _make_empty_cursor
def empty_cursor():
return invisible

9
GUI/Cocoa/StdFonts.py Normal file
View File

@ -0,0 +1,9 @@
#
# Python GUI - Standard Fonts - PyObjC
#
from AppKit import NSFont
from GUI import Font
system_font = Font._from_ns_font(NSFont.systemFontOfSize_(0))
application_font = Font._from_ns_font(NSFont.userFontOfSize_(0))

62
GUI/Cocoa/StdMenus.py Normal file
View File

@ -0,0 +1,62 @@
#
# Python GUI - Standard Menus - PyObjC
#
from GUI.GStdMenus import build_menus, \
fundamental_cmds, help_cmds, pref_cmds, file_cmds, print_cmds, edit_cmds
fundamental_cmds += ['hide_app_cmd', 'hide_other_apps_cmd', 'show_all_apps_cmd']
_appl_menu_items = [
("About <app>", 'about_cmd'),
"-",
("Preferences...", 'preferences_cmd'),
"-",
("Hide <app>/H", 'hide_app_cmd'),
("Hide Others", 'hide_other_apps_cmd'),
("Show All", 'show_all_apps_cmd'),
"-",
("Quit <app>/Q", 'quit_cmd'),
]
_file_menu_items = [
("New/N", 'new_cmd'),
("Open.../O", 'open_cmd'),
("Close/W", 'close_cmd'),
"-",
("Save/S", 'save_cmd'),
("Save As...", 'save_as_cmd'),
("Revert", 'revert_cmd'),
"-",
("Page Setup...", 'page_setup_cmd'),
("Print.../P", 'print_cmd'),
]
_edit_menu_items = [
("Undo/Z", 'undo_cmd'),
("Redo/^Z", 'redo_cmd'),
"-",
("Cut/X", 'cut_cmd'),
("Copy/C", 'copy_cmd'),
("Paste/V", 'paste_cmd'),
("Delete", 'clear_cmd'),
"-",
("Select All/A", 'select_all_cmd'),
]
_help_menu_items = [
("Help", 'help_cmd'),
]
#------------------------------------------------------------------------------
def basic_menus(substitutions = {}, include = None, exclude = None):
return build_menus([
("@", _appl_menu_items, False),
("File", _file_menu_items, False),
("Edit", _edit_menu_items, False),
("Help", _help_menu_items, True),
],
substitutions = substitutions,
include = include,
exclude = exclude)

89
GUI/Cocoa/Task.py Normal file
View File

@ -0,0 +1,89 @@
#
# PyGUI - Tasks - Cocoa
#
import sys
from weakref import WeakValueDictionary
from Foundation import NSTimer, NSRunLoop, NSDefaultRunLoopMode
from AppKit import NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode
from GUI import export
from GUI import Globals
from GUI.GTasks import Task as GTask
#----------------------------------------------------------------------
#
# Doing things this convoluted way to work around a memory
# leak in PyObjC. Need to avoid having the NSTimer trigger
# creation of a bound method each time it fires or the bound
# methods leak. Also can't use the userInfo of the NSTimer as
# it seems to leak too.
ns_timer_to_task = WeakValueDictionary()
class TaskTrigger(object):
pass
def fire_(ns_timer):
ns_timer_to_task[ns_timer]._ns_fire()
trigger = TaskTrigger()
trigger.fire_ = fire_
#----------------------------------------------------------------------
class Task(GTask):
def __init__(self, proc, interval, repeat = 0, start = 1):
self._proc = proc
self._interval = interval
self._repeat = repeat
self._ns_timer = None
if start:
self.start()
def destroy(self):
#print "Task.destroy:", self ###
self.stop()
def get_scheduled(self):
return self._ns_timer is not None
def get_interval(self):
return self._interval
def get_repeat(self):
return self._repeat
def start(self):
self.stop()
#ns_timer = \
# NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
# self._interval, self._target, '_ns_fire', None, self._repeat)
ns_timer = \
NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
self._interval, trigger, 'fire:', None, self._repeat)
self._ns_timer = ns_timer
ns_timer_to_task[ns_timer] = self
ns_run_loop = NSRunLoop.currentRunLoop()
ns_run_loop.addTimer_forMode_(
ns_timer, NSDefaultRunLoopMode)
ns_run_loop.addTimer_forMode_(
ns_timer, NSEventTrackingRunLoopMode)
ns_run_loop.addTimer_forMode_(
ns_timer, NSModalPanelRunLoopMode)
def stop(self):
ns_timer = self._ns_timer
if ns_timer:
ns_timer.invalidate()
del ns_timer_to_task[ns_timer]
self._ns_timer = None
def _ns_fire(self):
try:
self._proc()
except:
Globals.pending_exception = sys.exc_info()
self.stop()
export(Task)

104
GUI/Cocoa/TextEditor.py Normal file
View File

@ -0,0 +1,104 @@
#------------------------------------------------------------------------------
#
# Python GUI - Text Editor - Cocoa
#
#------------------------------------------------------------------------------
from AppKit import NSTextView, NSScrollView, NSViewWidthSizable, \
NSMutableParagraphStyle
from GUI import export
from GUI import StdFonts
from GUI.Utils import NSMultiClass, PyGUI_NS_EventHandler
from GUI.GTextEditors import TextEditor as GTextEditor
NUM_TAB_STOPS = 32
class TextEditor(GTextEditor):
_ns_handle_mouse = True
def __init__(self, scrolling = 'hv', **kwds):
width = 100
height = 100
frame = ((0, 0), (width, height))
ns_outer = NSScrollView.alloc().initWithFrame_(frame)
ns_outer.setHasHorizontalScroller_('h' in scrolling)
ns_outer.setHasVerticalScroller_('v' in scrolling)
if 'h' in scrolling:
cwidth = 2000
else:
cwidth = ns_outer.contentSize()[0]
frame = ((0, 0), (cwidth, height))
ns_inner = PyGUI_NSTextView.alloc().initWithFrame_(frame)
ns_inner.pygui_component = self
ps = NSMutableParagraphStyle.alloc().init()
ps.setDefaultTabInterval_(ps.tabStops()[0].location())
ps.setTabStops_([])
ns_inner.setDefaultParagraphStyle_(ps)
ns_inner.setAllowsUndo_(True)
ns_outer.setDocumentView_(ns_inner)
if 'h' not in scrolling:
ns_inner.setAutoresizingMask_(NSViewWidthSizable)
if 'font' not in kwds:
kwds['font'] = StdFonts.application_font
GTextEditor.__init__(self, ns_outer,
_ns_inner_view = ns_inner, **kwds)
def get_text(self):
return self._ns_inner_view.string()
def set_text(self, value):
self._ns_inner_view.setString_(value)
self._ns_apply_style()
def get_text_length(self):
return self._ns_inner_view.textStorage().length()
def get_selection(self):
start, length = self._ns_inner_view.selectedRanges()[0].rangeValue()
return (start, start + length)
def set_selection(self, value):
start, stop = value
self._ns_inner_view.setSelectedRange_((start, stop - start))
def get_font(self):
return self._font
def set_font(self, font):
self._font = font
self._ns_inner_view.setFont_(font._ns_font)
def get_tab_spacing(self):
#ns_storage = self._ns_inner_view.textStorage()
#ps, _ = ns_storage.attribute_atIndex_effectiveRange_("NSParagraphStyle", 0)
ps = self._ns_inner_view.defaultParagraphStyle()
return ps.defaultTabInterval()
def set_tab_spacing(self, x):
ps = NSMutableParagraphStyle.alloc().init()
ps.setTabStops_([])
ps.setDefaultTabInterval_(x)
self._ns_inner_view.setDefaultParagraphStyle_(ps)
self._ns_apply_style()
def paste_cmd(self):
GTextEditor.paste_cmd(self)
self._ns_apply_style()
def _ns_apply_style(self):
ns_textview = self._ns_inner_view
ps = ns_textview.defaultParagraphStyle()
font = ns_textview.font()
ns_storage = self._ns_inner_view.textStorage()
ns_storage.setAttributes_range_(
{"NSParagraphStyle": ps, "NSFont": font},
(0, self.text_length))
#------------------------------------------------------------------------------
class PyGUI_NSTextView(NSTextView, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
export(TextEditor)

49
GUI/Cocoa/TextField.py Normal file
View File

@ -0,0 +1,49 @@
#
# Python GUI - Text Fields - PyObjC
#
from Foundation import NSRange
from GUI import export
from GUI.StdFonts import system_font #application_font
from GUI import EditCmdHandler
from GUI.TextFieldBasedControls import TextFieldBasedControl
from GUI.GTextFields import TextField as GTextField
class TextField(TextFieldBasedControl, GTextField):
#_vertical_padding = 5
_intercept_tab_key = False
def __init__(self, text = "", font = system_font,
multiline = False, password = False, border = True, **kwds):
ns_textfield = self._create_ns_textfield(editable = True,
multiline = multiline, password = password,
text = text, font = font, border = border)
GTextField.__init__(self, _ns_view = ns_textfield,
multiline = multiline, **kwds)
def get_selection(self):
ns_editor = self._ns_editor()
if ns_editor:
start, length = ns_editor.selectedRange()
return (start, start + length)
else:
return (0, 0)
def set_selection(self, (start, end)):
self.become_target()
ns_editor = self._ns_editor()
if ns_editor:
ns_editor.setSelectedRange_(NSRange(start, end - start))
def select_all(self):
self.become_target()
self._ns_view.selectText_(None)
def _ns_editor(self):
return self._ns_view.currentEditor()
def _ns_edit_cmd_target(self):
return self._ns_editor()
export(TextField)

View File

@ -0,0 +1,90 @@
#
# Python GUI - PyObjC
#
# Base class for controls based on an NSTextField
#
from Foundation import NSRect, NSPoint, NSSize
from AppKit import NSTextField, NSSecureTextField, NSTextFieldCell
from GUI.Utils import NSMultiClass
from GUI import Color
from GUI.Utils import ns_size_to_fit, PyGUI_NS_EventHandler
from GUI.StdFonts import system_font
class TextFieldBasedControl(object):
_ns_handle_mouse = True
def _create_ns_textfield(self, editable, text, font,
multiline = False, password = False, border = False,
padding = (0, 0)):
self._ns_is_password = password
if password:
ns_class = PyGUI_NSSecureTextField
else:
ns_class = PyGUI_NSTextField
ns_frame = NSRect(NSPoint(0, 0), NSSize(20, 10))
ns_textfield = ns_class.alloc().initWithFrame_(ns_frame)
ns_textfield.pygui_component = self
if multiline and not password:
ns_textfield.pygui_multiline = True
# Be careful here -- calling setBordered_ seems to affect isBezeled as well
if editable:
ns_textfield.setBezeled_(border)
else:
ns_textfield.setBordered_(border)
if not editable:
ns_textfield.setDrawsBackground_(False)
ns_textfield.setEditable_(editable)
ns_textfield.setSelectable_(editable)
ns_textfield.setFont_(font._ns_font)
ns_textfield.setStringValue_(text)
ns_size_to_fit(ns_textfield, padding = padding)
return ns_textfield
def get_border(self):
ns_textfield = self._ns_inner_view
if ns_textfield.isEditable():
return ns_textfield.isBezeled()
else:
return ns_textfield.isBordered()
def set_border(self, border):
ns_textfield = self._ns_inner_view
if ns_textfield.isEditable():
ns_textfield.setBezeled_(border)
else:
ns_textfield.setBordered_(border)
def get_text(self):
return self._ns_inner_view.stringValue()
def set_text(self, v):
self._ns_inner_view.setStringValue_(v)
def get_color(self):
return Color._from_ns_color(self._ns_inner_view.textColor())
def set_color(self, v):
self._ns_inner_view.setTextColor_(v._ns_color)
def _get_vertical_padding(self):
if self.border:
return 5
else:
return 0
_vertical_padding = property(_get_vertical_padding)
#------------------------------------------------------------------------------
class PyGUI_NSTextField(NSTextField): #, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
pygui_multiline = False
class PyGUI_NSSecureTextField(NSSecureTextField): #, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
pygui_multiline = False

198
GUI/Cocoa/Utils.py Normal file
View File

@ -0,0 +1,198 @@
#------------------------------------------------------------------------------
#
# Python GUI - Utilities - PyObjC
#
#------------------------------------------------------------------------------
from math import ceil
from inspect import getmro
from Foundation import NSObject
from AppKit import NSView
from GUI import Event, Globals
def NSMultiClass(name, bases, dic):
# Workaround for PyObjC classes not supporting
# multiple inheritance properly. Note: MRO is
# right to left across the bases.
main = bases[0]
slots = list(dic.get('__slots__', ()))
dic2 = {}
for mix in bases[1:]:
for cls in getmro(mix)[::-1]:
slots2 = cls.__dict__.get('__slots__')
if slots2:
for slot in slots2:
if slot not in slots:
slots.append(slot)
dic2.update(cls.__dict__)
dic2.update(dic)
if slots:
dic2['__slots__'] = slots
cls = type(main)(name, (main,), dic2)
return cls
#------------------------------------------------------------------------------
class PyGUI_Flipped_NSView(NSView):
# An NSView with a flipped coordinate system.
def isFlipped(self):
return True
#------------------------------------------------------------------------------
class PyGUI_NSActionTarget(NSObject):
# A shared instance of this class is used as the target of
# all action messages from the NSViews of Components. It
# performs the action by calling the similarly-named method of
# the corresponding Component.
def doAction_(self, ns_sender):
self.call_method('do_action', ns_sender)
def call_method(self, method_name, ns_sender):
component = Globals._ns_view_to_component.get(ns_sender)
if component:
getattr(component, method_name)()
_ns_action_target = PyGUI_NSActionTarget.alloc().init()
def ns_set_action(ns_control, method_name):
# Arrange for the 'action' message of the NSControl to
# invoke the indicated method of its associated Component.
ns_control.setAction_(method_name)
ns_control.setTarget_(_ns_action_target)
#------------------------------------------------------------------------------
class PyGUI_NS_EventHandler:
# Methods to be mixed in with NSView subclasses that are
# to relay mouse and keyboard events to a Component.
#
# pygui_component Component
def mouseDown_(self, ns_event):
self._ns_mouse_event(ns_event)
def mouseUp_(self, ns_event):
self._ns_mouse_event(ns_event)
def mouseDragged_(self, ns_event):
self._ns_mouse_event(ns_event)
def rightMouseDown_(self, ns_event):
self._ns_mouse_event(ns_event)
def rightMouseUp_(self, ns_event):
self._ns_mouse_event(ns_event)
def rightMouseDragged_(self, ns_event):
self._ns_mouse_event(ns_event)
def otherMouseDown_(self, ns_event):
self._ns_mouse_event(ns_event)
def otherMouseUp_(self, ns_event):
self._ns_mouse_event(ns_event)
def otherMouseDragged_(self, ns_event):
self._ns_mouse_event(ns_event)
def mouseMoved_(self, ns_event):
self._ns_mouse_event(ns_event)
def mouseEntered_(self, ns_event):
self._ns_mouse_event(ns_event)
def mouseExited_(self, ns_event):
self._ns_mouse_event(ns_event)
def keyDown_(self, ns_event):
#print "PyGUI_NS_EventHandler.keyDown_:", repr(ns_event.characters()), \
# "for", object.__repr__(self) ###
self._ns_other_event(ns_event)
def keyUp_(self, ns_event):
#print "PyGUI_NS_EventHandler.keyUp_ for", self ###
self._ns_other_event(ns_event)
def _ns_mouse_event(self, ns_event):
#print "PyGUI_NS_EventHandler._ns_mouse_event" ###
event = self._ns_mouse_event_to_event(ns_event)
#print "...sending to", self.pygui_component ###
component = self.pygui_component
if component:
component.handle_event_here(event)
def _ns_mouse_event_to_event(self, ns_event):
event = Event(ns_event)
event.position = tuple(self._ns_event_position(ns_event))
return event
def _ns_event_position(self, ns_event):
#print "PyGUI_NS_EventHandler._ns_event_position:", self ###
#print "...mro =", self.__class__.__mro__ ###
ns_win_pos = ns_event.locationInWindow()
return self.convertPoint_fromView_(ns_win_pos, None)
def _ns_other_event(self, ns_event):
#print "PyGUI_NS_EventHandler._ns_other_event for", self ###
event = Event(ns_event)
component = self.pygui_component
if component:
#print "...passing", event.kind, "to", component ###
component.handle_event(event)
def acceptsFirstResponder(self):
###return True
return self.pygui_component._ns_accept_first_responder
# def canBecomeKeyView(self):
# return self.pygui_component._tab_stop
#------------------------------------------------------------------------------
class PyGUI_NS_ViewBase(PyGUI_NS_EventHandler):
# Methods to be mixed in with PyGUI_NSView classes.
#
# pygui_component ViewBase
__slots__ = ['tracking_rect']
# tracking_rect = None
def becomeFirstResponder(self):
self.pygui_component.targeted()
return True
def resignFirstResponder(self):
self.pygui_component.untargeted()
return True
def resetCursorRects(self):
#print "PyGUI_NS_ViewBase: resetCursorRects" ###
self.removeCursorRects()
self.tracking_rect = self.addTrackingRect_owner_userData_assumeInside_(
self.visibleRect(), self, 0, False)
self.pygui_component._ns_reset_cursor_rects()
def removeCursorRects(self):
#print "PyGUI_NS_ViewBase: removeCursorRects" ###
tag = getattr(self, 'tracking_rect', None)
if tag:
self.removeTrackingRect_(tag)
self.tracking_rect = None
#------------------------------------------------------------------------------
def ns_size_to_fit(ns_control, padding = (0, 0), height = None):
# Set size of control to fit its contents, plus the given padding.
# Height may be overridden, because some controls don't seem to
# calculate it properly.
# Auto sizing can result in fractional sizes, which seems to cause
# problems when NS autoresizing occurs later. So we round the size up
# to whole numbers of pixels.
ns_control.sizeToFit()
w, h = ns_control.frame().size
pw, ph = padding
ns_control.setFrameSize_((ceil(w + pw), ceil((height or h) + ph)))

12
GUI/Cocoa/View.py Normal file
View File

@ -0,0 +1,12 @@
#
# Python GUI - Views - PyObjC
#
from GUI import export
from GUI.GViews import View as GView
class View(GView):
_ns_scrollable = False
export(View)

61
GUI/Cocoa/ViewBase.py Normal file
View File

@ -0,0 +1,61 @@
#
# Python GUI - View Base - PyObjC
#
import Foundation
import AppKit
from GUI import Globals, export
from GUI.Properties import overridable_property
from GUI import Event
from GUI.Utils import PyGUI_NS_EventHandler
from GUI.GViewBases import ViewBase as GViewBase
ns_tracking_mask = (
AppKit.NSLeftMouseDraggedMask |
AppKit.NSRightMouseDraggedMask |
AppKit.NSOtherMouseDraggedMask |
AppKit.NSLeftMouseUpMask |
AppKit.NSRightMouseUpMask |
AppKit.NSOtherMouseUpMask)
# Need to use NSDefaultRunLoopMode here otherwise timers don't fire.
ns_tracking_mode = Foundation.NSDefaultRunLoopMode # AppKit.NSEventTrackingRunLoopMode
ns_distant_future = Foundation.NSDate.distantFuture()
class ViewBase(GViewBase):
def _change_container(self, new_container):
self._ns_inner_view.removeCursorRects()
super(ViewBase, self)._change_container(new_container)
def _ns_track_mouse(self, ns_view):
ns_app = Globals.ns_application
tracking = True
while tracking:
ns_event = ns_app.nextEventMatchingMask_untilDate_inMode_dequeue_(
ns_tracking_mask, ns_distant_future, ns_tracking_mode, True)
event = ns_view._ns_mouse_event_to_event(ns_event)
yield event
tracking = event.kind <> 'mouse_up'
def _cursor_changed(self):
#print "ViewBase._cursor_changed:", self ###
ns_view = self._ns_view
ns_window = ns_view.window()
if ns_window:
# invalidateCursorRectsForView_ doesn't seem to trigger
# resetCursorRects on the view.
#ns_window.invalidateCursorRectsForView_(ns_view)
ns_window.resetCursorRects()
def _ns_reset_cursor_rects(self):
#print "ViewBase._ns_reset_cursor_rects:", self ###
cursor = self._cursor
if cursor:
ns_view = self._ns_inner_view
ns_rect = ns_view.visibleRect()
ns_view.addCursorRect_cursor_(ns_rect, cursor._ns_cursor)
export(ViewBase)

292
GUI/Cocoa/Window.py Normal file
View File

@ -0,0 +1,292 @@
#------------------------------------------------------------------------------
#
# Python GUI - Windows - PyObjC version
#
#------------------------------------------------------------------------------
from Foundation import NSRect, NSPoint, NSSize, NSObject
import AppKit
from AppKit import NSWindow, NSScreen, NSTextView, NSMenu
from GUI import export
from GUI import Globals
from GUI.Utils import NSMultiClass, PyGUI_NS_EventHandler, PyGUI_Flipped_NSView
from GUI import application
from GUI import Event
from GUI.GWindows import Window as GWindow
_default_options_for_style = {
'standard':
{'movable': 1, 'closable': 1, 'hidable': 1, 'resizable': 1},
'nonmodal_dialog':
{'movable': 1, 'closable': 0, 'hidable': 1, 'resizable': 0},
'modal_dialog':
{'movable': 1, 'closable': 0, 'hidable': 0, 'resizable': 0},
'alert':
{'movable': 1, 'closable': 0, 'hidable': 0, 'resizable': 0},
'fullscreen':
{'movable': 0, 'closable': 0, 'hidable': 0, 'resizable': 0},
#{'movable': 1, 'closable': 1, 'hidable': 1, 'resizable': 1},
}
#------------------------------------------------------------------------------
class Window(GWindow):
# _ns_window PyGUI_NSWindow
# _ns_style_mask int
def __init__(self, style = 'standard', zoomable = None, **kwds):
# We ignore zoomable, since it's the same as resizable.
self._style = style
options = dict(_default_options_for_style[style])
for option in ['movable', 'closable', 'hidable', 'resizable']:
if option in kwds:
options[option] = kwds.pop(option)
self._ns_style_mask = self._ns_window_style_mask(**options)
if style == 'fullscreen':
ns_rect = NSScreen.mainScreen().frame()
else:
ns_rect = NSRect(NSPoint(0, 0), NSSize(self._default_width, self._default_height))
ns_window = PyGUI_NSWindow.alloc()
ns_window.initWithContentRect_styleMask_backing_defer_(
ns_rect, self._ns_style_mask, AppKit.NSBackingStoreBuffered, True)
ns_content = PyGUI_NS_ContentView.alloc()
ns_content.initWithFrame_(NSRect(NSPoint(0, 0), NSSize(0, 0)))
ns_content.pygui_component = self
ns_window.setContentView_(ns_content)
ns_window.setAcceptsMouseMovedEvents_(True)
ns_window.setDelegate_(ns_window)
ns_window.pygui_component = self
self._ns_window = ns_window
GWindow.__init__(self, style = style, closable = options['closable'],
_ns_view = ns_window.contentView(), _ns_responder = ns_window,
_ns_set_autoresizing_mask = False,
**kwds)
def _ns_window_style_mask(self, movable, closable, hidable, resizable):
if movable or closable or hidable or resizable:
mask = AppKit.NSTitledWindowMask
if closable:
mask |= AppKit.NSClosableWindowMask
if hidable:
mask |= AppKit.NSMiniaturizableWindowMask
if resizable:
mask |= AppKit.NSResizableWindowMask
else:
mask = AppKit.NSBorderlessWindowMask
return mask
def destroy(self):
#print "Window.destroy:", self ###
self.hide()
app = application()
if app._ns_key_window is self:
app._ns_key_window = None
GWindow.destroy(self)
# We can't drop all references to the NSWindow yet, because this method
# can be called from its windowShouldClose: method, and allowing an
# NSWindow to be released while executing one of its own methods seems
# to be a very bad idea (Cocoa hangs). So we hide the NSWindow and store
# a reference to it in a global. It will be released the next time a
# window is closed and the global is re-used.
global _ns_zombie_window
_ns_zombie_window = self._ns_window
self._ns_window.pygui_component = None
#self._ns_window = None
def get_bounds(self):
ns_window = self._ns_window
ns_frame = ns_window.frame()
(l, y), (w, h) = ns_window.contentRectForFrameRect_styleMask_(
ns_frame, self._ns_style_mask)
b = Globals.ns_screen_height - y
result = (l, b - h, l + w, b)
return result
def set_bounds(self, (l, t, r, b)):
y = Globals.ns_screen_height - b
ns_rect = NSRect(NSPoint(l, y), NSSize(r - l, b - t))
ns_window = self._ns_window
ns_frame = ns_window.frameRectForContentRect_styleMask_(
ns_rect, self._ns_style_mask)
ns_window.setFrame_display_(ns_frame, False)
def get_title(self):
return self._ns_window.title()
def set_title(self, v):
self._ns_window.setTitle_(v)
def get_visible(self):
return self._ns_window.isVisible()
def set_visible(self, v):
# At some mysterious time between creating a window and showing
# it for the first time, Cocoa adjusts its position so that it
# doesn't extend above the menu bar. This is a nuisance for
# our fullscreen windows, so we need to readjust the position
# before showing.
if v:
if self._style == 'fullscreen':
self._ns_window.setFrameOrigin_(NSPoint(0, 0))
self._ns_window.orderFront_(None)
else:
self._ns_window.orderOut_(None)
def _show(self):
self.visible = True
self._ns_window.makeKeyWindow()
def get_target(self):
ns_window = self._ns_window
ns_view = ns_window.firstResponder()
while ns_view and ns_view is not ns_window:
component = Globals._ns_view_to_component.get(ns_view)
if component:
return component
ns_view = ns_view.superview()
return self
def center(self):
self._ns_window.center()
def _stagger(self):
key_win = application()._ns_key_window
if key_win:
(x, y), (w, h) = key_win._ns_window.frame()
p = self._ns_window.cascadeTopLeftFromPoint_(NSPoint(x, y + h))
self._ns_window.setFrameTopLeftPoint_(p)
else:
(x, y), (w, h) = NSScreen.mainScreen().visibleFrame()
ns_vis_topleft = NSPoint(x, y + h)
self._ns_window.setFrameTopLeftPoint_(ns_vis_topleft)
def _document_needs_saving(self, state):
self._ns_window.setDocumentEdited_(state)
#------------------------------------------------------------------------------
class PyGUI_NSWindow(NSWindow, PyGUI_NS_EventHandler):
# pygui_component Window
# resize_delta point or None
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component', 'resize_delta', 'pygui_field_editor']
# pygui_component = None
# resize_delta = None
# pygui_field_editor = None
def _ns_event_position(self, ns_event):
return ns_event.locationInWindow()
def windowShouldClose_(self, sender):
# We use this to detect when the Aqua window closing button
# is pressed, and do the closing ourselves.
self.pygui_component.close_cmd()
return False
# The NSWindow is made its own delegate.
def windowWillResize_toSize_(self, ns_win, new_ns_size):
w0, h0 = self.frame().size
w1, h1 = new_ns_size
self.resize_delta = (w1 - w0, h1 - h0)
return new_ns_size
def windowDidResize_(self, notification):
delta = getattr(self, 'resize_delta', None)
if delta:
self.pygui_component._resized(delta)
self.resize_delta = None
def windowDidBecomeKey_(self, notification):
app = application()
app._ns_key_window = self.pygui_component
app._update_menubar()
def windowDidResignKey_(self, notification):
app = application()
app._ns_key_window = None
app._update_menubar()
def windowWillReturnFieldEditor_toObject_(self, ns_window, ns_obj):
# Return special field editor for newline handling in text fields.
#print "Window: Field editor requested for", object.__repr__(ns_obj) ###
#editor = self.pygui_field_editor
#if not editor:
try:
editor = self.pygui_field_editor
except AttributeError:
#print "...creating new field editor" ###
editor = PyGUI_FieldEditor.alloc().initWithFrame_(
NSRect(NSPoint(0, 0), NSSize(0, 0)))
editor.setFieldEditor_(True)
editor.setDrawsBackground_(False)
self.pygui_field_editor = editor
return editor
# Need the following two methods so that a fullscreen window can become
# the main window. Otherwise it can't, because it has no title bar.
def canBecomeKeyWindow(self):
return self.isVisible()
def canBecomeMainWindow(self):
#print "PyGUI_NSWindow.canBecomeMainWindow"
return self.isVisible()
def windowDidBecomeMain_(self, notification):
#print "PyGUI_NSWindow.windowDidBecomeMain_:", self.pygui_component.title ###
comp = self.pygui_component
if comp and comp._style == 'fullscreen':
#print "...hiding menu bar" ###
NSMenu.setMenuBarVisible_(False)
#self.setFrameOrigin_(NSPoint(0, 0))
def windowDidResignMain_(self, notification):
#print "PyGUI_NSWindow.windowDidResignMain_:", self.pygui_component.title ###
comp = self.pygui_component
if comp and comp._style == 'fullscreen':
#print "...showing menu bar" ###
NSMenu.setMenuBarVisible_(True)
#------------------------------------------------------------------------------
class PyGUI_NS_ContentView(PyGUI_Flipped_NSView, PyGUI_NS_EventHandler):
# pygui_component Window
__metaclass__ = NSMultiClass
__slots__ = ['pygui_component']
def acceptsFirstResponder(self):
return False
#------------------------------------------------------------------------------
class PyGUI_FieldEditorBase(NSTextView):
# Special field editor for use by TextFields. Intercepts
# return key events and handles them our own way.
def keyDown_(self, ns_event):
#print "PyGUI_FieldEditorBase.keyDown_ for", self.pygui_component ###
if ns_event.characters() == "\r":
if self.pygui_component._multiline:
self.insertText_("\n")
return
NSTextView.keyDown_(self, ns_event)
#------------------------------------------------------------------------------
class PyGUI_FieldEditor(PyGUI_FieldEditorBase, PyGUI_NS_EventHandler):
__metaclass__ = NSMultiClass
def get_pygui_component(self):
pygui_nstextfield = self.superview().superview()
component = pygui_nstextfield.pygui_component
component._ns_responder = self
return component
pygui_component = property(get_pygui_component)
export(Window)

62
GUI/Generic/Actions.py Normal file
View File

@ -0,0 +1,62 @@
#------------------------------------------------------------------------------
#
# Python GUI - Actions - Generic
#
#------------------------------------------------------------------------------
from GUI.Properties import overridable_property
from GUI.Exceptions import ApplicationError
#------------------------------------------------------------------------------
def action_property(name, doc):
attr = intern('_' + name)
def getter(self):
return getattr(self, attr)
def setter(self, value):
setattr(self, attr, value)
return property(getter, setter, None, doc)
#------------------------------------------------------------------------------
class ActionBase(object):
"""Mixin class providing base support for action properties."""
def do_named_action(self, name):
#print "ActionBase.do_named_action:", repr(name) ###
action = getattr(self, name)
#print "...action =", repr(action) ###
if action:
try:
if isinstance(action, tuple):
args = action[1:]
action = action[0]
else:
args = ()
if isinstance(action, str):
#print "...handling", action ###
self.handle(action, *args)
else:
action(*args)
except ApplicationError:
raise
except:
import sys
et, ev, tb = sys.exc_info()
raise et, et("%s (while doing action %r%r)" % (ev, action, args)), tb
#------------------------------------------------------------------------------
class Action(ActionBase):
"""Mixin class providing a single action property called 'action'."""
action = action_property('action', """Action to be performed.
May be <action> or (<action>, <arg>...) where <action> is either
a message name or a callable object.""")
_action = None
def do_action(self):
"Invoke the action."
self.do_named_action('action')

63
GUI/Generic/Alerts.py Normal file
View File

@ -0,0 +1,63 @@
#-----------------------------------------------------------------------
#
# PyGUI - Alert functions - Generic
#
#-----------------------------------------------------------------------
from GUI import BaseAlertFunctions
def alert(kind, prompt, ok_label = "OK", **kwds):
"""Displays an alert box with one button. Does not return a value.
Kind may be 'stop' for conditions preventing continuation,
'caution' for warning messages, 'note' for informational
messages, and 'query' for asking a question of the user."""
BaseAlertFunctions.alert(kind, prompt, ok_label, **kwds)
def alert2(kind, prompt, yes_label = "Yes", no_label = "No",
**kwds):
"""Displays an alert with two buttons. Returns 1 if the
first button is pressed, 0 if the second button is pressed.
The 'default' and 'cancel' arguments specify which buttons,
if any, are activated by the standard keyboard equivalents,
and take the values 1, 0 or None."""
return BaseAlertFunctions.alert2(kind, prompt, yes_label, no_label,**kwds)
def alert3(kind, prompt,
yes_label = "Yes", no_label = "No", other_label = "Cancel",
**kwds):
"""Displays an alert with 3 buttons. Returns 1 if the
first button is pressed, 0 if the second button is pressed,
and -1 if the third button is pressed. The 'default' and 'cancel'
arguments specify which buttons, if any, are activated by the
standard keyboard equivalents, and take the values 1, 0, -1 or None."""
return BaseAlertFunctions.alert3(kind, prompt, yes_label, no_label, other_label, **kwds)
def stop_alert(*args, **kwds):
"""Displays a 1-button alert of type 'stop'. See alert()."""
alert('stop', *args, **kwds)
def note_alert(*args, **kwds):
"""Displays a 1-button alert of type 'note'. See alert()."""
alert('note', *args, **kwds)
def confirm(*args, **kwds):
"""Displays a 2-button alert of type 'caution'. See alert2()."""
return alert2('caution', *args, **kwds)
def ask(*args, **kwds):
"""Displays a 2-button alert of type 'query'. See alert2()."""
return alert2('query', *args, **kwds)
def confirm_or_cancel(*args, **kwds):
"""Displays a 3-button alert of type 'caution'. See alert3()."""
return alert3('caution', *args, **kwds)
def ask_or_cancel(*args, **kwds):
"""Displays a 3-button alert of type 'query'. See alert3()."""
return alert3('query', *args, **kwds)

View File

@ -0,0 +1,26 @@
#
# Python GUI - Basic alert functions - Generic
#
from GUI.AlertClasses import Alert, Alert2, Alert3
def present_and_destroy(dlog):
dlog.center()
try:
return dlog.present()
finally:
dlog.destroy()
def alert(kind, prompt, ok_label, **kwds):
present_and_destroy(Alert(kind, prompt, ok_label))
def alert2(kind, prompt, yes_label, no_label, **kwds):
return present_and_destroy(
Alert2(kind, prompt, yes_label, no_label, **kwds))
def alert3(kind, prompt, yes_label, no_label, other_label, **kwds):
return present_and_destroy(
Alert3(kind, prompt, yes_label, no_label, other_label, **kwds))

52
GUI/Generic/Column.py Normal file
View File

@ -0,0 +1,52 @@
#---------------------------------------------------------------------------
#
# PyGUI - Column layout component - Generic
#
#---------------------------------------------------------------------------
from LayoutUtils import equalize_components
from GUI import Frame, export
class Column(Frame):
def __init__(self, items, spacing = 10, align = 'l', equalize = '',
expand = None, padding = (0, 0), **kwds):
Frame.__init__(self)
hpad, vpad = padding
if expand is not None and not isinstance(expand, int):
expand = items.index(expand)
equalize_components(items, equalize)
width = 0
for item in items:
if item:
width = max(width, item.width)
y = vpad
gap = 0
vanchor = 't'
hanchor = align
for i, item in enumerate(items):
if item:
y += gap
if 'l' in align:
x = 0
if 'r' in align:
item.width = width
elif align == 'r':
x = width - item.width
else:
x = (width - item.width) // 2
item.position = (x + hpad, y)
if i == expand:
item.anchor = 'tb' + hanchor
vanchor = 'b'
else:
item.anchor = vanchor + hanchor
y += item.height
if i == expand:
vanchor = 'b'
gap = spacing
self.size = (width + 2 * hpad, y + vpad)
self.add(items)
self.set(**kwds)
export(Column)

View File

@ -0,0 +1,10 @@
#-------------------------------------------------------------------------------
#
# PyGUI - Facilities for compatibility across Python versions
#
#-------------------------------------------------------------------------------
try:
from __builtin__ import set
except ImportError:
from sets import Set as set

321
GUI/Generic/Document.py Normal file
View File

@ -0,0 +1,321 @@
#
# Python GUI - Documents - Generic
#
import os, tempfile
from GUI import export
from GUI.Alerts import confirm, confirm_or_cancel
from GUI.Properties import overridable_property
from GUI import Model
from GUI import MessageHandler
from GUI.Files import FileRef, DirRef
from GUI.FileDialogs import request_new_file
from GUI import application
from GUI.Exceptions import Cancel, UnimplementedMethod, ApplicationError
from GUI.Printing import PageSetup, present_page_setup_dialog
_next_doc_number = 1 # Counter for generating default titles
class Document(Model, MessageHandler):
"""A Document represents an
application data structure that can be stored in a file. It
implements the standard parts of asking the user for file names and
reading and writing files.
Each Document can have one or more windows associated with it. When
the last window belonging to a document is closed, the document itself
is closed.
A Document provides support for keeping track of whether it has been
edited, and asking the user whether to save changes when it is
closed."""
# The following attribute prevents a Document that is the parent
# of a Model from being pickled along with that Model.
pickle_as_parent_model = False
needs_saving = overridable_property('needs_saving',
"True if the document has been edited and needs to be saved.")
file = overridable_property('file',
"""FileRef of the file that the document was read from or last written
to, or None. Changing this causes update_title to be called.""")
file_type = overridable_property('file_type',
"""FileType specifying the type of file handled by this document.""")
title = overridable_property('title',
"""The title of the document. Changing this causes update_title of each
associated window to be called.""")
windows = overridable_property('windows',
"List of windows associated with the document. Do not modify directly.")
page_setup = overridable_property('page_setup',
"The PageSetup to be used for printing this document.")
binary = True # True if files are to be opened in binary mode
_file_type = None # Type of file to create when saving
_needs_saving = 0 # True if has been edited
_file = None # FileRef of associated file, if any
_title = None # Title for use in window banners, etc.
_windows = None # List of associated windows
_page_setup = None # Document-specific PageSetup instance
#
# Initialisation and destruction
#
def __init__(self, **kwds):
self._windows = []
Model.__init__(self, **kwds)
application()._add_document(self)
def destroy(self):
"""Destroy any associated windows, then destroy document contents."""
#print "Document.destroy:", self ###
for win in self._windows[:]:
win.destroy()
application()._remove_document(self)
self.destroy_contents()
Model.destroy(self)
#
# Properties
#
def get_needs_saving(self):
return self._needs_saving
def set_needs_saving(self, x):
if self._needs_saving <> x:
self._needs_saving = x
for window in self._windows:
window._document_needs_saving(x)
def get_file(self):
return self._file
def set_file(self, x):
self._file = x
if x is not None:
application()._last_directory = x.dir
self.update_title()
def get_file_type(self):
return self._file_type
def set_file_type(self, x):
self._file_type = x
def get_title(self):
t = self._title
if t == None:
t = self.make_title()
self._title = t
return t
def set_title(self, x):
self._title = x
for win in self._windows:
win.update_title()
def get_windows(self):
return self._windows
def get_page_setup(self):
ps = self._page_setup
if not ps:
ps = PageSetup()
self._page_setup = ps
return ps
def set_page_setup(self, ps):
self._page_setup = ps
#
# Methods
#
def changed(self):
"Set the needs_saving property to true."
self.needs_saving = 1
def new_contents(self):
"""Should initialise the document to the appropriate state following a New
command."""
pass
def read_contents(self, file):
"""Should initialise the document's contents by reading it from the given
file object."""
raise UnimplementedMethod(self, 'read_contents')
def write_contents(self, file):
"""Should write the document's contents to the given file object."""
raise UnimplementedMethod(self, 'write_contents')
def destroy_contents(self):
"""Called when the contents of the document are about to be discarded.
If the contents contains any Model objects, they should be destroyed."""
def save_changes(self):
"""If the document has been edited, ask the user whether to save changes,
and do so if requested."""
if self._needs_saving:
result = confirm_or_cancel('Save changes to "%s"?' % self.title,
"Save", "Don't Save", "Cancel")
if result < 0:
raise Cancel
if result:
self.save_cmd()
def save_cmd(self):
"""Implements the standard Save command. Writes the document to its
associated file, asking the user for one first if necessary."""
if self._file == None:
self.get_new_file_name()
try:
self.write()
except EnvironmentError, e:
raise ApplicationError("Unable to save '%s'." % self._file.name, e)
def save_as_cmd(self):
"""Implements the standard Save As... command. Asks the user for a new file
and writes the document to it."""
self.get_new_file_name()
self.save_cmd()
def revert_cmd(self):
"""Implements the standard Revert command. Discards the current contents
of the document and re-reads it from the associated file."""
if self._file != None:
if confirm(
'Revert to the last saved version of "%s"?' % self.title,
"Revert", "Cancel"):
self.destroy_contents()
self.read()
def close_cmd(self):
"""Implements the standard Close command. Asks whether to save any
changes, then destroys the document."""
self.save_changes()
self.destroy()
def page_setup_cmd(self):
if present_page_setup_dialog(self.page_setup):
self.changed()
def make_title(self):
"""Generates a title for the document. If associated with a file,
uses its last pathname component, else generates 'Untitled-n'."""
global _next_doc_number
if self._file != None:
return os.path.basename(self._file)
else:
n = _next_doc_number
_next_doc_number = n + 1
return "Untitled-%d" % n
def update_title(self):
"""Called when the file property changes, to update the
title property appropriately."""
file = self._file
if file:
self.title = file.name
def get_default_save_directory(self):
"""Called when the user is about to be asked for a location in which
to save a document that has not been saved before, to find a default
directory for request_new_file(). Should return a DirRef or FileRef,
or None if there is no particular preferred location."""
return None
def get_default_save_filename(self):
"""Called when the user is about to be asked for a location in which
to save a document that has not been saved before, to find a default
file name for request_new_file(). Should return a suggested file name,
or an empty string to require the user to enter a file name."""
return ""
#
# Internal methods
#
def get_new_file_name(self):
"""Ask the user for a new file and associate the document with it."""
old_file = self.file
if old_file:
old_name = old_file.name
old_dir = old_file.dir
else:
old_name = self.get_default_save_filename()
old_dir = self.get_default_save_directory()
#print "Document.get_new_file_name: old_dir =", old_dir, "old_name =", old_name ###
new_file = request_new_file(
#'Save "%s" as:' % self.title,
default_dir = old_dir,
default_name = old_name,
file_type = self.file_type or application().save_file_type)
if new_file is None:
raise Cancel()
self.file = new_file
def read(self):
"""Read the document from its currently associated file. The
document must be associated with a file and not have any existing
contents when this is called."""
if self.binary:
mode = "rb"
else:
mode = "rU"
file = self.file.open(mode)
try:
self.read_contents(file)
finally:
file.close()
self.needs_saving = 0
def write(self):
"""Write the document to its currently associated file. The
document must be associated with a file when this is called.
The document is initially written to a temporary file which
is then renamed, so if writing fails part way through, the
original file is undisturbed."""
if self.binary:
mode = "wb"
else:
mode = "w"
dir_path = self.file.dir.path
fd, temp_path = tempfile.mkstemp(dir = dir_path, text = not self.binary)
file = os.fdopen(fd, mode)
try:
try:
self.write_contents(file)
finally:
file.close()
except EnvironmentError:
os.unlink(fd)
raise
path = self.file.path
try:
os.unlink(path)
except EnvironmentError:
pass
os.rename(temp_path, path)
self.needs_saving = 0
def setup_menus(self, m):
#print "Document.setup_menus" ###
if self._needs_saving or not self._file:
m.save_cmd.enabled = 1
if self._needs_saving and self._file:
m.revert_cmd.enabled = 1
m.save_as_cmd.enabled = 1
m.page_setup_cmd.enabled = 1
def next_handler(self):
return application()
export(Document)

View File

@ -0,0 +1,16 @@
#
# PyGUI - Enumerated type facilities
#
class EnumMap(dict):
def __init__(self, __name__, *args, **kwds):
self.name = __name__
dict.__init__(self, *args, **kwds)
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
raise ValueError("Invalid %s '%s', should be one of %s" %
(self.name, key, ", ".join(["'%s'" % val for val in self.keys()])))

67
GUI/Generic/Exceptions.py Normal file
View File

@ -0,0 +1,67 @@
#
# Exceptions.py - GUI exception classes
#
class Cancel(Exception):
"""Exception raised when user cancels an operation."""
pass
#class Quit(Exception):
# """Exception raised to exit the main event loop."""
# pass
class Error(StandardError):
def __init__(self, obj, mess):
self.obj = obj
self.mess = mess
Exception.__init__(self, "%s: %s" % (obj, mess))
class ApplicationError(StandardError):
"""Exception used for reporting errors to the user."""
def __init__(self, message, detail = None):
self.message = message
self.detail = detail
if detail:
message = "%s\n\n%s" % (message, detail)
StandardError.__init__(self, message)
class InternalError(Exception):
pass
class UnimplementedMethod(NotImplementedError):
def __init__(self, obj, meth_name):
self.obj = obj
StandardError.__init__(self, "%s.%s not implemented" % \
(obj.__class__.__name__, meth_name))
class ArgumentError(TypeError):
def __init__(self, obj, meth_name, arg_name, value):
self.obj = obj
self.meth_name = meth_name
self.arg_name = arg_name
self.value = value
TypeError.__init__(self,
"%s: Invalid value %s for argument %s of method %s",
(obj, value, arg_name, meth_name))
class SetAttributeError(AttributeError):
def __init__(self, obj, attr):
self.obj = obj
self.attr = attr
AttributeError.__init__(self, "Attribute '%s' of %s cannot be set" % (attr, obj))
class UsageError(StandardError):
pass

View File

@ -0,0 +1,56 @@
#
# Python GUI - File selection dialogs - Generic
#
from GUI.BaseFileDialogs import _request_old, _request_new
def request_old_file(prompt = "Open File", default_dir = None, file_types = None):
"""Present a dialog for selecting an existing file.
Returns a FileRef, or None if cancelled."""
return _request_old(prompt, default_dir, file_types,
dir = False, multiple = False)
def request_old_files(prompt = "Choose Files", default_dir = None, file_types = None):
"""Present a dialog for selecting a set of existing files.
Returns a list of FileRefs, or None if cancelled."""
return _request_old(prompt, default_dir, file_types,
dir = False, multiple = True)
def request_old_directory(prompt = "Choose Folder", default_dir = None):
"""Present a dialog for selecting an existing directory.
Returns a FileRef, or None if cancelled."""
return _request_old(prompt, default_dir, file_types = None,
dir = True, multiple = False)
def request_old_directories(prompt = "Choose Folders", default_dir = None,
multiple = False):
"""Present a dialog for selecting a set of existing directories.
Returns a list of FileRefs, or None if cancelled."""
return _request_old(prompt, default_dir, file_types = None,
dir = True, multiple = True)
def request_new_file(prompt = "Save As:", default_dir = None,
default_name = "", file_type = None):
"""Present a dialog requesting a name and location for a new file.
Returns a FileRef, or None if cancelled."""
return _request_new(prompt, default_dir, default_name, file_type,
dir = False)
def request_new_directory(prompt = "Create Folder:", default_dir = None,
default_name = ""):
"""Present a dialog requesting a name and location for a new directory.
Returns a FileRef, or None if cancelled."""
return _request_new(prompt, default_dir, default_name, file_type = None,
dir = True)

View File

@ -0,0 +1,67 @@
#
# Python GUI - Alerts - Generic
#
from GUI import BaseAlert
from GUI import Button
from GUI.StdButtons import DefaultButton, CancelButton
class Alert(BaseAlert):
def __init__(self, kind, prompt,
ok_label = "OK", default = 1, **kwds):
BaseAlert.__init__(self, kind, prompt,
button_labels = [ok_label], default = default, **kwds)
def _create_buttons(self, ok_label):
self.yes_button = DefaultButton(title = ok_label, action = self.yes)
#self.default_button = self.ok_button
def _layout_buttons(self):
self.place(self.yes_button,
right = self.label.right,
top = self.label + self._label_button_spacing)
class Alert2(BaseAlert):
def __init__(self, kind, prompt,
yes_label = "Yes", no_label = "No",
default = 1, cancel = 0, **kwds):
BaseAlert.__init__(self, kind, prompt,
button_labels = [yes_label, no_label],
default = default, cancel = cancel, **kwds)
def _create_buttons(self, yes_label, no_label):
self.yes_button = DefaultButton(title = yes_label, action = self.yes)
self.no_button = CancelButton(title = no_label, action = self.no)
def _layout_buttons(self):
self.place_row([self.no_button, self.yes_button],
right = self.label.right,
top = self.label + self._label_button_spacing)
class Alert3(BaseAlert):
_minimum_width = 300
def __init__(self, kind, prompt,
yes_label = "Yes", no_label = "No", other_label = "Cancel",
default = 1, cancel = -1, **kwds):
BaseAlert.__init__(self, kind, prompt,
button_labels = [yes_label, no_label, other_label],
default = default, cancel = cancel, **kwds)
def _create_buttons(self, yes_label, no_label, cancel_label):
self.yes_button = DefaultButton(title = yes_label, action = self.yes)
self.no_button = CancelButton(title = no_label, action = self.no)
self.other_button = Button(title = cancel_label, action = self.other)
def _layout_buttons(self):
self.place_row([self.other_button, self.yes_button],
right = self.label.right,
top = self.label + self._label_button_spacing)
self.place(self.no_button,
left = self._left_margin, top = self.label + self._label_button_spacing)

View File

@ -0,0 +1,547 @@
#
# Python GUI - Application class - Generic
#
import os, sys, traceback
from GUI import Globals
from GUI.Properties import Properties, overridable_property
from GUI import MessageHandler
from GUI.Exceptions import Cancel, UnimplementedMethod, UsageError, \
ApplicationError #, Quit
from GUI.StdMenus import basic_menus
from GUI.GMenus import MenuState
from GUI.Files import FileRef
from GUI.Printing import PageSetup, present_page_setup_dialog
class Application(Properties, MessageHandler):
"""The user should create exactly one Application object,
or subclass thereof. It implements the main event loop
and other application-wide behaviour."""
_windows = None # List of all existing Windows
_documents = None # List of all existing Documents
_menus = None # Menus to appear in all Windows
_clipboard = None
_save_file_type = None
_exit_event_loop_flag = False
_last_directory = None
windows = overridable_property('windows',
"""A list of all existing Windows.""")
documents = overridable_property('documents',
"""A list of all existing documents.""")
menus = overridable_property('menus',
"""A list of Menus that are to be available from all Windows.""")
open_file_types = overridable_property('open_file_types',
"""List of FileTypes openable by the default Open... command.""")
save_file_type = overridable_property('save_file_type',
"""Default FileType for Documents that do not specify their own.""")
file_type = overridable_property('file_type',
"""Write only. Sets open_file_types and save_file_type.""")
target = overridable_property('target',
"""Current target for key events and menu messages.""")
target_window = overridable_property('target_window',
"""Window containing the current target, or None if there are no windows.""")
page_setup = overridable_property('page_setup',
"""Default PageSetup instance.""")
def __init__(self, title = None):
if Globals._application is not None:
raise UsageError("More than one Application instance created")
if title:
Globals.application_name = title
self._open_file_types = []
self._windows = []
self._documents = []
self._update_list = []
self._idle_tasks = []
self._page_setup = None
Globals._application = self
self._quit_flag = False
def destroy(self):
Globals._application = None
#
# Constants
#
# def get_std_menus(self):
# """Returns a list of Menus containing the standard
# framework-defined menu commands in their standard
# positions."""
# return basic_menus()
#
# std_menus = property(get_std_menus)
#
# Properties
#
def get_windows(self):
return self._windows
def get_documents(self):
return self._documents
def get_menus(self):
menus = self._menus
if menus is None:
menus = []
return menus
def set_menus(self, menus):
self._menus = menus
def get_open_file_types(self):
return self._open_file_types
def set_open_file_types(self, x):
self._open_file_types = x
def get_save_file_type(self):
return self._save_file_type
def set_save_file_type(self, x):
self._save_file_type = x
def set_file_type(self, x):
self._open_file_types = [x]
self._save_file_type = x
def get_page_setup(self):
# This property is initialised lazily, because on Windows it turn out
# that calling PageSetupDlg() before the application's first window is
# created causes the app not to be brought to the foreground initially.
ps = self._page_setup
if not ps:
ps = PageSetup()
self._page_setup = ps
return ps
def set_page_setup(self, x):
self._page_setup = x
#
# Event loop
#
def run(self):
"""The main event loop. Runs until _quit() is called, or
KeyboardInterrupt or SystemExit is raised."""
# Implementations may override this together with _quit() to use
# a different means of causing the main event loop to exit.
self.process_args(sys.argv[1:])
if self._menus is None:
self.menus = basic_menus()
while not self._quit_flag:
try:
self.event_loop()
#except (KeyboardInterrupt, Quit), e:
except KeyboardInterrupt:
return
except SystemExit:
raise
except:
self.report_error()
def _quit(self):
# Causes the main event loop to exit.
self._quit_flag = True
self._exit_event_loop()
def event_loop(self):
"""Loop reading and handling events until exit_event_loop() is called."""
# Implementations may override this together with exit_event_loop() to
# implement non-modal event loops in a different way.
self._event_loop(None)
def _event_loop(self, modal_window):
# Generic modal and non-modal event loop.
# Loop reading and handling events for the given window, or for all
# windows if window is None, until exit_event_loop() is called.
# Enabled application-wide menu items should be selectable in any case.
# If an exception other than Cancel is raised, it should either be
# reported using report_error() or propagated. Implementations may
# override this together with _exit_event_loop() if handling events
# individually is not desirable.
save = self._exit_event_loop_flag
self._exit_event_loop_flag = False
try:
while not self._exit_event_loop_flag:
try:
self.handle_next_event(modal_window)
except Cancel:
pass
finally:
self._exit_event_loop_flag = save
def exit_event_loop(self):
"""Cause the current call to event_loop() or modal_event_loop()
to exit."""
self._exit_event_loop()
def _exit_event_loop(self):
# Exit the generic _event_loop implementation.
self._exit_event_loop_flag = True
# def event_loop_until(self, exit):
# """Loop reading and handling events until exit() returns
# true, _quit_flag is set or an exception other than Cancel
# is raised."""
# while not exit() and not self._quit_flag:
# try:
# self.handle_next_event()
# except Cancel:
# pass
# def handle_events(self):
# """Handle events until an exception occurs. Waits for at least one event;
# may handle more, at the discretion of the implementation."""
# self.handle_next_event()
def handle_next_event(self, modal_window):
# Wait for the next event to arrive and handle it. Transparently handles
# any internal events such as window updates, etc., and executes any idle
# tasks that become due while waiting for an event. If modal_window is
# not None, restrict interaction to that window (but allow use of enabled
# application-wide menu items).
#
# This only needs to be implemented if the generic _event_loop() is being
# used.
raise UnimplementedMethod(self, "handle_next_event")
#
# Menu commands
#
def setup_menus(self, m):
m.new_cmd.enabled = 1
m.open_cmd.enabled = 1
m.page_setup_cmd.enabled = 1
m.quit_cmd.enabled = 1
def new_cmd(self):
"Handle the New menu command."
doc = self.make_new_document()
if not doc:
raise UsageError(
"Application.make_document(None) did not return a Document.")
doc.new_contents()
self.make_window(doc)
def open_cmd(self):
"Handle the Open... menu command."
from FileDialogs import request_old_file
dir = self.get_default_open_directory()
fileref = request_old_file(default_dir = dir,
file_types = self._open_file_types)
if fileref:
self.open_fileref(fileref)
else:
raise Cancel
def get_default_open_directory(self):
"""Called by the default implementation of open_cmd() to find an initial
directory for request_old_file(). Should return a DirRef or FileRef, or
None if there is no preferred location. By default it returns the last
directory in which a document was opened or saved during this session,
if any."""
return self._last_directory
def page_setup_cmd(self):
present_page_setup_dialog(self.page_setup)
def quit_cmd(self):
"""Handle the Quit menu command."""
while self._documents:
self._documents[0].close_cmd()
windows = self._windows
while windows:
window = windows[-1]
window.destroy()
assert not (windows and windows[-1] is window), \
"%r failed to remove itself from application on destruction" % window
self._quit()
#
# Opening files
#
def process_args(self, args):
"""Process command line arguments. Called by run() when the application
is starting up."""
if not args:
self.open_app()
else:
for arg in args:
if os.path.exists(arg):
arg = os.path.abspath(arg)
self.open_path(arg)
def open_app(self):
"""Called by run() when the application is opened with no arguments."""
pass
def open_path(self, path):
"""Open document specified by a pathname. Called for each command line
argument when the application is starting up."""
self.open_fileref(FileRef(path = path))
def open_fileref(self, fileref):
"""Open document specified by a FileRef."""
doc = self.make_file_document(fileref)
if not doc:
raise ApplicationError("The file '%s' is not recognised by %s." % (
fileref.name, Globals.application_name))
doc.set_file(fileref)
try:
doc.read()
except EnvironmentError, e:
raise ApplicationError("Unable to open '%s'." % fileref.name, e)
self.make_window(doc)
#
# Message dispatching
#
# def dispatch(self, message, *args):
# target_window = self._find_target_window()
# if target_window:
# target_window.dispatch(message, *args)
# else:
# self.handle(message, *args)
def dispatch(self, message, *args):
self.target.handle(message, *args)
def dispatch_menu_command(self, command):
if isinstance(command, tuple):
name, index = command
self.dispatch(name, index)
else:
self.dispatch(command)
def get_target(self):
# Implementations may override this to locate the target in a
# different way if they choose not to implement the Window.target
# property. Should return self if no other target can be found.
window = self.target_window
if window:
return window.target
else:
return self
def get_target_window(self):
"""Return the window to which messages should be dispatched, or None."""
raise NotImplementedError
#
# Abstract
#
def make_new_document(self):
"""Create a new Document object of the appropriate
class in response to a New command."""
return self.make_document(None)
def make_file_document(self, fileref):
"""Create a new Document object of the appropriate
class for the given FileRef."""
return self.make_document(fileref)
def make_document(self, fileref):
"""Should create a new Document object of the appropriate
class for the given FileRef, or if FileRef is None, a new
empty Document of the appropriate class for the New command."""
return None
def make_window(self, document):
"""Should create a Window set up appropriately for viewing
the given Document."""
raise UnimplementedMethod(self, 'make_window')
#
# Clipboard
#
def query_clipboard(self):
"Tests whether the clipboard contains any data."
return not not self._clipboard
def get_clipboard(self):
return self._clipboard
def set_clipboard(self, x):
self._clipboard = x
#
# Window list management
#
def _add_window(self, window):
if window not in self._windows:
self._windows.append(window)
def _remove_window(self, window):
if window in self._windows:
self._windows.remove(window)
#
# Document list management
#
def _add_document(self, doc):
if doc not in self._documents:
self._documents.append(doc)
def _remove_document(self, doc):
if doc in self._documents:
self._documents.remove(doc)
#
# Exception reporting
#
def report_error(self):
"""Display an appropriate error message for the most recent
exception caught."""
try:
raise
except Cancel:
pass
except ApplicationError, e:
from GUI.Alerts import stop_alert
stop_alert(str(e))
except:
self.report_exception()
def report_exception(self):
"""Display an alert box describing the most recent exception, and
giving the options Continue, Traceback or Abort. Traceback displays
a traceback and continues; Abort raises SystemExit."""
try:
exc_type, exc_val, exc_tb = sys.exc_info()
exc_desc = "%s: %s" % (exc_type.__name__, exc_val)
self.print_traceback(exc_desc, exc_tb)
from GUI.Alerts import alert3
message = "Sorry, something went wrong."
result = alert3('stop', "%s\n\n%s" % (message, exc_desc),
"Continue", "Abort", "Traceback",
default = 1, cancel = None, width = 450, lines = 5)
if result == 1: # Continue
return
elif result == -1: # Traceback
self.display_traceback(exc_desc, exc_tb)
return
else: # Abort
raise SystemExit
except (KeyboardInterrupt, SystemExit):
os._exit(1)
except:
print >>sys.stderr, "---------- Exception while reporting exception ----------"
traceback.print_exc()
print >>sys.stderr, "------------------ Original exception -------------------"
traceback.print_exception(exc_type, exc_val, exc_tb)
#os._exit(1)
def display_traceback(self, exc_desc, exc_tb):
"""Display an exception description and traceback.
TODO: display this in a scrolling window."""
self.print_traceback(exc_desc, exc_tb)
def print_traceback(self, exc_desc, exc_tb):
"""Print exception description and traceback to standard error."""
import traceback
sys.stderr.write("\nTraceback (most recent call last):\n")
traceback.print_tb(exc_tb)
sys.stderr.write("%s\n\n" % exc_desc)
#
# Other
#
def zero_windows_allowed(self):
"""Platforms should implement this to return false if there
must be at least one window open at all times. Returning false
here forces the Quit command to be used instead of Close when
there is only one window open."""
# TODO: Move this somewhere more global.
raise UnimplementedMethod(self, 'zero_windows_allowed')
def _perform_menu_setup(self, menus = None):
"""Given a list of Menu objects, perform menu setup processing
and update associated platform menus ready for popping up or
pulling down."""
if menus is None:
menus = self._effective_menus()
menu_state = MenuState(menus)
menu_state.reset()
self._dispatch_menu_setup(menu_state)
for menu in menus:
menu._update_platform_menu()
def _dispatch_menu_setup(self, menu_state):
self.dispatch('_setup_menus', menu_state)
def _effective_menus(self):
"""Return a list of the menus in effect for the currently active
window, including both application-wide and window-specific menus,
in an appropriate order according to platform conventions."""
window = self.target_window
return self._effective_menus_for_window(window)
def _effective_menus_for_window(self, window):
"""Return a list of the menus in effect for the specified
window, including both application-wide and window-specific menus,
in an appropriate order according to platform conventions."""
menus = self.menus
if window:
menus = menus + window.menus
regular_menus = []
special_menus = []
for menu in menus:
if menu.special:
special_menus.insert(0, menu)
else:
regular_menus.append(menu)
return regular_menus + special_menus
# def _may_close_a_window(self):
# # On implementations where at least one window is needed in order to
# # interact with the application, check whether closing a window would
# # leave no more visible windows.
# if self.zero_windows_allowed():
# return True
# count = 0
# for window in self.windows:
# if window.visible:
# count += 1
# if count >= 2:
# return True
# return False
def _check_for_no_windows(self):
# On implementations where at least one window is needed in order to
# interact with the application, check whether there are no more visible
# windows and take appropriate action.
if not self.zero_windows_allowed():
for window in self.windows:
if window.visible:
return
self.no_visible_windows()
def no_visible_windows(self):
"""On platforms that require a window in order to interact with the
application, this is called when there are no more visible windows.
The default action is to close the application; subclasses may override
it to take some other action, such as creating a new window."""
self.quit_cmd()

117
GUI/Generic/GBaseAlerts.py Normal file
View File

@ -0,0 +1,117 @@
#
# Python GUI - Alert base class - Generic
#
import textwrap
from GUI import ModalDialog
from GUI import Label
class BaseAlert(ModalDialog):
_wrapwidth = 50
_minimum_width = 200
_left_margin = 24
_right_margin = 24
_top_margin = 14
_bottom_margin = 20
_icon_spacing = 16
_label_button_spacing = 20
_default_width = 380
_default_lines = 3
yes_button = None
no_button = None
other_button = None
def __init__(self, kind, prompt, width = None, lines = None,
button_labels = None, default = None, cancel = None):
#if width is None:
# width = self._default_width
#if lines is None:
# lines = self._default_lines
ModalDialog.__init__(self, style = 'alert')
self.label = Label(text = self._wrap(prompt), lines = lines)
if self.label.width < self._minimum_width:
self.label.width = self._minimum_width
self._create_buttons(*button_labels)
#self.default_button = self._find_button(default)
#self.cancel_button = self._find_button(cancel)
self._layout(kind)
def _layout(self, kind):
icon_width, icon_height = self._layout_icon(kind)
label_left = self._left_margin
if icon_width:
label_left += icon_width + self._icon_spacing
if self.label.height < icon_height:
self.label.height = icon_height
self.place(self.label,
left = label_left,
top = self._top_margin)# + icon_height/4)
#_wrap_text(self.label, self._default_width - label_left - self._right_margin)
self._layout_buttons()
self.shrink_wrap(padding = (self._right_margin, self._bottom_margin))
def _layout_icon(self, kind):
# Place icon for the given alert kind, if any, and return its size.
# If there is no icon, return (0, 0).
return (0, 0)
def _wrap(self, text):
width = self._wrapwidth
return "\n\n".join(
[textwrap.fill(para, width)
for para in text.split("\n\n")])
def _find_button(self, value):
#print "BaseAlert._find_button:", value ###
if value == 1:
result = self.yes_button
elif value == 0:
result = self.no_button
elif value == -1:
result = self.other_button
else:
result = None
#print "BaseAlert._find_button: result =", result ###
return result
def yes(self):
self.dismiss(1)
def no(self):
self.dismiss(0)
def other(self):
self.dismiss(-1)
#def _wrap_text(label, label_width):
# hard_lines = [text.split()
# for text in label.text.split("\n")]
# words = hard_lines[0]
# for hard_line in hard_lines[1:]:
# words.append("\n")
# words.extend(hard_line)
# font = label.font
# space_width = font.width(" ")
# lines = []
# line = []
# line_width = 0
# for word in words:
# word_width = font.width(word)
# if word == "\n" or (line_width > 0
# and line_width + space_width + word_width > label_width):
# lines.append(line)
# line = []
# line_width = 0
# if word <> "\n":
# line.append(word)
# if line_width > 0:
# line_width += space_width
# line_width += word_width
# if line:
# lines.append(line)
# label.text = "\n".join([" ".join(line) for line in lines])

23
GUI/Generic/GButtons.py Normal file
View File

@ -0,0 +1,23 @@
#
# Python GUI - Buttons - Generic
#
from GUI.Properties import overridable_property
from GUI.Actions import Action
from GUI import Control
class Button(Control, Action):
""" A pushbutton control."""
style = overridable_property('style',
"One of 'normal', 'default', 'cancel'")
def activate(self):
"""Highlight the button momentarily and then perform its action."""
self.flash()
self.do_action()
def flash(self):
"""Highlight the button momentarily as though it had been clicked,
without performing the action."""
raise NotImplementedError

View File

@ -0,0 +1,55 @@
#
# Python GUI - Canvas Paths - Generic
#
class CanvasPaths:
# Mixin class providing generic implementations of
# canvas path construction operators.
def __init__(self):
self.newpath()
def newpath(self):
self._path = []
self._current_subpath = None
self._current_point = (0, 0)
def moveto(self, x, y):
self._current_subpath = None
self._current_point = self._coords(x, y)
def rmoveto(self, dx, dy):
x, y = self._current_point
self.moveto(x + dx, y + dy)
def lineto(self, x, y):
subpath = self._current_subpath
if subpath is None:
subpath = [self._current_point]
self._path.append(subpath)
self._current_subpath = subpath
p = self._coords(x, y)
subpath.append(p)
self._current_point = p
def rlineto(self, dx, dy):
x, y = self._current_point
self.lineto(x + dx, y + dy)
def closepath(self):
subpath = self._current_subpath
if subpath:
subpath.append(subpath[0])
self._current_subpath = None
def get_current_point(self):
return self._current_point
# Implementations may set _coords to one of the following
def _int_coords(self, x, y):
return int(round(x)), int(round(y))
def _float_coords(self, x, y):
return x, y

282
GUI/Generic/GCanvases.py Normal file
View File

@ -0,0 +1,282 @@
#
# Python GUI - Drawing - Generic
#
from GUI.StdColors import black, white
from GUI.StdFonts import application_font
from GUI.Properties import Properties, overridable_property
class Canvas(Properties):
_default_forecolor = black
_default_backcolor = white
_printing = False
pencolor = overridable_property('pencolor', "Current color for stroking paths.")
fillcolor = overridable_property('fillcolor', "Current color for filling paths.")
textcolor = overridable_property('textcolor', "Current color for drawint text.")
forecolor = overridable_property('forecolor', "Sets pen, fill and text colors to the same color.")
backcolor = overridable_property('backcolor', "Current color for erasing regions.")
pensize = overridable_property('pensize', "Width of pen for framing and stroking.")
font = overridable_property('font', "Font for drawing text.")
current_point = overridable_property('current_point', "The current point, or None.")
printing = overridable_property('printing', "True if drawing destination is a non-display device.")
#forecolor = overridable_property('forecolor', "Sets both pencolor and fillcolor.")
def __init__(self):
self.newpath()
def get_printing(self):
return self._printing
def initgraphics(self):
self.set_forecolor(self._default_forecolor)
self.set_backcolor(self._default_backcolor)
self.set_pensize(1)
self.set_font(application_font)
def set_forecolor(self, c):
self.pencolor = c
self.fillcolor = c
self.textcolor = c
def rmoveto(self, dx, dy):
x0, y0 = self._current_point()
self.moveto(x0 + dx, y0 + dy)
def rlineto(self, dx, dy):
x0, y0 = self.current_point
self.lineto(x0 + dx, y0 + dy)
def curve(self, sp, cp1, cp2, ep):
self.moveto(sp)
self.curveto(cp1, cp2, ep)
def rcurveto(self, cp1, cp2, ep):
x0, y0 = self.current_point
x1, y1 = cp1
x2, y2 = cp2
x3, y3 = ep
self.curveto(
(x0 + x1, y0 + y1),
(x0 + x2, y0 + y2),
(x0 + x3, y0 + y3))
def fill_stroke(self):
self.fill()
self.stroke()
# Rectangles
def _pen_inset_rect(self, rect):
l, t, r, b = rect
p = 0.5 * self.pensize
return (l + p, t + p, r - p, b - p)
def rect(self, rect):
l, t, r, b = rect
self.moveto(l, t)
self.lineto(r, t)
self.lineto(r, b)
self.lineto(l, b)
self.closepath()
def rect_frame(self, rect):
self.rect(self._pen_inset_rect(rect))
def fill_rect(self, rect):
self.newpath()
self.rect(rect)
self.fill()
def stroke_rect(self, rect):
self.newpath()
self.rect(rect)
self.stroke()
def frame_rect(self, rect):
self.newpath()
self.rect_frame(rect)
self.stroke()
def fill_stroke_rect(self, rect):
self.rect_path(rect)
self.fill_stroke()
def fill_frame_rect(self, rect):
self.fill_rect(rect)
self.frame_rect(rect)
def erase_rect(self, rect):
self.newpath()
self.rect(rect)
self.erase()
# Ovals
def oval_frame(self, rect):
self.oval(self._pen_inset_rect(rect))
def fill_oval(self, rect):
self.newpath()
self.oval_frame(rect)
self.fill()
def stroke_oval(self, rect):
self.newpath()
self.oval(rect)
self.stroke()
def frame_oval(self, rect):
self.newpath()
self.oval_frame(rect)
self.stroke()
def fill_stroke_oval(self, rect):
self.newpath()
self.oval(rect)
self.fill_stroke()
def fill_frame_oval(self, rect):
self.fill_oval(rect)
self.frame_oval()
def erase_oval(self, rect):
self.newpath()
self.oval(rect)
self.erase()
# Arcs
def _arc_path(self, c, r, a0, a1):
# x, y = c
# a0r = a0 * deg
# x0 = x + r * cos(a0r)
# y0 = y + r * sin(a0r)
self.newpath()
# self.moveto(x0, y0)
self.arc(c, r, a0, a1)
def _arc_frame_path(self, c, r, a0, a1):
self._arc_path(c, r - 0.5 * self.pensize, a0, a1)
def stroke_arc(self, c, r, a0, a1):
self._arc_path(c, r, a0, a1)
self.stroke()
def frame_arc(self, c, r, a0, a1):
self._arc_frame_path(c, r, a0, a1)
self.stroke()
# Wedges
def wedge(self, c, r, a0, a1):
self.moveto(*c)
self.arc(c, r, a0, a1)
self.closepath()
def fill_wedge(self, c, r, a0, a1):
self.newpath()
self.wedge(c, r, a0, a1)
self.fill()
def stroke_wedge(self, c, r, a0, a1):
self.newpath()
self.wedge(c, r, a0, a1)
self.stroke()
def fill_stroke_wedge(self, c, r, a0, a1):
self.newpath()
self.wedge(c, r, a0, a1)
self.fill_stroke()
def erase_wedge(self, c, r, a0, a1):
self.newpath()
self.wedge(c, r, a0, a1)
self.erase()
# Polylines
def lines(self, points):
point_iter = iter(points)
self.moveto(*point_iter.next())
for p in point_iter:
self.lineto(*p)
def linesto(self, points):
for p in points:
self.lineto(*p)
def stroke_lines(self, points):
self.newpath()
self.lines(points)
self.stroke()
# Polycurves
def curves(self, points):
self.moveto(*points[0])
for i in xrange(1, len(points), 3):
self.curveto(*points[i:i+3])
def curvesto(self, points):
for i in xrange(0, len(points), 3):
self.curveto(*points[i:i+3])
def stroke_curves(self, points):
self.newpath()
self.curves(points)
self.stroke()
# Polygons
def poly(self, points):
self.lines(points)
self.closepath()
def fill_poly(self, points):
self.newpath()
self.poly(points)
self.fill()
def stroke_poly(self, points):
self.newpath()
self.poly(points)
self.stroke()
def fill_stroke_poly(self, points):
self.newpath()
self.poly(points)
self.fill_stroke()
def erase_poly(self, points):
self.newpath()
self.poly(points)
self.erase()
# Loops
def loop(self, points):
self.curves(points)
self.closepath()
def fill_loop(self, points):
self.newpath()
self.loop(points)
self.fill()
def stroke_loop(self, points):
self.newpath()
self.loop(points)
self.stroke()
def fill_stroke_loop(self, points):
self.newpath()
self.loop(points)
self.fill_stroke()
def erase_loop(self, points):
self.newpath()
self.loop(points)
self.erase()

View File

@ -0,0 +1,43 @@
#
# Python GUI - Check boxes - Generic
#
from GUI.Properties import overridable_property
from GUI import Control
from GUI.Actions import Action
class CheckBox(Control, Action):
"""A CheckBox is a control used to represent a binary choice."""
def __init__(self, **kwds):
Control.__init__(self, **kwds)
on = overridable_property('on', "Boolean value of the check box.")
auto_toggle = overridable_property('auto_toggle', """If true,
the check box's 'on' property will automatically be toggled
before performing the action, if any.""")
mixed = overridable_property('mixed', """If true, the check box
is capable of displaying a mixed state.""")
_auto_toggle = True
_mixed = False
def get_auto_toggle(self):
return self._auto_toggle
def set_auto_toggle(self, v):
self._auto_toggle = v
def get_mixed(self):
return self._mixed
def set_mixed(self, v):
self._mixed = v
def get_value(self):
return self.on
def set_value(self, x):
self.on = x

46
GUI/Generic/GColors.py Normal file
View File

@ -0,0 +1,46 @@
#
# Python GUI - Colors - Generic
#
from GUI.Properties import overridable_property
class Color(object):
"""A drawing color.
Constructors:
rgb(red, green, blue, alpha = 1.0)
where red, green, blue, alpha are in the range 0.0 to 1.0
Properties:
red --> float
green --> float
blue --> float
rgb --> (red, green, blue)
rgba --> (red, green, blue, alpha)
"""
red = overridable_property('red', "Red component (0.0 to 1.0)")
green = overridable_property('green', "Blue component (0.0 to 1.0)")
blue = overridable_property('blue', "Blue component (0.0 to 1.0)")
alpha = overridable_property('alpha', "Alpha (opacity) component")
rgb = overridable_property('rgb', "Tuple of (red, green, blue) (0.0 to 1.0)")
rgba = overridable_property('rgba',
"Tuple of (red, green, blue, alpha) (0.0 to 1.0)")
def get_alpha(self):
return 1.0
def get_rgb(self):
return (self.red, self.green, self.blue)
def set_rgb(self, x):
self.red, self.green, self.blue = x
def get_rgba(self):
return (self.red, self.green, self.blue, self.alpha)
def set_rgba(self, x):
self.red, self.green, self.blue, self.alpha = x
def __str__(self):
return "Color(%g,%g,%g,%g)" % self.rgba

477
GUI/Generic/GComponents.py Normal file
View File

@ -0,0 +1,477 @@
#
# Python GUI - Components - Generic
#
import os
from GUI.Properties import Properties, overridable_property
from GUI import MessageHandler
from GUI.Geometry import add_pt, sub_pt, rect_size, rect_sized, rect_topleft
from GUI import application
_user_tab_stop = os.environ.get("PYGUI_KEYBOARD_NAVIGATION") or None
# Allow "False", "True", "0", "1"
if _user_tab_stop is not None:
_user_tab_stop = _user_tab_stop.strip().capitalize()
try:
_user_tab_stop = {"False": False, "True": True}[_user_tab_stop]
except KeyError:
try:
_user_tab_stop = int(_user_tab_stop)
except ValueError:
sys.stderr.write("PYGUI_KEYBOARD_NAVIGATION: Unrecognized value %r"
% _user_tab_stop)
_user_tab_stop = None
class Component(Properties, MessageHandler):
"""Component is an abstract class representing a user
interface component."""
left = overridable_property('left', "Position of left edge relative to container.")
top = overridable_property('top', "Position of top edge relative to container.")
right = overridable_property('right', "Position of right edge relative to container.")
bottom = overridable_property('bottom', "Position of bottom edge relative to container.")
x = overridable_property('x', "Horizontal position relative to container.")
y = overridable_property('y', "Vertical position relative to container.")
width = overridable_property('width')
height = overridable_property('height')
position = overridable_property('position', "Position relative to container.")
size = overridable_property('size')
bounds = overridable_property('bounds', "Bounding rectangle in container's coordinates.")
container = overridable_property('container',
"Container which contains this Component. Setting this property has the "
"effect of removing the component from its previous container, if any, "
"and adding it to the new one, if any.")
# visible = overridable_property('visible',
# "Whether the component is currently shown.")
tab_stop = overridable_property('tab_stop',
"Whether tab key can navigate into this control.")
anchor = overridable_property('anchor', "A string of 'ltrb' controlling behaviour when container is resized.")
border = overridable_property('border', "True if the component should have a border.")
_is_scrollable = False # Overridden by scrollable subclasses
_generic_tabbing = True # Whether to use generic tab navigation code
_default_tab_stop = False
_user_tab_stop_override = False # Whether user preference overrides _default_tab_stop
_tab_stop = None
#
# Class variables defined by implementations:
#
# _has_local_coords bool True if component has a local coordinate system
#
_container = None
_border = False
hmove = 0
vmove = 0
hstretch = 0
vstretch = 0
def __init__(self, tab_stop = None, **kwds):
Properties.__init__(self, **kwds)
if tab_stop is None:
tab_stop = self._get_default_tab_stop()
self.tab_stop = tab_stop
def destroy(self):
self.container = None
#
# Geometry properties
#
# Default implementations of position and size properties
# in terms of the bounds property. A minimal implementation
# need only implement get_bounds and set_bounds.
#
# It is the implementation's responsibility to call _resized()
# whenever the size of the component changes, either by
# explicit assignment to geometry properties or by the user
# resizing the containing window. It should not be called if
# setting a geometry property does not cause the size to change.
#
def get_left(self):
return self.position[0]
def set_left(self, v):
l, t, r, b = self.bounds
self.bounds = (v, t, r, b)
def get_top(self):
return self.bounds[1]
def set_top(self, v):
l, t, r, b = self.bounds
self.bounds = (l, v, r, b)
def get_right(self):
return self.bounds[2]
def set_right(self, v):
l, t, r, b = self.bounds
self.bounds = (l, t, v, b)
def get_bottom(self):
return self.bounds[3]
def set_bottom(self, v):
l, t, r, b = self.bounds
self.bounds = (l, t, r, v)
def get_x(self):
return self.bounds[0]
def set_x(self, v):
l, t, r, b = self.bounds
self.bounds = (v, t, v + r - l, b)
def get_y(self):
return self.bounds[1]
def set_y(self, v):
l, t, r, b = self.bounds
self.bounds = (l, v, r, v + b - t)
def get_position(self):
l, t, r, b = self.bounds
return (l, t)
def set_position(self, (x, y)):
l, t, r, b = self.bounds
self.bounds = (x, y, x + r - l, y + b - t)
def get_width(self):
l, t, r, b = self.bounds
return r - l
def set_width(self, v):
l, t, r, b = self.bounds
self.bounds = (l, t, l + v, b)
def get_height(self):
l, t, r, b = self.bounds
return b - t
def set_height(self, v):
l, t, r, b = self.bounds
self.bounds = (l, t, r, t + v)
def get_size(self):
l, t, r, b = self.bounds
return (r - l, b - t)
def set_size(self, (w, h)):
l, t, r, b = self.bounds
self.bounds = (l, t, l + w, t + h)
#
# Container management
#
def get_container(self):
return self._container
def set_container(self, new_container):
if self._container != new_container:
self._change_container(new_container)
def _change_container(self, new_container):
old_container = self._container
if old_container:
self._container = None
old_container._remove(self)
if new_container:
self._container = new_container
new_container._add(self)
#
# Message dispatching
#
def become_target(self):
"""Arrange for this object to be the first to handle messages
dispatched to the containing Window. If the component is not
contained in a Window, the effect is undefined."""
raise NotImplementedError
def is_target(self):
"""Return true if this is the current target within the containing
Window. If the component is not contained in a Window, the result
is undefined."""
return self.window and self.window.target is self
#
# Message handling
#
def next_handler(self):
return self._container
#
# Visibility control
#
# def show(self):
# """Make the Component visible (provided its container is visible)."""
# self.visible = 1
#
# def hide(self):
# """Make the Component invisible."""
# self.visible = 0
#
# Border
#
def get_border(self):
return self._border
def set_border(self, x):
self._border = x
#
# Resizing
#
def get_anchor(self):
if self.hmove:
s1 = 'r'
elif self.hstretch:
s1 = 'lr'
else:
s1 = 'l'
if self.vmove:
s2 = 'b'
elif self.vstretch:
s2 = 'tb'
else:
s2 = 't'
return s1 + s2
def set_anchor(self, s):
if 'r' in s:
if 'l' in s:
self.hstretch = True
self.hmove = False
else:
self.hstretch = False
self.hmove = True
else:
self.hstretch = False
self.hmove = False
if 'b' in s:
if 't' in s:
self.vstretch = True
self.vmove = False
else:
self.vstretch = False
self.vmove = True
else:
self.vstretch = False
self.vmove = False
def get_auto_layout(self):
return self._auto_layout
def set_auto_layout(self, x):
self._auto_layout = x
def _resized(self, delta):
# Called whenever the size of the component changes for
# any reason.
pass
def container_resized(self, delta):
"""Called whenever the component's container changes size and the
container's auto_layout property is true. The default implementation
repositions and resizes this component according to its resizing
options."""
dw, dh = delta
left, top, right, bottom = self.bounds
if self.hmove:
left += dw
right += dw
elif self.hstretch:
right += dw
if self.vmove:
top += dh
bottom += dh
elif self.vstretch:
bottom += dh
self.bounds = (left, top, right, bottom)
#
# Update region maintenance
#
def invalidate(self):
"""Mark the whole Component as needing to be redrawn."""
self.invalidate_rect(self.viewed_rect())
# def invalidate_rect(self, r):
# print "GComponent.invalidate_rect:", self, r ###
# container = self._container
# if container:
# container.invalidate_rect(r)
# def _invalidate_in_container(self):
# container = self._container
# if container:
# container._invalidate_subcomponent(self)
#
# Coordinate transformation
#
def local_to_global(self, p):
p = self.local_to_container(p)
parent = self._container
if parent:
return parent.local_to_global(p)
else:
return p
def global_to_local(self, p):
parent = self._container
if parent:
p = parent.global_to_local(p)
return self.container_to_local(p)
def local_to_container(self, p):
if self._has_local_coords:
return add_pt(p, self.local_to_container_offset())
else:
return p
def container_to_local(self, p):
if self._has_local_coords:
return sub_pt(p, self.local_to_container_offset())
else:
return p
def local_to_container_offset(self):
if self._has_local_coords:
return self.position
else:
return (0, 0)
def transform_from(self, other, p):
return transform_coords(other, self, p)
def transform_to(self, other, p):
return transform_coords(self, other, p)
#
# Placement specification support
#
def __add__(self, offset):
return (self, offset)
def __sub__(self, offset):
return (self, -offset)
#
# Tabbing
#
# def get_tabbable(self):
# return self._tabbable
#
# def set_tabbable(self, value):
# if self._tabbable <> value:
# self._tabbable = value
# self._invalidate_tab_chain()
def get_tab_stop(self):
return self._tab_stop
def set_tab_stop(self, x):
if self._tab_stop <> x:
self._tab_stop = x
self._invalidate_tab_chain()
def _get_default_tab_stop(self):
if self._user_tab_stop_override:
result = _user_tab_stop
else:
result = None
if result is None:
result = self._default_tab_stop
return result
def _tab_out(self):
pass
def _tab_in(self):
self.become_target()
def _build_tab_chain(self, chain):
if self._tab_stop:
chain.append(self)
def _invalidate_tab_chain(self):
window = self.window
if window:
window._invalidate_tab_chain()
def _is_targetable(self):
return True
#
# Other
#
window = overridable_property('window', """The Window ultimately containing
this Component, or None.""")
def get_window(self):
container = self._container
if container:
return container.window
else:
return None
def reset_blink(self):
application().reset_blink()
def viewed_rect(self):
"""Returns the rectangle in local coordinates that is
currently visible within the component."""
if self._has_local_coords:
width, height = self.size
return (0, 0, width, height)
else:
return self.bounds
def broadcast(self, message, *args):
"""Traverse the component hierarchy, calling each component's handler for
the given message, if any."""
method = getattr(self, message, None)
if method:
method(*args)
def _dispatch_mouse_event(self, event):
self._handle_mouse_event(event)
def _handle_mouse_event(self, event):
self.handle(event.kind, event)
def transform_coords(from_component, to_component, p):
if from_component:
g = from_component.local_to_global(p)
else:
g = p
if to_component:
return to_component.global_to_local(g)
else:
return g

382
GUI/Generic/GContainers.py Normal file
View File

@ -0,0 +1,382 @@
#
# Python GUI - Containers - Generic
#
try:
maketrans = str.maketrans
except AttributeError:
from string import maketrans
from GUI.Properties import overridable_property
from GUI.Exceptions import ArgumentError
from GUI.Geometry import pt_in_rect
from GUI import Component
anchor_to_sticky = maketrans("ltrb", "wnes")
class Container(Component):
"""A Container is a Component that can contain other Components.
The sub-components are clipped to the boundary of their container."""
contents = overridable_property('contents',
"List of subcomponents. Do not modify directly.")
content_width = overridable_property('content_width', "Width of the content area.")
content_height = overridable_property('content_height', "Height of the content area.")
content_size = overridable_property('content_size', "Size of the content area.")
auto_layout = overridable_property('auto_layout',
"Automatically adjust layout of subcomponents when resized.")
_auto_layout = True
# _contents [Component]
def __init__(self, **kw):
self._contents = []
Component.__init__(self, **kw)
def destroy(self):
"""Destroy this Container and all of its contents."""
contents = self._contents
while contents:
comp = contents[-1]
comp.destroy()
assert not contents or contents[-1] is not comp, \
"%r failed to remove itself from container on destruction" % comp
Component.destroy(self)
#
# Content area
#
def get_content_width(self):
return self.content_size[0]
def set_content_width(self, w):
self.content_size = w, self.content_height
def get_content_height(self):
return self.content_size[1]
def set_content_height(self, h):
self.content_size = self.content_width, h
get_content_size = Component.get_size
set_content_size = Component.set_size
#
# Subcomponent Management
#
def get_contents(self):
return self._contents
def add(self, comp):
"""Add the given Component as a subcomponent."""
if comp:
if isinstance(comp, Component):
comp.container = self
else:
for item in comp:
self.add(item)
def remove(self, comp):
"""Remove subcomponent, if present."""
if isinstance(comp, Component):
if comp in self._contents:
comp.container = None
else:
for item in comp:
self.remove(item)
def _add(self, comp):
# Called by comp.set_container() to implement subcomponent addition.
self._contents.append(comp)
self._invalidate_tab_chain()
self.added(comp)
def _remove(self, comp):
# Called by comp.set_container() to implement subcomponent removal.
self._contents.remove(comp)
self._invalidate_tab_chain()
self.removed(comp)
def added(self, comp):
"""Called after a subcomponent has been added."""
pass
def removed(self, comp):
"""Called after a subcomponent has been removed."""
pass
#
# The infamous 'place' method and friends.
#
_place_default_spacing = 8
def place(self, item,
left = None, right = None, top = None, bottom = None,
sticky = 'nw', scrolling = '', border = None, anchor = None):
"""Add a component to the frame with positioning,
resizing and scrolling options. See the manual for details."""
self._place([item], left = left, right = right, top = top, bottom = bottom,
sticky = sticky, scrolling = scrolling, border = border, anchor = anchor)
def place_row(self, items,
left = None, right = None, top = None, bottom = None,
sticky = 'nw', scrolling = '', border = None, spacing = None,
anchor = None):
"""Add a row of components to the frame with positioning,
resizing and scrolling options. See the manual for details."""
if left is not None and right is not None:
raise ValueError("Cannot specify both left and right to place_row")
elif left is None and right is not None:
direction = 'left'
items = items[:]
items.reverse()
else:
direction = 'right'
self._place(items, left = left, right = right, top = top, bottom = bottom,
sticky = sticky, scrolling = scrolling, border = border,
direction = direction, spacing = spacing, anchor = anchor)
def place_column(self, items,
left = None, right = None, top = None, bottom = None,
sticky = 'nw', scrolling = '', border = None, spacing = None,
anchor = None):
"""Add a column of components to the frame with positioning,
resizing and scrolling options. See the manual for details."""
if top is not None and bottom is not None:
raise ValueError("Cannot specify both top and bottom to place_column")
elif top is None and bottom is not None:
direction = 'up'
items = items[:]
items.reverse()
else:
direction = 'down'
self._place(items, left = left, right = right, top = top, bottom = bottom,
sticky = sticky, scrolling = scrolling, border = border,
direction = direction, spacing = spacing, anchor = anchor)
def _place(self, items,
left = None,
right = None,
top = None,
bottom = None,
sticky = 'nw',
scrolling = '',
direction = 'right',
spacing = None,
border = None,
anchor = None):
def side(spec, name):
# Process a side specification taking the form of either
# (1) an offset, (2) a reference component, or (3) a
# tuple (component, offset). Returns a tuple (ref, offset)
# where ref is the reference component or None (representing
# the Frame being placed into). Checks that the reference
# component, if any, is directly contained by this Frame.
ref = None
offset = None
if spec is not None:
if isinstance(spec, tuple):
ref, offset = spec
elif isinstance(spec, Component):
ref = spec
offset = 0
elif isinstance(spec, (int, float)):
offset = spec
else:
raise ArgumentError(self, 'place', name, spec)
if ref is self:
ref = None
elif ref:
con = ref.container
#if con is not self and isinstance(con, ScrollFrame):
# ref = con
# con = ref.container
if con is not self:
raise ValueError("Reference component for place() is not"
" directly contained by the frame being placed into.")
return ref, offset
if spacing is None:
spacing = self._place_default_spacing
# Decode the sticky options
if anchor is not None:
sticky = anchor.translate(anchor_to_sticky)
hmove = vmove = hstretch = vstretch = 0
if 'e' in sticky:
if 'w' in sticky:
hstretch = 1
else:
hmove = 1
if 's' in sticky:
if 'n' in sticky:
vstretch = 1
else:
vmove = 1
# Translate the direction argument
try:
dir = {'right':0, 'down':1, 'left':2, 'up':3}[direction]
except KeyError:
raise ArgumentError(self, 'place', 'direction', direction)
# Unpack the side arguments
left_obj, left_off = side(left, 'left')
right_obj, right_off = side(right, 'right')
top_obj, top_off = side(top, 'top')
bottom_obj, bottom_off = side(bottom, 'bottom')
# Process the items
#if not isinstance(items, list):
# items = [items]
for item in items:
x, y = item.position
w, h = item.size
# Calculate left edge position
if left_obj:
l = left_obj.left + left_obj.width + left_off
elif left_off is not None:
if left_off < 0:
l = self.width + left_off
else:
l = left_off
else:
l = None
# Calculate top edge position
if top_obj:
t = top_obj.top + top_obj.height + top_off
elif top_off is not None:
if top_off < 0:
t = self.height + top_off
else:
t = top_off
else:
t = None
# Calculate right edge position
if right_obj:
r = right_obj.left + right_off
elif right_off is not None:
if right_off <= 0:
r = self.width + right_off
else:
r = right_off
else:
r = None
# Calculate bottom edge position
if bottom_obj:
b = bottom_obj.top + bottom_off
elif bottom_off is not None:
if bottom_off <= 0:
b = self.height + bottom_off
else:
b = bottom_off
else:
b = None
# Fill in unspecified positions
if l is None:
if r is not None:
l = r - w
else:
l = x
if r is None:
r = l + w
if t is None:
if b is not None:
t = b - h
else:
t = y
if b is None:
b = t + h
if scrolling:
item.scrolling = scrolling
# Position, resize and add the item
item.bounds = (l, t, r, b)
self.add(item)
# Record resizing and border options
item.hmove = hmove
item.vmove = vmove
item.hstretch = hstretch
item.vstretch = vstretch
if border is not None:
item.border = border
# Step to the next item
if dir == 0:
left_obj = item
left_off = spacing
elif dir == 1:
top_obj = item
top_off = spacing
elif dir == 2:
right_obj = item
right_off = -spacing
else:
bottom_obj = item
bottom_off = -spacing
#
# Resizing
#
def _resized(self, delta):
if self._auto_layout:
self.resized(delta)
def resized(self, delta):
for c in self._contents:
c.container_resized(delta)
def resize(self, auto_layout = False, **kwds):
"""Change the geometry of the component, with control over whether
the layout of subcomponents is updated. The default is not to do so.
Keyword arguments to this method may be any of the properties
affecting position and size (i.e. left, top, right, bottom, x, y,
width, height, position, size, bounds)."""
old_auto_layout = self.auto_layout
try:
self.auto_layout = auto_layout
self.set(**kwds)
finally:
self.auto_layout = old_auto_layout
#
# Tabbing
#
def _build_tab_chain(self, chain):
Component._build_tab_chain(self, chain)
for c in self._contents:
c._build_tab_chain(chain)
#
# Other
#
def shrink_wrap(self, padding = None):
"""Adjust the size of the component so that it neatly encloses its
contents. If padding is specified, it specifies the amount of space
to leave at right and bottom, otherwise the minimum distance from the
left and top sides to the nearest components is used."""
contents = self.contents
if not contents:
return
if padding:
hpad, vpad = padding
else:
hpad = min([item.left for item in contents])
vpad = min([item.top for item in contents])
rights = [item.right for item in contents]
bottoms = [item.bottom for item in contents]
self.resize(size = (max(rights) + hpad, max(bottoms) + vpad))
def broadcast(self, message, *args):
"""Traverse the component hierarchy, calling each component's handler for
the given message, if any."""
Component.broadcast(self, message, *args)
for comp in self._contents:
comp.broadcast(message, *args)

46
GUI/Generic/GControls.py Normal file
View File

@ -0,0 +1,46 @@
#
# Python GUI - Controls - Generic
#
from GUI.Properties import overridable_property
from GUI import Component
class Control(Component):
"""Abstract base class for components such as buttons, check
boxes and text entry boxes."""
title = overridable_property('title', "Title of the control.")
value = overridable_property('value', "Value of the control.")
enabled = overridable_property('enabled', "True if user can manipulate the control.")
font = overridable_property('font')
color = overridable_property('color')
just = overridable_property('just', "Justification ('left', 'center' or 'right').")
lines = overridable_property('lines',
"Height of the control measured in lines of the current font.")
tab_stop = overridable_property('tab_stop',
"Whether tab key can navigate into this control.")
_vertical_padding = 0 # Extra height to add when setting 'lines' property
_default_tab_stop = True
_user_tab_stop_override = True
def __init__(self, font = None, lines = None, **kwds):
Component.__init__(self, **kwds)
# If font and lines are both specified, must set font first.
if font:
self.font = font
if lines is not None:
self.lines = lines
def get_lines(self):
return int(round((self.height - self._vertical_padding) / self.font.line_height))
def set_lines(self, num_lines):
self.height = self._calc_height(self.font, num_lines)
def _calc_height(self, font, num_lines = 1):
return num_lines * font.line_height + self._vertical_padding
def _is_targetable(self):
return self.enabled

55
GUI/Generic/GCursors.py Normal file
View File

@ -0,0 +1,55 @@
#--------------------------------------------------------------------------
#
# Python GUI - Cursors - Generic
#
#--------------------------------------------------------------------------
from GUI.Properties import Properties
from GUI.Resources import lookup_resource, find_resource, get_resource
from GUI import Image
def _hotspot_for_resource(resource_name):
path = lookup_resource(resource_name, "hot")
if path:
f = open(path, "rU")
xs, ys = f.readline().split()
return int(xs), int(ys)
else:
return None
class Cursor(Properties):
"""A Cursor is an image representing the mouse pointer.
Constructors:
Cursor(resource_name, hotspot)
Cursor(image, hotspot)
"""
def from_resource(cls, name, hotspot = None, **kwds):
def load(path):
image = Image.from_resource(name, **kwds)
return cls(image, hotspot or _hotspot_for_resource(name))
return get_resource(load, name)
from_resource = classmethod(from_resource)
def __init__(self, spec, hotspot = None):
"""Construct a Cursor from a resource or Image and a hotspot point.
The hotspot defaults to the centre of the image."""
if isinstance(spec, basestring):
self._init_from_resource(spec, hotspot)
else:
self._init_from_image(spec, hotspot)
def _init_from_resource(self, resource_name, hotspot):
image = Image(file = find_resource(resource_name))
if not hotspot:
hotspot = _hotspot_for_resource(resource_name)
self._init_from_image(image, hotspot)
def _init_from_image(self, image, hotspot):
if not hotspot:
width, height = image.size
hotspot = (width // 2, height // 2)
self._init_from_image_and_hotspot(image, hotspot)

77
GUI/Generic/GDialogs.py Normal file
View File

@ -0,0 +1,77 @@
#
# Python GUI - Dialogs - Generic
#
from GUI import Globals
from GUI.Properties import overridable_property
from GUI.Actions import ActionBase, action_property
from GUI import Window
class Dialog(Window, ActionBase):
_default_keys = "\r"
_cancel_keys = "\x1b"
# default_button = overridable_property('default_button',
# "Button to be activated by the default key.")
#
# cancel_button = overridable_property('cancel_button',
# "Button to be activated by the cancel key.")
#
# _default_button = None
# _cancel_button = None
default_action = action_property('default_action',
"Action to perform when Return or Enter is pressed.")
cancel_action = action_property('cancel_action',
"Action to perform when Escape is pressed.")
_default_action = 'ok'
_cancel_action ='cancel'
def __init__(self, style = 'nonmodal_dialog',
closable = 0, zoomable = 0, resizable = 0, **kwds):
if 'title' not in kwds:
kwds['title'] = Globals.application_name
Window.__init__(self, style = style,
closable = closable, zoomable = zoomable, resizable = resizable,
**kwds)
# def get_default_button(self):
# return self._default_button
#
# def set_default_button(self, button):
# self._default_button = button
# if button:
# button.style = 'default'
#
# def get_cancel_button(self):
# return self._cancel_button
#
# def set_cancel_button(self, button):
# self._cancel_button = button
# if button:
# button.style = 'cancel'
def key_down(self, event):
#print "GDialog.key_down:", repr(event.char) ###
c = event.char
if c:
if c in self._default_keys:
self.do_default_action()
return
elif c in self._cancel_keys:
self.do_cancel_action()
return
Window.key_down(self, event)
def do_default_action(self):
self.do_named_action('default_action')
def do_cancel_action(self):
self.do_named_action('cancel_action')
# def _activate_button(self, button):
# if button:
# button.activate()

View File

@ -0,0 +1,62 @@
#--------------------------------------------------------------------
#
# PyGUI - DrawableContainer - Generic
#
#--------------------------------------------------------------------
from GUI.Geometry import rect_sized
from GUI import Container
from GUI import ViewBase
from GUI.Printing import Printable
default_size = (100, 100)
class DrawableContainer(ViewBase, Container, Printable):
#
# Construction and destruction
#
def __init__(self, **kwds):
Container.__init__(self, **kwds)
ViewBase.__init__(self)
def destroy(self):
ViewBase.destroy(self)
Container.destroy(self)
def setup_menus(self, m):
ViewBase.setup_menus(self, m)
Container.setup_menus(self, m)
def viewed_rect(self):
"""Return the rectangle in local coordinates bounding the currently
visible part of the extent."""
return rect_sized((0, 0), self.size)
def with_canvas(self, proc):
"""Call the procedure with a canvas suitable for drawing in this
view. The canvas is only valid for the duration of the call, and
should not be retained beyond it."""
raise NotImplementedError
def update(self):
"""Redraw invalidated regions immediately, without waiting for a
return to the event loop."""
raise NotImplementedError
def get_print_extent(self):
return self.content_size
def _draw_background(self, canvas, clip_rect):
return clip_rect
#
# Callbacks
#
def draw(self, canvas, rect):
"""Called when the view needs to be drawn. The rect is the bounding
rectangle of the region needing to be drawn. The default implementation
does nothing."""
pass

View File

@ -0,0 +1,28 @@
#
# PyGUI - Edit command handling - Generic
#
from GUI import application
class EditCmdHandler(object):
# Mixin for objects that implement the standard editing commands.
_may_be_password = False
def setup_menus(self, m):
selbeg, selend = self.selection
anysel = selbeg < selend
anyscrap = application().query_clipboard()
passwd = self._may_be_password and self.password
m.cut_cmd.enabled = anysel and not passwd
m.copy_cmd.enabled = anysel and not passwd
m.paste_cmd.enabled = anyscrap
m.clear_cmd.enabled = anysel
m.select_all_cmd.enabled = True
def select_all_cmd(self):
self.select_all()
def select_all(self):
self.selection = (0, self.get_text_length())

71
GUI/Generic/GEvents.py Normal file
View File

@ -0,0 +1,71 @@
#
# Python GUI - Events - Generic
#
class Event(object):
"""An input event.
Attributes:
kind Type of event. One of 'mouse_down', 'mouse_up', 'key_down',
'key_up'.
global_position Position of mouse in screen coordinates at the time of the event.
position For mouse events, position in local coordinates of the View that
was the target of this event. Undefined for other event types.
time Time of event, in platform-dependent units.
button Button identifier for mouse down/up events.
num_clicks Number of consecutive clicks within double-click time.
char For key events, an ASCII character. Undefined for other event types.
key For non-printing keys, a value identifying the key. Undefined for other event types.
auto True if key-down event is an autorepeat (not supported on all platforms).
Platform-independent modifiers (boolean):
shift The Shift key.
control The Control key.
option The additional modifier key.
extend_contig The contiguous selection extension modifier key.
extend_noncontig The noncontiguous selection extension modifier key.
"""
kind = None
global_position = None
position = None
time = None
button = None
num_clicks = 0
char = None
key = None
auto = False
shift = False
control = False
option = False
extend_contig = False
extend_noncontig = False
delta = (0, 0)
_keycode = 0 # Platform-dependent key code
_originator = None # Component to which originally delivered by platform
_not_handled = False # Reached default event method of originating component
def position_in(self, view):
"""Return the position of this event in the coordinate system
of the specified view."""
return view.global_to_local(self.global_position)
def __str__(self):
return "<GUI.Event: %s global:%s local:%s time:%s clicks:%s char:%r" \
" key:%s shift:%s control:%s option:%s extend_contig:%s" \
" extend_noncontig:%s auto:%s%s>" \
% (self.kind, self.global_position, self.position, self.time,
self.num_clicks, self.char, self.key, self.shift, self.control,
self.option, self.extend_contig, self.extend_noncontig, self.auto,
self._platform_modifiers_str())

193
GUI/Generic/GFiles.py Normal file
View File

@ -0,0 +1,193 @@
#
# Python GUI - File references and types - Generic
#
# Classes for dealing with file references and file types
# in as platform-independent a manner as possible.
#
# In this view of things, a file reference consists
# of two parts:
#
# 1) A directory reference, whose nature is
# platform-dependent,
#
# 2) A name.
#
import os
from GUI.Properties import Properties, overridable_property
class FileRef(Properties):
"""A FileRef represents a file system object in a platform-independent way.
It consists of two parts, a directory specification and the name of an
object within that directory. The directory specification always refers
to an existing directory, but the named object may or may not exist.
Constructors:
FileRef(dir = DirRef or path, name = string)
FileRef(path = string)
"""
dir = overridable_property('dir', "DirRef representing the parent directory.")
name = overridable_property('name', "Name of the object within the parent directory.")
path = overridable_property('path', "Full pathname of the object.")
_dir = None # DirRef representing the parent directory
_name = None # Name, including type suffix if any
#
# Constructor
#
def __init__(self, dir = None, name = None, path = None):
if dir and name and not path:
if not isinstance(dir, DirRef):
dir = DirRef(dir)
elif path and not (dir or name):
dirpath, name = os.path.split(path)
dir = DirRef(path = dirpath)
else:
raise TypeError("Invalid argument combination to FileRef constructor")
self._dir = dir
self._name = name
#
# Properties
#
def get_dir(self):
return self._dir
def get_name(self):
"Return the name of the file."
return self._name
def get_path(self):
return os.path.join(self._dir.path, self._name)
#
# Methods
#
def open(self, mode, file_type = None):
"""Open as a file with the given mode and return a file object. On
platforms which have file-type metadata (e.g. Macintosh), if the
mode contains 'w' and a file_type is specified, the newly-created
file will be given the specified type."""
f = open(self.path, mode)
if "w" in mode and file_type:
self._set_type(file_type)
return f
def mkdir(self):
"""Create a directory with the name and parent directory specified
by this FileRef. Returns a DirRef for the created directory."""
return DirRef(os.mkdir(self.path))
def _set_type(self, file_type):
# Platforms which have file-type metadata (e.g. Macintosh) use this
# to set the type of a file.
pass
def __str__(self):
return "FileRef(%r,%r)" % (self.dir.path, self.name)
#-------------------------------------------------------------------------
class DirRef(Properties):
"""A DirRef is an object representing a directory in the
file system. Its representation is completely platform
dependent.
Constructor:
DirRef(path = string)
"""
_path = None
path = overridable_property('path', "Full pathname of the directory.")
def __init__(self, path):
self._path = path
def get_path(self):
return self._path
def __str__(self):
return "DirRef(%r)" % self.path
#-------------------------------------------------------------------------
class FileType(Properties):
"""A FileType is a multi-platform representation of a file type."""
_name = None
_suffix = None
_mac_creator = None
_mac_type = None
_mac_force_suffix = True
name = overridable_property('name', "Human-readable description of the file type")
suffix = overridable_property('suffix', "Filename suffix (without dot)")
mac_creator = overridable_property('mac_creator', "Macintosh 4-character creator code")
mac_type = overridable_property('mac_type', "Macintosh 4-character type code")
mac_force_suffix = overridable_property('mac_force_suffix', "Enforce filename suffix on MacOSX")
def get_name(self):
return self._name
def set_name(self, x):
self._name = x
def get_suffix(self):
return self._suffix
def set_suffix(self, x):
self._suffix = x
def get_mac_creator(self):
return self._mac_creator
def set_mac_creator(self, x):
self._mac_creator = x
def get_mac_type(self):
return self._mac_type
def set_mac_type(self, x):
self._mac_type = x
def get_mac_force_suffix(self):
return self._mac_force_suffix
def set_mac_force_suffix(self, x):
self._mac_force_suffix = x
def _matches(self, name, mac_type):
# Return true if the given name or type code matches that of
# this file type.
this_mac_type = self._mac_type
this_suffix = self._suffix
if this_mac_type and mac_type == this_mac_type:
return True
# Allow generic text files to match typeless files for MacOSX
if not this_suffix and this_mac_type == "TEXT" and mac_type == "\0\0\0\0":
return True
if this_suffix and _matches_suffix(name, this_suffix):
return True
return False
def _add_suffix(self, name):
# Force the given name to have the appropriate suffix for this file
# type. Platforms which have other means of representing file types
# (e.g. Macintosh) may override this.
suffix = self._suffix
if suffix and not _matches_suffix(name, suffix):
name = "%s.%s" % (name, suffix)
return name
#-------------------------------------------------------------------------
def _matches_suffix(name, suffix):
# Test case-insensitively whether the given filename has
# the given suffix.
return name.lower().endswith("." + suffix.lower())

93
GUI/Generic/GFonts.py Normal file
View File

@ -0,0 +1,93 @@
#
# Python GUI - Fonts - Generic
#
import sys
from GUI.Properties import overridable_property
class Font(object):
"""A Font object represents a set of characters of a particular
typeface, style and size. Font objects are immutable.
Constructors:
Font(family, size, style)
family = family name
size = size in points
style = a list of 'bold', 'italic'
Properties:
family --> string
size --> number
style --> ['bold', 'italic']
ascent --> number
descent --> number
leading --> number
height --> number
cap_height --> number
x_height --> number
line_height --> number
"""
family = overridable_property('family', "Family name ('Times', 'Helvetica', etc.)")
size = overridable_property('size', "Size in points")
style = overridable_property('style', "A list of 'bold', 'italic'")
ascent = overridable_property('ascent', "Distance from baseline to top of highest character")
descent = overridable_property('descent', "Distance from baseline to bottom of lowest character")
height = overridable_property('height', "Sum of ascent and descent")
cap_height = overridable_property('cap_height', "Height above baseline of capital letters")
x_height = overridable_property('x_height', "Height above baseline of lowercase letters without ascenders")
leading = overridable_property('leading', "Recommended extra space between lines")
line_height = overridable_property('line_height', "Recommended distance between baselines")
def get_cap_height(self):
# Approximation for platforms not supporting this
return self.ascent
def get_x_height(self):
# Approximation for platforms not supporting this
return self.ascent - self.descent
def get_leading(self):
return self.line_height - self.height
def but(self, family = None, size = None, style = None,
style_includes = None, style_excludes = None):
"""Return a new Font that is the same as this one except for the
specified characteristics."""
if not family:
family = self.family
if not size:
size = self.size
if style is None:
style = self.style
style = style[:]
if style_includes:
for item in style_includes:
style.append(item)
if style_excludes:
for item in style_excludes:
if item in style:
style.remove(item)
return self.__class__(family, size, style)
def width(self, s, start = 0, end = None):
"""width(s [,start [,end ]])
The width of the specified part of the given string in this font."""
if start or end is not None:
if end is None:
end = len(s)
s = s[start:end]
return self._width(s)
def _width(self, s):
raise NotImplementedError
def x_to_pos(self, s, x):
"""Given a number of pixels measured from the left of
the given string, returns the nearest inter-character position.
Returns 0 if x is negative, and len(string) if x is beyond the
right end of the string."""
raise NotImplementedError
def __str__(self):
return "Font(%r,%g,%s)" % (self.family, self.size, self.style)

10
GUI/Generic/GFrames.py Normal file
View File

@ -0,0 +1,10 @@
#
# Python GUI - Frames - Generic
#
from GUI import Container
class Frame(Container):
"""A Frame is a general-purpose instantiable subclass of Container."""
_default_size = (100, 100)

162
GUI/Generic/GGLConfig.py Normal file
View File

@ -0,0 +1,162 @@
#
# PyGUI - OpenGL Pixel Formats - Generic
#
from GUI.Properties import Properties, overridable_property
class GLConfig(Properties):
"""Class holding the attributes of an OpenGL context configuration."""
# NOTE: When adding a property here, also add it to
# _pixel_format_attribute_names below.
double_buffer = overridable_property("double_buffer", "True if context is to be double-buffered.")
alpha = overridable_property("alpha", "True if there is to be an alpha channel.")
color_size = overridable_property("color_size", "Number of bits per colour buffer component.")
alpha_size = overridable_property("alpha_size", "Number of bits per alpha channel component.")
stereo = overridable_property("stereo", "True if stereoscopic context is required.")
aux_buffers = overridable_property("aux_buffers", "Number of auxiliary colour buffers to allocate.")
depth_buffer = overridable_property("depth_buffer", "True if a depth buffer is required.")
depth_size = overridable_property("depth_size", "Number of bits per depth buffer element.")
stencil_buffer = overridable_property("stencil_buffer", "True if a stencil buffer is required.")
stencil_size = overridable_property("stencil_size", "Number of bits per stencil buffer element.")
accum_buffer = overridable_property("accum_buffer", "True if an accumulation buffer is required.")
accum_size = overridable_property("accum_size", "Number of bits per accumulation buffer component.")
multisample = overridable_property("multisample", "True if a multisampled context is required.")
samples_per_pixel = overridable_property("samples_per_pixel", "Number of samples per multisampled pixel.")
_double_buffer = True
_alpha = True
_color_size = 8
_alpha_size = 8
_stereo = False
_aux_buffers = 0
_depth_buffer = True
_depth_size = 32
_stencil_buffer = False
_stencil_size = 8
_accum_buffer = False
_accum_size = 8
_multisample = False
_samples_per_pixel = 4
_pixel_format_attribute_names = (
'double_buffer', 'alpha', 'color_size', 'alpha_size',
'stereo', 'aux_buffers', 'depth_buffer', 'depth_size',
'stencil_buffer', 'stencil_size', 'accum_buffer', 'accum_size',
'multisample', 'samples_per_pixel',
)
def _from_args(cls, config, kwds):
# Extract pixel format arguments from arguments of GLView.__init__
# or GLPixmap.__init__ and return a GLConfig. Used keyword
# arguments are removed from kwds.
pf_kwds = {}
for name in cls._pixel_format_attribute_names:
if name in kwds:
pf_kwds[name] = kwds.pop(name)
if config and pf_kwds:
raise TypeError("Explicit config cannot be used with other configuration keyword arguments")
if not config:
config = cls(**pf_kwds)
return config
_from_args = classmethod(_from_args)
def get_double_buffer(self):
return self._double_buffer
def set_double_buffer(self, x):
self._double_buffer = x
def get_alpha(self):
return self._alpha
def set_alpha(self, x):
self._alpha = x
def get_color_size(self):
return self._color_size
def set_color_size(self, x):
self._color_size = x
def get_alpha_size(self):
return self._alpha_size
def set_alpha_size(self, x):
self._alpha_size = x
def get_stereo(self):
return self._stereo
def set_stereo(self, x):
self._stereo = x
def get_aux_buffers(self):
return self._aux_buffers
def set_aux_buffers(self, x):
self._aux_buffers = x
def get_depth_buffer(self):
return self._depth_buffer
def set_depth_buffer(self, x):
self._depth_buffer = x
def get_depth_size(self):
return self._depth_size
def set_depth_size(self, x):
self._depth_size = x
def get_stencil_buffer(self):
return self._stencil_buffer
def set_stencil_buffer(self, x):
self._stencil_buffer = x
def get_stencil_size(self):
return self._stencil_size
def set_stencil_size(self, x):
self._stencil_size = x
def get_accum_buffer(self):
return self._accum_buffer
def set_accum_buffer(self, x):
self._accum_buffer = x
def get_accum_size(self):
return self._accum_size
def set_accum_size(self, x):
self._accum_size = x
def get_multisample(self):
return self._multisample
def set_multisample(self, x):
self._multisample = x
def get_samples_per_pixel(self):
return self._samples_per_pixel
def set_samples_per_pixel(self, x):
self._samples_per_pixel = x
def supported(self):
"""Determine whether the combination of attributes requested by this configuration
can be satisfied. If successful, a new GLConfig object is returned whose
attributes reflect those actually allocated. Otherwise, a GLConfigError is
raised."""
raise NotImplementedError
#------------------------------------------------------------------------------
class GLConfigError(ValueError):
def __init__(self, msg = "OpenGL configuration not available"):
ValueError.__init__(self, msg)

View File

@ -0,0 +1,72 @@
#
# PyGUI - OpenGL Contexts - Generic
#
from GUI.Properties import overridable_property
from GUI.GLShareGroups import ShareGroup
_current_share_group = None
class GLContext(object):
"""Abstract base class for objects having an OpenGL context."""
#
# _share_group ShareGroup
#
share_group = overridable_property('share_group',
"ShareGroup to which this context should belong, or None.")
def __init__(self, share_group):
if not share_group:
share_group = ShareGroup()
self._share_group = share_group
if share_group:
share_group._add(self)
def destroy(self):
pass
def init_context(self):
"""This method is called once after the associated OpenGL context
is created. When called, this object's OpenGL context is the current
context and the viewport is set to (0, 0, width, height). This method
may be used to establish any desired initial OpenGL state."""
pass
def get_share_group(self):
return self._share_group
def _get_shared_context(self):
"""Return another arbitrarily-chosen member of the share group of this
context, or None if this context has no share group or there are no
other members."""
return self._share_group._some_member(exclude = self)
def with_context(self, proc, flush = False):
"""The proc should be a callable object of no arguments. Calls
the proc with the associated OpenGL context as the current context.
If flush is true, after calling proc, a glFlush followed by a
buffer flush or swap is performed as appropriate."""
self._with_context(proc, flush)
def _with_context(self, proc, flush):
# Subclasses override this to implement with_context.
# Should call _with_share_group(proc).
# Signature can be changed if with_context is overridden to match.
raise NotImplementedError
def _with_share_group(self, proc):
global _current_share_group
old_share_group = _current_share_group
_current_share_group = self._share_group
try:
proc()
finally:
_current_share_group = old_share_group
def current_share_group():
group = _current_share_group
if not group:
raise ValueError("No current PyGUI OpenGL context")
return group

View File

@ -0,0 +1,163 @@
#
# PyGUI - OpenGL Pixel Formats - Generic
#
from GUI.Properties import Properties, overridable_property
class GLPixelFormat(Properties):
"""Class holding the attributes of an OpenGL pixel format."""
# NOTE: When adding a property here, also add it to
# _pixel_format_attribute_names below.
double_buffer = overridable_property("double_buffer", "True if context is to be double-buffered.")
alpha = overridable_property("alpha", "True if there is to be an alpha channel.")
color_size = overridable_property("color_size", "Number of bits per colour buffer component.")
alpha_size = overridable_property("alpha_size", "Number of bits per alpha channel component.")
stereo = overridable_property("stereo", "True if stereoscopic context is required.")
aux_buffers = overridable_property("aux_buffers", "Number of auxiliary colour buffers to allocate.")
depth_buffer = overridable_property("depth_buffer", "True if a depth buffer is required.")
depth_size = overridable_property("depth_size", "Number of bits per depth buffer element.")
stencil_buffer = overridable_property("stencil_buffer", "True if a stencil buffer is required.")
stencil_size = overridable_property("stencil_size", "Number of bits per stencil buffer element.")
accum_buffer = overridable_property("accum_buffer", "True if an accumulation buffer is required.")
accum_size = overridable_property("accum_size", "Number of bits per accumulation buffer component.")
multisample = overridable_property("multisample", "True if a multisampled context is required.")
samples_per_pixel = overridable_property("samples_per_pixel", "Number of samples per multisampled pixel.")
_double_buffer = True
_alpha = True
_color_size = 8
_alpha_size = 8
_stereo = False
_aux_buffers = 0
_depth_buffer = True
_depth_size = 32
_stencil_buffer = False
_stencil_size = 8
_accum_buffer = False
_accum_size = 8
_multisample = False
_samples_per_pixel = False
_pixel_format_attribute_names = (
'double_buffer', 'alpha', 'color_size', 'alpha_size',
'stereo', 'aux_buffers', 'depth_buffer', 'depth_size',
'stencil_buffer', 'stencil_size', 'accum_buffer', 'accum_size',
'multisample', 'samples_per_pixel',
)
def _from_args(cls, pixel_format, kwds):
# Extract pixel format arguments from arguments of GLView.__init__
# or GLPixmap.__init__ and return a GLPixelFormat. Used keyword
# arguments are removed from kwds.
pf_kwds = {}
for name in cls._pixel_format_attribute_names:
if name in kwds:
pf_kwds[name] = kwds.pop(name)
if pixel_format and pf_kwds:
raise TypeError("Explicit pixel_format cannot be used with other pixel format keyword arguments")
if not pixel_format:
pixel_format = cls(**pf_kwds)
return pixel_format
_from_args = classmethod(_from_args)
def get_double_buffer(self):
return self._double_buffer
def set_double_buffer(self, x):
self._double_buffer = x
def get_alpha(self):
return self._alpha
def set_alpha(self, x):
self._alpha = x
def get_color_size(self):
return self._color_size
def set_color_size(self, x):
self._color_size = x
def get_alpha_size(self):
return self._alpha_size
def set_alpha_size(self, x):
self._alpha_size = x
def get_stereo(self):
return self._stereo
def set_stereo(self, x):
self._stereo = x
def get_aux_buffers(self):
return self._aux_buffers
def set_aux_buffers(self, x):
self._aux_buffers = x
def get_depth_buffer(self):
return self._depth_buffer
def set_depth_buffer(self, x):
self._depth_buffer = x
def get_depth_size(self):
return self._depth_size
def set_depth_size(self, x):
self._depth_size = x
def get_stencil_buffer(self):
return self._stencil_buffer
def set_stencil_buffer(self, x):
self._stencil_buffer = x
def get_stencil_size(self):
return self._stencil_size
def set_stencil_size(self, x):
self._stencil_size = x
def get_accum_buffer(self):
return self._accum_buffer
def set_accum_buffer(self, x):
self._accum_buffer = x
def get_accum_size(self):
return self._accum_size
def set_accum_size(self, x):
self._accum_size = x
def get_multisample(self):
return self._multisample
def set_multisample(self, x):
self._multisample = x
def get_samples_per_pixel(self):
return self._samples_per_pixel
def set_samples_per_pixel(self, x):
self._samples_per_pixel = x
def supported(self):
"""Determine whether the combination of attributes requested by this pixel format
can be satisfied. If successful, a new GLPixelFormat object is returned whose
attributes reflect those actually allocated. Otherwise, a GLPixelFormatError is
raised."""
raise NotImplementedError
#------------------------------------------------------------------------------
class GLPixelFormatError(ValueError):
def __init__(self):
ValueError.__init__(self,
"OpenGL pixel format attribute request cannot be satisfied")

23
GUI/Generic/GGLPixmaps.py Normal file
View File

@ -0,0 +1,23 @@
#
# PyGUI - OpenGL Pixmap - Generic
#
from OpenGL.GL import glViewport
from GUI import ImageBase
from GUI.GLContexts import GLContext
class GLPixmap(ImageBase, GLContext):
"""An offscreen OpenGL drawing area.
Constructors:
GLPixmap(width, height, share = None, config_attr = value...)
GLPixmap(width, height, config, share = None)
"""
def destroy(self):
GLContext.destroy(self)
def _init_context(self):
width, height = self.size
glViewport(0, 0, int(width), int(height))
self.init_context()

122
GUI/Generic/GGLTextures.py Normal file
View File

@ -0,0 +1,122 @@
#
# PyGUI - OpenGL Textures - Generic
#
from weakref import WeakKeyDictionary
from OpenGL.GL import glGenTextures, glBindTexture, glDeleteTextures, \
glTexImage2D, GL_TEXTURE_2D, GL_RGBA
from OpenGL.GLU import gluBuild2DMipmaps
from GUI.GGLContexts import current_share_group
from GUI.GLDisplayLists import call_when_not_compiling_display_list
#----------------------------------------------------------------------
class TextureIdMap(WeakKeyDictionary):
def __del__(self):
#print "GL.TextureIdMap.__del__:", self ###
def free_texture():
glDeleteTextures([gl_id])
for share_group, gl_id in self.items():
context = share_group._some_member()
if context:
#print "...freeing texture id", gl_id, "for", share_group, "using", context ###
context.with_context(free_texture)
#----------------------------------------------------------------------
class Texture(object):
"""This class encapsulates an OpenGL texture and maintains a
representation of it for each OpenGL context with which it is used.
Allocation and maintentance of texture numbers is handled automatically.
Constructor:
Texture(texture_type)
where texture_type is the appropriate GL constant for the type
of texture (GL_TEXTURE_2D etc.)
"""
#
# _gl_type int GL_TEXTURE_2D, etc.
# _gl_id ShareGroup -> int Mapping from OpenGL share group to texture number
def __init__(self, texture_type):
self._gl_type = texture_type
self._gl_id = TextureIdMap()
def deallocate(self):
"""Deallocate any OpenGL resources that have been allocated for this
texture in any context."""
self._gl_id.__del__()
def bind(self):
"""Makes this texture the current texture for the current context
by calling glBindTexture. If this texture has not previously been
used with the current context, do_setup() is called to allocate
and initialise a representation of the texture."""
gl_id = self.gl_id()
glBindTexture(self._gl_type, gl_id)
def gl_id(self):
"""Returns the OpenGL texture number corresponding to this texture
in the current context. May trigger allocation of a new texture and
a call to do_setup(). Does not bind the texture, unless a new texture
is allocated, in which case the current texture binding may be changed
as a side effect."""
share_group = current_share_group()
gl_id = self._gl_id.get(share_group)
if gl_id is None:
gl_id = glGenTextures(1)
#print "GLTexture: assigned id %d for %s in share group %s" % (
# gl_id, self, share_group) ###
self._gl_id[share_group] = gl_id
call_when_not_compiling_display_list(lambda: self._setup(gl_id))
return gl_id
def _setup(self, gl_id):
glBindTexture(self._gl_type, gl_id)
self.do_setup()
def do_setup(self):
"""Subclasses should override this to make the necessary OpenGL
calls to initialise the texture, assuming that glBindTexture has
already been called."""
raise NotImplementedError
def gl_tex_image_2d(self, image, target = GL_TEXTURE_2D, internal_format = GL_RGBA,
border = False, with_mipmaps = False):
"""Load the currently bound texture with data from an image, with automatic
scaling to power-of-2 size and optional mipmap generation."""
border = bool(border)
if border and with_mipmaps:
raise ValueError("Bordered texture cannot have mipmaps")
b2 = 2 * border
width, height = image.size
twidth = pow2up(width - b2) + b2
theight = pow2up(height - b2) + b2
#print "GUI.GGLTextures.Texture.gl_tex_image_2d: before scaling: size =", (width, height) ###
if width <> twidth or height <> theight:
#print "GUI.GGLTextures.Texture.gl_tex_image_2d: scaling image to size", (twidth, theight) ###
from Pixmaps import Pixmap
image2 = Pixmap(twidth, theight)
def scale(canvas):
image.draw(canvas, (0, 0, width, height), (0, 0, twidth, theight))
image2.with_canvas(scale)
image = image2
format, type, data = self._gl_get_texture_data(image)
if with_mipmaps:
#print "GUI.GGLTextures.Texture.gl_tex_image_2d: loading mipmaps" ###
gluBuild2DMipmaps(target, internal_format, twidth, theight,
format, type, data)
else:
#print "GUI.GGLTextures.Texture.gl_tex_image_2d: loading texture" ###
glTexImage2D(target, 0, internal_format, twidth, theight, border,
format, type, data)
#----------------------------------------------------------------------
def pow2up(size):
# Round size up to a power of 2
psize = 1
while psize < size:
psize <<= 1
return psize

73
GUI/Generic/GGLViews.py Normal file
View File

@ -0,0 +1,73 @@
#
# PyGUI - OpenGL View - Generic
#
from OpenGL.GL import glViewport, glMatrixMode, glLoadIdentity, \
GL_PROJECTION, GL_MODELVIEW
from GUI import Component
from GUI import ViewBase
from GUI.GLContexts import GLContext
class GLError(StandardError):
pass
class GLView(ViewBase, Component, GLContext):
"""A GLView is a Component providing an OpenGL 3D display area.
Constructors:
GLView(config_attr = value..., share = None)
GLView(config, share = None)
"""
_default_size = (100, 100)
def __init__(self, **kwds):
Component.__init__(self, **kwds)
ViewBase.__init__(self)
def destroy(self):
ViewBase.destroy(self)
Component.destroy(self)
def _render(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
self.render()
def render(self):
"""This method is called when the contents of the view needs to
be redrawn, with the view's OpenGL context as the current context.
The modelview matrix has been selected as the current matrix and
set to an identity matrix. After calling this method, buffers will
be automatically swapped or drawing flushed as appropriate."""
pass
def viewport_changed(self):
"""This method is called when the view's size has changed, with
the view's OpenGL context as the current context, and the OpenGL
viewport set to (0, 0, width, height). The default implementation
loads an identity projection matrix and calls init_projection()."""
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
self.init_projection()
def init_projection(self):
"""This method is called to establish the projection whenever the
viewport changes. The projection matrix has been selected as the
current matrix and set to an identity matrix."""
pass
def update(self):
"""Redraws the contents of the view immediately, without waiting
for a return to the event loop."""
self.with_context(self.render, flush = True)
def _init_context(self):
self.init_context()
self._update_viewport()
def _update_viewport(self):
width, height = self.size
glViewport(0, 0, int(width), int(height))
self.viewport_changed()

73
GUI/Generic/GGeometry.py Normal file
View File

@ -0,0 +1,73 @@
#
# Python GUI - Point and rectangle utilities - Generic
#
def add_pt((x1, y1), (x2, y2)):
return (x1 + x2), (y1 + y2)
def sub_pt((x1, y1), (x2, y2)):
return (x1 - x2), (y1 - y2)
def rect_sized((l, t), (w, h)):
return (l, t, l + w, t + h)
def rect_left(r):
return r[0]
def rect_top(r):
return r[1]
def rect_right(r):
return r[2]
def rect_bottom(r):
return r[3]
def rect_width(r):
return r[2] - r[0]
def rect_height(r):
return r[3] - r[1]
def rect_topleft(r):
return r[:2]
def rect_botright(r):
return r[2:]
def rect_center((l, t, r, b)):
return ((l + r) // 2, (t + b) // 2)
def rect_size((l, t, r, b)):
return (r - l, b - t)
def union_rect((l1, t1, r1, b1), (l2, t2, r2, b2)):
return (min(l1, l2), min(t1, t2), max(r1, r2), max(b1, b2))
def sect_rect((l1, t1, r1, b1), (l2, t2, r2, b2)):
return (max(l1, l2), max(t1, t2), min(r1, r2), min(b1, b2))
def inset_rect((l, t, r, b), (dx, dy)):
return (l + dx, t + dy, r - dx, b - dy)
def offset_rect((l, t, r, b), (dx, dy)):
return (l + dx, t + dy, r + dx, b + dy)
def offset_rect_neg((l, t, r, b), (dx, dy)):
return (l - dx, t - dy, r - dx, b - dy)
def empty_rect((l, t, r, b)):
return r <= l or b <= t
def pt_in_rect((x, y), (l, t, r, b)):
return l <= x < r and t <= y < b
def rects_intersect((l1, t1, r1, b1), (l2, t2, r2, b2)):
return l1 < r2 and l2 < r1 and t1 < b2 and t2 < b1
def rect_with_center((l, t, r, b), (x, y)):
w = r - l
h = b - t
rl = x - w // 2
rt = y - h // 2
return (rl, rt, rl + w, rt + h)

View File

@ -0,0 +1,26 @@
#
# Python GUI - Image Base - Generic
#
from GUI.Properties import Properties, overridable_property
from GUI.Geometry import rect_sized
class ImageBase(Properties):
"""Abstract base class for Image, Pixmap and GLPixmap."""
width = overridable_property('width', "Width of the image in pixels (read only).")
height = overridable_property('height', "Height of the image in pixels (read only).")
size = overridable_property('size', "Size of the image in pixels (read only).")
bounds = overridable_property('bounds', "Bounding rectangle of the image in pixels (read only).")
def get_size(self):
return (self.width, self.height)
def get_bounds(self):
return rect_sized((0, 0), self.size)
def draw(self, canvas, src_rect, dst_rect):
"""Draw the part of the image specified by src_rect on the given canvas,
scaled to fit within dst_rect."""
raise NotImplementedError

24
GUI/Generic/GImages.py Normal file
View File

@ -0,0 +1,24 @@
#
# Python GUI - Images - Generic
#
from GUI.Files import FileRef
from GUI.Resources import get_resource
from GUI import ImageBase
class Image(ImageBase):
"""Class Image represents an RGB or RGBA image.
Constructors:
Image(file = FileRef or pathname)
"""
def from_resource(cls, name, **kwds):
return get_resource(cls, name, **kwds)
from_resource = classmethod(from_resource)
def __init__(self, file):
if isinstance(file, FileRef):
file = file.path
self._init_from_file(file)

View File

@ -0,0 +1,107 @@
#
# PyGUI - OpenGL Display Lists - Generic
#
from weakref import WeakKeyDictionary
from OpenGL.GL import glGenLists, glNewList, glEndList, glCallList, \
glDeleteLists, GL_COMPILE
from GUI.Properties import Properties, overridable_property
from GUI.GGLContexts import current_share_group
#----------------------------------------------------------------------
class DisplayListIdMap(WeakKeyDictionary):
def __del__(self):
# Delete any display lists that have been allocated for this map.
#print "GL.DisplayListIdMap.__del__:", self ###
def free_display_list():
glDeleteLists(gl_id, 1)
for share_group, gl_id in self.items():
context = share_group._some_member()
if context:
#print "...freeing display list id", gl_id, "for", share_group, "using", context ###
context.with_context(free_display_list)
#----------------------------------------------------------------------
class DisplayList(Properties):
"""This class encapsulates an OpenGL display list and maintains a
representation of it for each OpenGL context with which it is used.
Allocation and maintentance of display list numbers is handled
automatically."""
#
# _gl_id ShareGroup -> int Mapping from OpenGL share group to
# display list number
setup = overridable_property('setup',
"""Function to set up the display list by making the necessary
OpenGL calls, excluding glNewList and glEndList.""")
def __init__(self, setup = None):
self._gl_id = DisplayListIdMap()
self._setup = setup
def deallocate(self):
"""Deallocate any OpenGL resources that have been allocated for this
display list in any context."""
self._gl_id.__del__()
def get_setup(self):
return self._setup
def set_setup(self, x):
self._setup = x
def call(self):
"""Calls the display list using glCallList(). If this display list
has not previously been used with the current context, allocates
a display list number and arranges for do_setup() to be called
to compile a representation of the display list."""
share_group = current_share_group()
gl_id = self._gl_id.get(share_group)
if gl_id is None:
gl_id = glGenLists(1)
#print "GLDisplayList: assigned id %d for %s in share group %s" % (
# gl_id, self, share_group) ###
self._gl_id[share_group] = gl_id
call_when_not_compiling_display_list(lambda: self._compile(gl_id))
glCallList(gl_id)
def _compile(self, gl_id):
global compiling_display_list
compiling_display_list = True
glNewList(gl_id, GL_COMPILE)
try:
self.do_setup()
finally:
glEndList()
compiling_display_list = False
def do_setup(self):
"""Make all the necessary OpenGL calls to compile the display list,
except for glNewList() and glEndList() which will be called automatically
before and after. The default implementation calls the 'setup' property."""
setup = self._setup
if setup:
setup()
else:
raise NotImplementedError(
"No setup function or do_setup method for GL.DisplayList")
compiling_display_list = False
pending_functions = []
def call_when_not_compiling_display_list(func):
#print "GLDisplayLists: entering call_when_not_compiling_display_list" ###
if compiling_display_list:
#print "GLDisplayLists: deferring", func ###
pending_functions.append(func)
else:
#print "GLDisplayLists: immediately calling", func ###
func()
while pending_functions:
#print "GLDisplayLists: calling deferred", func ###
pending_functions.pop()()
#print "GLDisplayLists: exiting call_when_not_compiling_display_list" ###

View File

@ -0,0 +1,29 @@
#
# PyGUI - OpenGL Context Sharing - Generic
#
from weakref import WeakKeyDictionary
class ShareGroup(object):
"""Object representing a shared texture and display list
namespace for OpenGL contexts."""
def __init__(self):
self.contexts = WeakKeyDictionary()
def __contains__(self, context):
"Test whether a GLView or GLPixmap is a member of this share group."
return context in self.contexts
def __iter__(self):
"Return an iterator over the members of this share group."
return iter(self.contexts)
def _add(self, context):
self.contexts[context] = 1
def _some_member(self, exclude = None):
for member in self.contexts:
if member is not exclude:
return member
return None

14
GUI/Generic/GLabels.py Normal file
View File

@ -0,0 +1,14 @@
#
# Python GUI - Labels - Generic
#
from GUI.Properties import overridable_property
from GUI import Control
class Label(Control):
"""A piece of static text for labelling items in a window."""
_default_tab_stop = False
text = overridable_property('text')

View File

@ -0,0 +1,66 @@
#--------------------------------------------------------------
#
# PyGUI - Pop-up list control - Generic
#
#--------------------------------------------------------------
from GUI.Properties import overridable_property
from GUI.Actions import Action
from GUI import Control, application
class ListButton(Control, Action):
"""A button that displays a value and provides a pop-up or
pull-down list of choices."""
titles = overridable_property('titles',
"List of item title strings")
values = overridable_property('values',
"List of values corresponding to tiles, or None to use item index as value")
def _extract_initial_items(self, kwds):
titles = kwds.pop('titles', None) or []
values = kwds.pop('values', None)
return titles, values
def get_titles(self):
return self._titles
def set_titles(self, x):
self._titles = x
self._update_items()
def get_values(self):
return self._values
def set_values(self, x):
self._values = x
def get_value(self):
i = self._get_selected_index()
if i >= 0:
values = self.values
if values:
return values[i]
else:
return i
def set_value(self, value):
values = self.values
if values:
try:
i = values.index(value)
except ValueError:
i = -1
else:
if value is None:
i = -1
else:
i = value
self._set_selected_index(i)
def do_action(self):
try:
Action.do_action(self)
except:
application().report_error()

335
GUI/Generic/GMenus.py Normal file
View File

@ -0,0 +1,335 @@
#----------------------------------------------------------------------
#
# Python GUI - Menus - Generic
#
#----------------------------------------------------------------------
from GUI import Globals
from GUI.Properties import Properties, overridable_property
#----------------------------------------------------------------------
def search_list_for_key(items, char, shift, option):
for i in xrange(len(items)-1, -1, -1):
result = items[i]._search_for_key(char, shift, option)
if result:
return result
#----------------------------------------------------------------------
class Menu(Properties):
"""Pull-down or pop-up menu class.
Menu(title, item_descriptors)
constructs a menu with the given title and items. Each
item_descriptor is of the form
"-"
for a separator,
("text/key", 'command_name')
for a single menu item, or
(["text/key", ...], 'command_name')
for an indexed item group. An indexed group is a group
of items sharing the same command name and distinguished
by an integer index. Items can be added to and removed
from the group dynamically, to implement e.g. a font
menu or windows menu.
The "key" part of the item descriptor (which is optional)
specifies the keyboard equivalent. It should consist of
a single character together with the following optional
modifiers:
^ representing the Shift key
@ representing the Alt or Option key
"""
title = overridable_property('title', "Title string appearing in menu bar")
special = overridable_property('special', "Menu appears at right end of menu bar")
_flat_items = None
def __init__(self, title, items, special = False, substitutions = {}, **kwds):
self._title = title
self._items = []
self._special = special
Properties.__init__(self, **kwds)
self.extend(items, substitutions)
def get_title(self):
return self._title
def get_special(self):
return self._special
def item_with_command(self, cmd):
for item in self._items:
if item._command_name == cmd:
return item
return None
def append(self, item, substitutions = {}):
items = self._items
item = self._make_item(item, substitutions)
if not (items and isinstance(item, MenuSeparator)
and isinstance(items[-1], MenuSeparator)):
items.append(item)
def extend(self, items, substitutions = {}):
for item in items:
self.append(item, substitutions)
def _make_item(self, item, substitutions):
if isinstance(item, MenuItem):
return item
elif item == "-":
return _menu_separator
else:
(text, cmd) = item
if isinstance(text, basestring):
return SingleMenuItem(text, cmd, substitutions)
else:
return MenuItemGroup(text, cmd)
def _command_and_args_for_item(self, item_num):
i = 1
for item in self._items:
n = item._num_subitems()
if item_num < i + n:
return item._command_and_args_for_subitem(item_num - i)
i += n
return '', ()
def _update_platform_menu(self):
# Called during menu setup after items have been enabled/checked.
# Generic implementation rebuilds the whole menu from scratch.
# Implementations may override this to be more elegant.
self._rebuild_platform_menu()
def _rebuild_platform_menu(self):
self._clear_platform_menu()
for item in self._items:
item._add_to_platform_menu(self)
def _search_for_key(self, char, shift, option):
return search_list_for_key(self._items, char, shift, option)
def _get_flat_items(self):
flat = self._flat_items
if flat is None:
flat = []
for item in self._items:
item._collect_flat_items(flat)
self._flat_items = flat
return flat
def _get_flat_item(self, i):
return self._get_flat_items()[i]
#----------------------------------------------------------------------
class MenuItem(Properties):
# Internal class representing a menu item, group or separator.
#
# _command_name string Internal command name
def _num_subitems(self):
return 1
def _split_text(self, text):
# Split menu text into label and key combination.
if "/" in text:
return text.split("/")
else:
return text, ""
def _name(self):
return self._label.replace("<app>", Globals.application_name)
def _collect_flat_items(self, result):
result.append(self)
#----------------------------------------------------------------------
class MenuSeparator(MenuItem):
# Internal class representing a menu separator.
_command_name = ''
def _add_to_platform_menu(self, menu):
menu._add_separator_to_platform_menu()
def _search_for_key(self, char, shift, option):
pass
#----------------------------------------------------------------------
class SingleMenuItem(MenuItem):
"""Class representing a menu item.
Properties:
enabled boolean
checked boolean
"""
enabled = 0
checked = 0
_key = None
_shift = 0
_option = 0
#_index = None
def __init__(self, text, cmd, substitutions = {}):
label1, keycomb1 = self._split_text(text)
label2, keycomb2 = self._split_text(substitutions.get(cmd, ""))
self._label = label2 or label1
keycomb = keycomb2 or keycomb1
for c in keycomb:
if c == '^':
self._shift = 1
elif c == '@':
self._option = 1
else:
self._key = c.upper()
self._command_name = cmd
def __str__(self):
return "<SingleMenuItem %r %r Sh:%s Op:%s En:%s>" % (
self._label, self._key, self._shift, self._option, self.enabled)
def _add_to_platform_menu(self, menu):
menu._add_item_to_platform_menu(self, self._name(), self._command_name)
def _command_and_args_for_subitem(self, i):
return self._command_name, ()
def _search_for_key(self, char, shift, option):
if self._matches_key(char, shift, option):
return self._command_name
def _matches_key(self, char, shift, option):
return self._key == char and self._shift == shift \
and self._option == option and self.enabled
#----------------------------------------------------------------------
class MenuItemGroup(MenuItem):
"""Class representing a menu item group.
Properties:
enabled <- boolean Assigning to these changes the corresponding
checked <- boolean property of all the group's items.
Operators:
group[index] -> MenuItem
Methods:
set_items(["text/key", ...])
Replaces all the items in the group by the specified items.
"""
enabled = overridable_property('enabled')
checked = overridable_property('checked')
def __init__(self, text_list, cmd):
self.set_items(text_list)
self._command_name = cmd
def _num_subitems(self):
return len(self._items)
def _command_and_args_for_subitem(self, i):
return self._command_name, (i,)
def get_enabled(self):
raise AttributeError("'enabled' property of MenuItemGroup is write-only")
def set_enabled(self, state):
for item in self._items:
item.enabled = state
def get_checked(self):
raise AttributeError("'checked' property of MenuItemGroup is write-only")
def set_checked(self, state):
for item in self._items:
item.checked = state
def __getitem__(self, index):
return self._items[index]
def set_items(self, text_list):
self._items = [SingleMenuItem(text, '') for text in text_list]
def _add_to_platform_menu(self, menu):
#for item in self._items:
# item._add_to_platform_menu(menu)
cmd = self._command_name
for index, item in enumerate(self._items):
menu._add_item_to_platform_menu(item, item._name(), cmd, index)
def _search_for_key(self, char, shift, option):
items = self._items
for i in xrange(len(items)-1, -1, -1):
if items[i]._matches_key(char, shift, option):
return (self._command_name, i)
def _collect_flat_items(self, result):
for item in self._items:
item._collect_flat_items(result)
#----------------------------------------------------------------------
_menu_separator = MenuSeparator()
_dummy_menu_item = SingleMenuItem("", '')
#----------------------------------------------------------------------
class MenuState:
"""A MenuState object is used to enable/disable and check/uncheck
menu items, and to add or remove items of indexed groups,
during the menu setup phase of menu command handling.
Each single menu item or item group appears as an attribute of
the MenuState object, with the command name as the attribute name,
allowing operations such as
menu_state.copy_cmd.enabled = 1
menu_state.font_cmd[current_font].checked = 1
The command name may also be used as a mapping index.
Operators:
menu_state.command_name -> MenuItem
menu_state['command_name'] -> MenuItem
"""
def __init__(self, menu_list):
mapping = {}
for menu in menu_list:
for item in menu._items:
cmd = item._command_name
if cmd:
mapping[cmd] = item
self._mapping = mapping
def __getattr__(self, name):
try:
return self._mapping[name]
except KeyError:
if name.startswith("__"):
raise AttributeError, name
return _dummy_menu_item
__getitem__ = __getattr__
def reset(self):
"""Disable and uncheck all items."""
for item in self._mapping.values():
item.enabled = 0
item.checked = None

View File

@ -0,0 +1,27 @@
#
# Python GUI - Mouse trackers - Generic
#
from GUI import application
class MouseTracker(object):
"""Iterator used to track movements of the mouse following a mouse_down
event in a Views. Each call to the next() method returns a mouse_drag
event, except for the last one, which returns a mouse_up event."""
def __init__(self, view):
self._view = view
self._finished = 0
def __iter__(self):
return self
def next(self):
if not self._finished:
event = self._next_mouse_event()
event.position = event.position_in(self._view)
if event.kind == 'mouse_up':
self._finished = 1
return event
else:
raise StopIteration

20
GUI/Generic/GPixmaps.py Normal file
View File

@ -0,0 +1,20 @@
#
# Python GUI - Pixmap - Generic
#
from GUI import ImageBase
class Pixmap(ImageBase):
"""A Pixmap is an offscreen area that can be used both as a
destination for drawing and a source of image data for drawing
in a View or another Pixmap.
Constructor:
Pixmap(width, height)
"""
def with_canvas(self, proc):
"""Call the given procedure with a canvas suitable for drawing on
this Pixmap. The canvas is valid only for the duration of the call,
and should not be retained beyond it."""
raise NotImplementedError

167
GUI/Generic/GPrinting.py Normal file
View File

@ -0,0 +1,167 @@
#------------------------------------------------------------------------------
#
# PyGUI - Printing - Generic
#
#------------------------------------------------------------------------------
from __future__ import division
from math import ceil
import cPickle as pickle
from GUI.Properties import overridable_property
from GUI import application
class PageSetup(object):
"""Holder of information specified by the "Page Setup" command."""
paper_name = overridable_property('paper_name')
paper_width = overridable_property('paper_width')
paper_height = overridable_property('paper_height')
left_margin = overridable_property('left_margin')
top_margin = overridable_property('top_margin')
right_margin = overridable_property('right_margin')
bottom_margin = overridable_property('bottom_margin')
orientation = overridable_property('orientation')
paper_size = overridable_property('paper_size')
margins = overridable_property('margins')
page_width = overridable_property('page_width')
page_height = overridable_property('page_height')
page_size = overridable_property('page_size')
page_rect = overridable_property('page_rect')
printable_rect = overridable_property('printable_rect') # May not work
printer_name = overridable_property('printer_name')
_pickle_attributes = ['paper_name', 'paper_size', 'margins',
'printer_name', 'orientation']
def __getstate__(self):
state = {}
for name in self._pickle_attributes:
state[name] = getattr(self, name)
return state
def __setstate__(self, state):
for name, value in state.iteritems():
setattr(self, name, value)
def from_string(s):
"""Restore a pickled PageSetup object from a string."""
return pickle.loads(s)
from_string = staticmethod(from_string)
def to_string(self):
"""Pickle the PageSetup object and return it as a string."""
return pickle.dumps(self, 2)
def get_paper_size(self):
return self.paper_width, self.paper_height
def set_paper_size(self, x):
self.paper_width, self.paper_height = x
def get_margins(self):
return self.left_margin, self.top_margin, self.right_margin, self.bottom_margin
def set_margins(self, x):
self.left_margin, self.top_margin, self.right_margin, self.bottom_margin = x
def get_page_width(self):
return self.paper_width - self.left_margin - self.right_margin
def get_page_height(self):
return self.paper_height - self.top_margin - self.bottom_margin
def get_page_size(self):
return (self.page_width, self.page_height)
def get_page_rect(self):
lm, tm, rm, bm = self.margins
pw, ph = self.paper_size
return (lm, tm, pw - rm, ph - bm)
#------------------------------------------------------------------------------
class Printable(object):
"""Mixin class for components implementing the "Print" command."""
printable = overridable_property('printable', "Whether this component should handle the 'Print' command.")
page_setup = overridable_property('page_setup', "The PageSetup object to use for printing.")
print_title = overridable_property('print_title', "Title for print job.")
_printable = True
def get_printable(self):
return self._printable
def set_printable(self, x):
self._printable = x
def get_print_title(self):
window = self.window
if window:
return window.title
else:
return ""
def get_page_setup(self):
result = None
model = getattr(self, 'model', None)
if model:
result = getattr(model, 'page_setup', None)
if not result:
result = application().page_setup
return result
def setup_menus(self, m):
if self.printable:
m.print_cmd.enabled = True
def print_cmd(self):
if self.printable:
page_setup = self.page_setup
if page_setup:
self.print_view(page_setup)
else:
self.pass_to_next_handler('print_cmd')
#------------------------------------------------------------------------------
class Paginator(object):
"""Used internally. A generic pagination algorithm for printing."""
def __init__(self, view, page_setup):
self.view = view
extent_width, extent_height = view.get_print_extent()
#paper_width, paper_height = page_setup.paper_size
#lm, tm, rm, bm = page_setup.margins
#page_width = int(paper_width - lm - rm)
#page_height = int(paper_height - tm - bm)
page_width, page_height = page_setup.page_size
if page_width <= 0 or page_height <= 0:
from AlertFunctions import stop_alert
stop_alert("Margins are too large for the page size.")
return
self.extent_rect = (0, 0, extent_width, extent_height)
self.page_width = page_width
self.page_height = page_height
self.left_margin = page_setup.left_margin
self.top_margin = page_setup.top_margin
self.pages_wide = int(ceil(extent_width / page_width))
self.pages_high = int(ceil(extent_height / page_height))
self.num_pages = self.pages_wide * self.pages_high
def draw_page(self, canvas, page_num):
row, col = divmod(page_num, self.pages_wide)
view_left = col * self.page_width
view_top = row * self.page_height
view_right = view_left + self.page_width
view_bottom = view_top + self.page_height
view_rect = (view_left, view_top, view_right, view_bottom)
dx = self.left_margin - view_left
dy = self.top_margin - view_top
canvas.translate(dx, dy)
canvas.rectclip(self.extent_rect)
canvas.rectclip(view_rect)
canvas._printing = True
self.view.draw(canvas, view_rect)

View File

@ -0,0 +1,52 @@
#
# Python GUI - Radio buttons - Generic
#
from GUI.Properties import overridable_property
from GUI import Control
class RadioButton(Control):
"""RadioButtons are used in groups to represent a 1-of-N
choice. A group of RadioButtons is coordinated by a
RadioGroup object. The 'group' property indicates the
RadioGroup to which it belongs, and the 'value' property
is the value to which the RadioGroup's value is set
when this RadioButton is selected."""
group = overridable_property('group', """The RadioGroup to
which this radio button belongs.""")
value = overridable_property('value', """The value to which
the associated radio group's 'value' property should be
set when this radio button is selected.""")
_group = None
_value = None
#
# Properties
#
def get_group(self):
return self._group
def set_group(self, new_group):
old_group = self._group
if new_group is not old_group:
if old_group:
old_group._remove_item(self)
self._group = new_group
if new_group:
new_group._add_item(self)
def get_value(self):
return self._value
def set_value(self, new_value):
old_value = self._value
if new_value != old_value:
self._value = new_value
self._value_changed()
def _value_changed(self):
raise NotImplementedError

View File

@ -0,0 +1,87 @@
#
# Python GUI - Radio groups - Generic
#
from GUI.Properties import Properties, overridable_property
from GUI.Actions import Action
class RadioGroup(Properties, Action):
"""A RadioGroup coordinates a group of RadioButtons.
It has a 'value' property which is equal to the value
of the currently selected RadioButton. It may be given
an action procedure to execute when its value changes.
Operations:
iter(group)
Returns an iterator over the items of the group.
"""
value = overridable_property('value', """The value of the currently
selected radio button.""")
_items = None
_value = None
def __init__(self, items = [], **kwds):
Properties.__init__(self, **kwds)
self._items = []
self.add_items(items)
#
# Operations
#
def __iter__(self):
return iter(self._items)
#
# Properties
#
def get_value(self):
return self._value
def set_value(self, x):
if self._value <> x:
self._value = x
self._value_changed()
self.do_action()
#
# Adding and removing items
#
def add_items(self, items):
"Add a sequence of RadioButtons to this group."
for item in items:
self.add_item(item)
def add_item(self, item):
"Add a RadioButton to this group."
item.group = self
def remove_items(self, items):
"Remove a sequence of RadioButtons from this group."
for item in items:
item.group = None
def remove_item(self, item):
"Remove a RadioButton from this group."
item.group = None
def _add_item(self, item):
self._items.append(item)
self._item_added(item)
def _remove_item(self, item):
self._items.remove(item)
self._item_removed(item)
def _item_added(self, item):
raise NotImplementedError
def _item_removed(self, item):
raise NotImplementedError
def _value_changed(self):
raise NotImplementedError

View File

@ -0,0 +1,168 @@
#
# Python GUI - Scrollable Views - Generic
#
from GUI.Geometry import rect_sized, add_pt, sub_pt
from GUI.Properties import overridable_property
from GUI.Geometry import sect_rect
from GUI import DrawableContainer
default_extent = (300, 300)
default_line_scroll_amount = (16, 16)
default_scrolling = 'hv'
class ScrollableView(DrawableContainer):
"""A ScrollableView is a 2D drawing area having its own coordinate
system and clipping area, with support for scrolling."""
scrolling = overridable_property('scrolling',
"String containing 'h' for horizontal and 'v' for vertical scrolling.")
hscrolling = overridable_property('hscrolling',
"True if horizontal scrolling is enabled.")
vscrolling = overridable_property('vscrolling',
"True if vertical scrolling is enabled.")
extent = overridable_property('extent',
"Size of scrollable area in local coordinates.")
scroll_offset = overridable_property('scroll_offset',
"Current scrolling position.")
line_scroll_amount = overridable_property('line_scroll_amount',
"Tuple specifying horizontal and vertical line scrolling increments.")
background_color = overridable_property('background_color',
"Color with which to fill areas outside the extent, or None")
#scroll_bars = overridable_property('scroll_bars',
# "Attached ScrollBar instances.")
#
## _scroll_bars [ScrollBar]
def set(self, **kwds):
if 'scrolling' in kwds:
self.scrolling = kwds.pop('scrolling')
DrawableContainer.set(self, **kwds)
def get_scrolling(self):
chars = []
if self.hscrolling:
chars.append('h')
if self.vscrolling:
chars.append('v')
return ''.join(chars)
def set_scrolling(self, value):
self.hscrolling = 'h' in value
self.vscrolling = 'v' in value
def viewed_rect(self):
"""Return the rectangle in local coordinates bounding the currently
visible part of the extent."""
return rect_sized(self.scroll_offset, self.size)
def get_print_extent(self):
return self.extent
def get_background_color(self):
return self._background_color
def set_background_color(self, x):
self._background_color = x
self.invalidate()
#
# Coordinate transformation
#
def local_to_container_offset(self):
return sub_pt(self.position, self.scroll_offset)
#
# Scrolling
#
def h_line_scroll_amount(self):
"""Return the horizontal line scroll increment."""
return self.line_scroll_amount[0]
def v_line_scroll_amount(self):
"""Return the vertical line scroll increment."""
return self.line_scroll_amount[1]
def h_page_scroll_amount(self):
"""Return the horizontal page scroll increment."""
return self.width - self.h_line_scroll_amount()
def v_page_scroll_amount(self):
"""Return the vertical page scroll increment."""
return self.height - self.v_line_scroll_amount()
def scroll_by(self, dx, dy):
"""Scroll by the given amount horizontally and vertically."""
self.scroll_offset = add_pt(self.scroll_offset, (dx, dy))
def scroll_line_left(self):
"""Called by horizontal scroll bar to scroll left by one line."""
self.scroll_by(-self.h_line_scroll_amount(), 0)
def scroll_line_right(self):
"""Called by horizontal scroll bar to scroll right by one line."""
self.scroll_by(self.h_line_scroll_amount(), 0)
def scroll_line_up(self):
"""Called by vertical scroll bar to scroll up by one line."""
self.scroll_by(0, -self.v_line_scroll_amount())
def scroll_line_down(self):
"""Called by vertical scroll bar to scroll down by one line."""
self.scroll_by(0, self.v_line_scroll_amount())
def scroll_page_left(self):
"""Called by horizontal scroll bar to scroll left by one page."""
self.scroll_by(-self.h_page_scroll_amount(), 0)
def scroll_page_right(self):
"""Called by horizontal scroll bar to scroll right by one page."""
self.scroll_by(self.h_page_scroll_amount(), 0)
def scroll_page_up(self):
"""Called by vertical scroll bar to scroll up by one page."""
self.scroll_by(0, -self.v_page_scroll_amount())
def scroll_page_down(self):
"""Called by vertical scroll bar to scroll down by one page."""
self.scroll_by(0, self.v_page_scroll_amount())
#
# Background drawing
#
def _draw_background(self, canvas, clip_rect):
# If the view has a background color, uses it to fill the parts of the
# clip_rect that are outside the view's extent and returns the remaining
# rectangle. Otherwise, returns the clip_rect unchanged.
color = self._background_color
if color:
vl, vt, vr, vb = clip_rect
ew, eh = self.extent
if vr > ew or vb > eh:
#if getattr(self, "_debug_bg", False): ###
# print "ScrollableView: old backcolor =", canvas.backcolor ###
canvas.gsave()
canvas.backcolor = color
if ew < vr:
#if getattr(self, "_debug_bg", False): ###
# print "ScrollableView: erasing", (ew, vt, vr, vb) ###
canvas.erase_rect((ew, vt, vr, vb))
if eh < vb:
if getattr(self, "_debug_bg", False): ###
print "ScrollableView: erasing", (vl, eh, ew, vb) ###
canvas.erase_rect((vl, eh, ew, vb))
canvas.grestore()
#if getattr(self, "_debug_bg", False): ###
# print "ScrollableView: restored backcolor =", canvas.backcolor ###
return sect_rect(clip_rect, (0, 0, ew, eh))
return clip_rect

32
GUI/Generic/GSliders.py Normal file
View File

@ -0,0 +1,32 @@
#
# Python GUI - Slider - Generic
#
from GUI.Properties import overridable_property
from GUI.Actions import Action
from GUI import Control
class Slider(Control, Action):
"""A control for entering a value by moving a knob along a scale.
Constructor:
Slider(orient)
where orient = 'h' for horizontal or 'v' for vertical.
"""
_default_length = 100
value = overridable_property('value', "The current value of the control")
min_value = overridable_property('min_value', "Minimum value of the control")
max_value = overridable_property('max_value', "Maximum value of the control")
range = overridable_property('range', "Tuple (min_value, max_value)")
ticks = overridable_property('ticks', "Number of tick marks")
discrete = overridable_property('discrete', "Whether to constrain value to ticks")
live = overridable_property('live', "Whether to invoke action continuously while dragging")
def get_range(self):
return (self.min_value, self.max_value)
def set_range(self, x):
self.min_value = x[0]
self.max_value = x[1]

117
GUI/Generic/GStdMenus.py Normal file
View File

@ -0,0 +1,117 @@
#-------------------------------------------------------------------------------
#
# PyGUI - Standard Menus - Generic
#
#-------------------------------------------------------------------------------
from GUI.Compatibility import set
from GUI import Menu
from GUI import MenuList
#-------------------------------------------------------------------------------
class CommandSet(set):
"""A set of menu command names.
Constructors:
CommandSet(string)
CommandSet(sequence of strings)
Operations:
string in CommandSet
CommandSet + x
CommmandSet - x
x + CommandSet
x - CommandSet
CommandSet += x
CommandSet -= x
where x is a CommandSet, a string or a sequence of strings
"""
def __init__(self, arg = None):
if arg:
if isinstance(arg, basestring):
arg = [arg]
set.__init__(self, arg)
def __or__(self, other):
return set.__or__(self, as_command_set(other))
__ror__ = __add__ = __radd__ = __or__
def __ior__(self, other):
return set.__ior__(self, as_command_set(other))
__iadd__ = __ior__
def __sub__(self, other):
return set.__sub__(self, as_command_set(other))
def __rsub__(self, other):
return as_command_set(other) - self
def __isub__(self, other):
return set.__isub__(self, as_command_set(other))
#-------------------------------------------------------------------------------
def as_command_set(x):
if not isinstance(x, CommandSet):
if isinstance(x, basestring):
x = [x]
x = CommandSet(x)
return x
def filter_menu_items(items, include):
result = []
sep = False
for item in items:
if item == "-":
sep = True
elif item[1] in include:
if sep:
result.append("-")
sep = False
result.append(item)
return result
def build_menus(spec_list, substitutions = {}, include = None, exclude = None):
if include is None:
include = sum(default_includes)
include = include + sum(always_include)
if exclude is not None:
include = include - exclude
menus = []
for title, items, special in spec_list:
items = filter_menu_items(items, include)
if items:
menus.append(Menu(title, items, special = special, substitutions = substitutions))
return MenuList(menus)
#-------------------------------------------------------------------------------
fundamental_cmds = CommandSet(['quit_cmd'])
help_cmds = CommandSet(['about_cmd', 'help_cmd'])
pref_cmds = CommandSet(['preferences_cmd'])
file_cmds = CommandSet(['new_cmd', 'open_cmd', 'close_cmd', 'save_cmd', 'save_as_cmd', 'revert_cmd'])
print_cmds = CommandSet(['page_setup_cmd', 'print_cmd'])
edit_cmds = CommandSet(['undo_cmd', 'redo_cmd', 'cut_cmd', 'copy_cmd', 'paste_cmd', 'clear_cmd', 'select_all_cmd'])
always_include = [fundamental_cmds, edit_cmds]
default_includes = [help_cmds, pref_cmds, file_cmds, print_cmds]
#-------------------------------------------------------------------------------
if __name__ == "__main__":
s1 = CommandSet('a')
print "s1 =", s1
s2 = CommandSet(['a', 'b'])
print "s2 =", s2
s3 = s2 + 'c'
print "s3 =", s3
s4 = 'd' + s3
print "s4 =", s4
s5 = s4 - 'b'
print "s5 =", s5
s6 = ['a', 'b', 'c', 'd', 'e', 'f'] - s5
print "s6 =", s6

37
GUI/Generic/GTasks.py Normal file
View File

@ -0,0 +1,37 @@
#
# PyGUI - Tasks - Generic
#
from GUI.Properties import Properties, overridable_property
class Task(Properties):
"""A Task represents an action to be performed after a specified
time interval, either once or repeatedly.
Constructor:
Task(proc, interval, repeat = False, start = True)
Creates a task to call the given proc, which should be
a callable object of no arguments, after the specified
interval in seconds from the time the task is scheduled.
If repeat is true, the task will be automatically re-scheduled
each time the proc is called. If start is true, the task will be
automatically scheduled upon creation; otherwise the start()
method must be called to schedule the task.
"""
interval = overridable_property('interval', "Time in seconds between firings")
repeat = overridable_property('repeat', "Whether to fire repeatedly or once only")
def __del__(self):
self.stop()
scheduled = overridable_property('scheduled',
"True if the task is currently scheduled. Read-only.")
def start(self):
"""Schedule the task if it is not already scheduled."""
raise NotImplementedError("GUI.Task.start")
def stop(self):
"""Unschedules the task if it is currently scheduled."""
raise NotImplementedError("GUI.Task.stop")

View File

@ -0,0 +1,34 @@
#
# Python GUI - Text Editor - Generic
#
from GUI.Properties import overridable_property
from GUI import Component
from GUI import EditCmdHandler
from GUI.Printing import Printable
class TextEditor(Component, EditCmdHandler, Printable):
"""A component for editing substantial amounts of text. The text is
kept internally to the component and cannot be shared between views."""
text = overridable_property('text', "The contents as a string.")
text_length = overridable_property('text_length', "Number of characters in the text.")
selection = overridable_property('selection', "Range of text selected.")
font = overridable_property('font')
tab_spacing = overridable_property('tab_spacing', "Distance between tab stops")
def setup_menus(self, m):
Component.setup_menus(self, m)
EditCmdHandler.setup_menus(self, m)
Printable.setup_menus(self, m)
def key_down(self, e):
if e.key == 'enter':
self.pass_to_next_handler('key_down', e)
else:
Component.key_down(self, e)
def print_view(self, page_setup):
from TextEditorPrinting import TextEditorPrintView
view = TextEditorPrintView(self, page_setup)
view.print_view(page_setup)

Some files were not shown because too many files have changed in this diff Show More