# Sketch - A Python-based interactive drawing program
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2002, 2003 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	USA

# This file contains the classes that act as a more or less abstract
# graphics device. For the purposes of this file, a graphics device
# provides methods for drawing primitives like rectangles, curves or
# images and for setting the colors and pattern with which they are
# drawn. A graphics device is abstract in the sense that all coordinates
# are given in document coordinates and code using a graphics device
# need not know whether output goes to a window or a printer, etc.
#

import sys
from types import TupleType
import operator, string
from math import pi

import PIL.Image

import X
from pax import PaxRegionType, IntersectMasks, CreateRegion

from Sketch.warn import pdebug, warn, INTERNAL, USER
from Sketch import _, SketchError, const, config
from Sketch.Lib.util import Empty

from Sketch.const import ArcPieSlice, ArcChord

from Sketch import _sketch


from Sketch import Point, Polar, Rect, UnitRect, Identity, SingularMatrix, \
     Trafo, Rotation, Scale, Translation, Undo, TransformRectangle

from color import StandardColors
import color
from properties import SolidLine, PropertyStack
from pattern import SolidPattern, EmptyPattern


# approximate a unit circle
circle_path = _sketch.approx_arc(0, 2 * pi)

# Flip y coordinates
FlipY = Trafo(1, 0, 0, -1, 0, 0)


# Define a device dependent color (as opposed to color.RGBColor, which
# is a bit device independent). Mainly interesting when drawing on a
# Bitmap or inverting device where it is important which bits are
# involved and not which color is actually used. The colors are
# currently represented as python integers.
def ScreenColor(color):
    return int(color)

color_0 = ScreenColor(0)
color_1 = ScreenColor(1)

# some default properties
black_line = PropertyStack()
black_line.AddStyle(SolidLine(StandardColors.black))
blue_line = PropertyStack()
blue_line.AddStyle(SolidLine(StandardColors.blue))
defaultLineStyle = black_line
default_outline_style = black_line
default_guide_style = blue_line
default_grid_style = blue_line


class GCDevice:

    #	The base class for devices dealing with X graphics contexts
    #	(GCs). Implements the methods for converting between window and
    #	doc coordinates, clipping and outline mode

    outline_style = default_outline_style
    line = defaultLineStyle

    def __init__(self):
	self.orig_x = self.orig_y = 0.0
	self.scale = 1.0
	self.win_to_doc = self.doc_to_win = Identity
	self.init_trafo_stack()
	self.gc = None
	self.outline_mode = 0
	self.font = None
	self.clip_region = None
	self.clip_stack = None
	self.proc_fill = 0

    def init_gc(self, widget, **gcargs):
	self.gc = widget.CreateGC(gcargs)
	self.widget = widget
	self.visual = color.skvisual
	self.InitClip()

    #
    #	Clipping
    #
    #	Ideally, the clipping mechanism works as follows:
    #
    #	Before drawing anything, the canvas initializes the clipping
    #	mechanism with InitClip(). After this, no clipping is active
    #	(apart from clipping to the window which is always done by X)
    #
    #	Subsequently, whenever clipping is needed, you are expected to
    #	call PushClip to save the current clipping region on a stack and
    #	use one (or more) of the methods ClipRegion, ClipRect,
    #	ClipPolygon or ClipBitmap to establish a new clipping region.
    #	After this, only those parts of the device that lie within the
    #	clipping region are modified.
    #
    #	If there already is an active clipping region, the new clipping
    #	region will be the intersection of the old clipping region and
    #	the region specified in the method invocation.
    #
    #	To restore the old clipping region, finally call PopClip. There
    #	must be a call to PopClip for every call to PushClip, etc.
    #
    #	This mechanism allows arbitrarily nested clipping operations.
    #
    #	The clipping regions are given in device coordinates
    #


    def InitClip(self):
	# reset all clipping...
	self.gc.SetClipMask(None)
	self.clip_region = None
	self.clip_stack = ()

    def IsClipping(self):
	return self.clip_stack != ()

    def PushClip(self):
	self.clip_stack = (self.clip_region, self.clip_stack)

    def PopClip(self):
	# Pop the old clip region from the clip stack and make it the
	# active clip region.
	self.clip_region, self.clip_stack = self.clip_stack
	self.gc.SetClipMask(self.clip_region)

    def ClipRegion(self, region):
	# Itersect the current clip region and REGION and make the
	# result the new clip region. REGION may be a region object or a
	# pixmap object of depth 1
	self.clip_region = IntersectMasks(self.clip_region, region)
	self.gc.SetClipMask(self.clip_region)

    def ClipRect(self, recttuple):
	region = self.widget.CreateRegion()
	apply(region.UnionRectWithRegion, recttuple)
	self.ClipRegion(region)

    def ClipPolygon(self, pts):
	self.ClipRegion(self.widget.PolygonRegion(pts))

    ClipBitmap = ClipRegion

    def create_clip_bitmap(self):
	width = self.widget.width
	height = self.widget.height
	bitmap = self.widget.CreatePixmap(width, height, 1)
	bitmap_gc = bitmap.CreateGC(foreground = 0)
	bitmap_gc.FillRectangle(0, 0, width, height)
	bitmap_gc.foreground = 1
	return (bitmap, bitmap_gc)

    #
    # Convert document coordinates to window coordinates and vice versa.
    #

    def DocToWin(self, *args):
	# Return the point in window coords as a tuple of ints
	return apply(self.doc_to_win.DocToWin, args)

    def DocToWinPoint(self, p):
	# Return the point in window coords as a point object
	return self.doc_to_win(p)

    def LengthToWin(self, len):
	return int(len * self.scale + 0.5)

    def LengthToWinFloat(self, len):
	return len * self.scale

    def LengthToDoc(self, len):
	return len / self.scale

    def WinToDoc(self, *args):
	return apply(self.win_to_doc, args)

    def init_trafo_stack(self):
	self.trafo_stack = ()
	self.default_trafo = (self.win_to_doc, self.doc_to_win)

    def SetViewportTransform(self, scale, doc_to_win, win_to_doc):
	self.scale = scale
	self.doc_to_win = doc_to_win
	self.win_to_doc = win_to_doc
	self.init_trafo_stack()

    def InitTrafo():
	self.win_to_doc, self.doc_to_win = self.default_trafo
	self.trafo_stack = ()

    def PushTrafo(self):
	self.trafo_stack = (self.win_to_doc, self.doc_to_win, self.trafo_stack)

    def Concat(self, trafo):
	self.doc_to_win = self.doc_to_win(trafo)
	try:
	    self.win_to_doc = trafo.inverse()(self.win_to_doc)
	except SingularMatrix:
	    pass

    def Translate(self, *args):
	self.Concat(apply(Translation, args))

    def Scale(self, factor):
	self.Concat(Scale(factor))

    def Rotate(self, angle):
	self.Concat(Rotation(angle))

    def PopTrafo(self):
	self.win_to_doc, self.doc_to_win, self.trafo_stack = self.trafo_stack

    def WindowResized(self, width, height):
	pass


    #
    #	Double Buffering
    #

    def StartDblBuffer(self):
	self.buffer_pixmap = self.widget.CreatePixmap()
	self.gc.SetDrawable(self.buffer_pixmap)

    def EndDblBuffer(self):
	self.gc.SetDrawable(self.widget)
	self.buffer_pixmap.CopyArea(self.widget, self.gc, 0, 0,
				    self.widget.width, self.widget.height,
				    0, 0)
	self.buffer_pixmap = None


class SimpleGC(GCDevice):

    #
    #	Functions for drawing X-primitives (rectangles, lines, ...)
    #	with solid colors
    #
    #	These functions are used internally and by the Patterns
    #

    def __init__(self):
	GCDevice.__init__(self)
	self.current_color = StandardColors.black

    def SetFillColor(self, color):
	self.current_color = color
	self.gc.SetForegroundAndFill(self.visual.get_pixel(color))

    def SetLineColor(self, color):
	self.current_color = color
	self.gc.SetForegroundAndFill(self.visual.get_pixel(color))


    def SetLineAttributes(self, width, cap = X.CapButt, join = X.JoinMiter,
			  dashes = None):
	if dashes:
	    line = X.LineOnOffDash
	else:
	    line = X.LineSolid
	self.gc.SetLineAttributes(int(round(width * self.scale)), line, cap,
                                  join)
	if dashes:
	    if width < 1.0:
		scale = self.scale
	    else:
		scale = width * self.scale
	    dashes = map(operator.mul, dashes, [scale] * len(dashes))
	    dashes = map(int, map(round, dashes))
	    for idx in range(len(dashes)):
		length = dashes[idx]
		if length <= 0:
		    dashes[idx] = 1
		elif length > 255:
		    dashes[idx] = 255
	    self.gc.SetDashes(dashes)

    def SetLineSolid(self):
	self.gc.line_style = X.LineSolid

    def DrawLine(self, start, end):
	startx, starty	= self.DocToWin(start)
	endx,	endy	= self.DocToWin(end)
	self.gc.DrawLine(startx, starty, endx, endy)

    def DrawLineXY(self, x1, y1, x2, y2):
	startx, starty	= self.DocToWin(x1, y1)
	endx,	endy	= self.DocToWin(x2, y2)
	self.gc.DrawLine(startx, starty, endx, endy)

    def DrawLines(self, pts):
	pts = map(self.DocToWin, pts)
	self.gc.DrawLines(pts, X.CoordModeOrigin)

    def FillPolygon(self, pts):
	pts = map(self.DocToWin, pts)
	self.gc.FillPolygon(pts, X.Complex, X.CoordModeOrigin)

    def DrawRectangle(self, start, end):
	# draw the outline of the rectangle whose opposite corners are
	# start and end.
	pts = TransformRectangle(self.doc_to_win, Rect(start, end))
	if type(pts) == TupleType:
	    apply(self.gc.DrawRectangle, pts)
	else:
	    self.gc.DrawLines(pts, X.CoordModeOrigin)

    def FillRectangle(self, left, top, right, bottom):
	pts = TransformRectangle(self.doc_to_win,
				 Rect(left, top, right, bottom))
	if type(pts) == TupleType:
	    apply(self.gc.FillRectangle, pts)
	else:
	    self.gc.FillPolygon(pts, X.Convex, X.CoordModeOrigin)

    def DrawEllipse(self, start, end):
	pts = TransformRectangle(self.doc_to_win, Rect(start, end))

	if type(pts) == TupleType:
	    apply(self.gc.DrawArc, pts + (0, 360 * 64))
	else:
	    d = end - start
	    self.PushTrafo()
	    self.Concat(Trafo(d.x, 0, 0, d.y, start.x, start.y))
	    self.DrawBezierPath(circle_path)
	    self.PopTrafo()

    def DrawCircle(self, center, radius):
	rect = Rect(center.x - radius, center.y - radius,
		    center.x + radius, center.y + radius)
	pts = TransformRectangle(self.doc_to_win, rect)
	if type(pts) == TupleType:
	    apply(self.gc.DrawArc, pts + (0, 360 * 64))
	else:
	    self.PushTrafo()
	    self.Concat(Trafo(radius, 0, 0, radius, center.x, center.y))
	    self.DrawBezierPath(circle_path)
	    self.PopTrafo()

    def FillCircle(self, center, radius):
	rect = Rect(center.x - radius, center.y - radius,
		    center.x + radius, center.y + radius)
	pts = TransformRectangle(self.doc_to_win, rect)
	if type(pts) == TupleType:
	    apply(self.gc.FillArc, pts + (0, 360 * 64))
	else:
	    self.PushTrafo()
	    self.Concat(Trafo(radius, 0, 0, radius, center.x, center.y))
	    self.FillBezierPath(circle_path)
	    self.PopTrafo()


    def DrawBezierPath(self, path, rect = None):
	path.draw_transformed(self.gc, self.doc_to_win, 1, 0, rect)

    def FillBezierPath(self, path, rect = None):
	path.draw_transformed(self.gc, self.doc_to_win, 0, 1, rect)



class CommonDevice:

    # some methods common to GraphicsDevice and PostScriptDevice

    def draw_arrow(self, arrow, width, pos, dir, rect = None):
	self.PushTrafo()
	self.Translate(pos.x, pos.y)
	self.Rotate(dir.polar()[1])
	if width >= 1.0:
	    self.Scale(width)
	self.SetLineSolid()
	arrow.Draw(self) #, rect)
	self.PopTrafo()

    def draw_arrows(self, paths, rect = None):
	if self.line:
	    arrow1 = self.properties.line_arrow1
	    arrow2 = self.properties.line_arrow2
	    if arrow1 or arrow2:
		width = self.properties.line_width
		for path in paths:
		    if not path.closed and path.len > 1:
			if arrow1:
			    type, controls, p3, cont = path.Segment(1)
			    p = path.Node(0)
			    if type == _sketch.Bezier:
				p1, p2 = controls
				dir = p - p1
				if not abs(dir):
				    dir = p - p2
			    else:
				dir = p - p3
			    self.draw_arrow(arrow1, width, p, dir, rect)
			if arrow2:
			    type, controls, p, cont = path.Segment(-1)
			    p3 = path.Node(-2)
			    if type == _sketch.Bezier:
				p1, p2 = controls
				dir = p - p2
				if not abs(dir):
				    dir = p - p1
			    else:
				dir = p - p3
			    self.draw_arrow(arrow2, width, p, dir, rect)

    def draw_ellipse_arrows(self, trafo, start_angle, end_angle, arc_type,
			    rect = None):
	if arc_type == const.ArcArc and self.line and start_angle != end_angle:
	    pi2 = pi / 2
	    width = self.properties.line_width
	    arrow1 = self.properties.line_arrow1
	    if arrow1 is not None:
		pos = trafo(Polar(1, start_angle))
		dir = trafo.DTransform(Polar(1, start_angle - pi2))
		self.draw_arrow(arrow1, width, pos, dir, rect)
	    arrow2 = self.properties.line_arrow2
	    if arrow2 is not None:
		pos = trafo(Polar(1, end_angle))
		dir = trafo.DTransform(Polar(1, end_angle + pi2))
		self.draw_arrow(arrow2, width, pos, dir, rect)

use_shm_images = 0
shm_images_supported = 0

class GraphicsDevice(SimpleGC, CommonDevice):

    #
    #	Graphics device that allows complex fill and line properties
    #	XXX This class should have a different name
    #

    ximage = None
    font_cache = None
    old_font_cache = None

    grid_style = default_grid_style
    guide_style = default_guide_style

    # the following flags may be used by the document and layer objects
    # to determine which parts to draw, depending on whether the user
    # marks them as visible or printable.
    draw_visible = 1
    draw_printable = 0

    def __init__(self):
	SimpleGC.__init__(self)
	self.line = 0
	self.fill = 0
	self.properties = PropertyStack()
	self.gradient_steps = config.preferences.gradient_steps_editor
	self.images_drawn = 0
	self.unknown_fonts = {}
        self.failed_fonts = {}

    def InitClip(self):
	SimpleGC.InitClip(self)
	self.images_drawn = 0


    proc_fill = proc_line = 0
    fill_rect = None

    def set_properties(self, properties):
	self.properties = properties
	if properties.HasFill():
	    self.fill = 1
	    self.proc_fill = properties.IsAlgorithmicFill()
	else:
	    self.fill = 0
	    self.proc_fill = 0
	if properties.HasLine():
	    self.line = 1
	    self.proc_line = properties.IsAlgorithmicLine()
	else:
	    self.line = 0
	    self.proc_line = 0


    def SetProperties(self, properties, rect = None):
	if not self.outline_mode:
	    self.fill_rect = rect
	    self.set_properties(properties)
	else:
	    # if outline, set only font properties
	    self.properties.SetProperty(font = properties.font,
					 font_size = properties.font_size)

    #
    def activate_line(self):
	self.properties.ExecuteLine(self)

    def activate_fill(self):
	self.properties.ExecuteFill(self, self.fill_rect)

    #
    #	Patterns
    #

    def get_pattern_image(self):
	width = self.widget.width
	height = self.widget.height
	winrect = self.doc_to_win(self.fill_rect)
	left, top, right, bottom = map(int, map(round, winrect))
	l = max(left, 0);	r = min(right, width);
	t = max(top, 0);	b = min(bottom, height);
	if type(self.clip_region) == PaxRegionType:
	    cx, cy, cw, ch = self.clip_region.ClipBox()
	    l = max(l, cx);	r = min(r, cx + cw)
	    t = max(t, cy);	b = min(b, cy + ch)
	if l >= r or t >= b:
	    return None, None, None
	image = PIL.Image.new('RGB', (r - l, b - t), (255, 255, 255))
	trafo = Translation(-l, -t)(self.doc_to_win)
	return image, trafo, (l, t)

    def draw_pattern_image(self, image, pos):
	if use_shm_images and self.images_drawn:
	    # force a shmimage to be drawn if ShmPutImage requests might
	    # be in the queue
	    self.widget.Sync()
	self.create_ximage()
	ximage = self.ximage
	x, y = pos
	w, h = image.size
	_sketch.copy_image_to_ximage(self.visual, image.im, ximage, x, y, w, h)
	if use_shm_images:
	    self.gc.ShmPutImage(ximage, x, y, x, y, w, h, 0)
	    self.images_drawn = 1
	else:
	    self.gc.PutImage(ximage, x, y, x, y, w, h)

    has_axial_gradient = 1
    def AxialGradient(self, gradient, p0, p1):
	# p0 and p1 may be PointSpecs
	image, trafo, pos = self.get_pattern_image()
	if image is None:
	    return
	x0, y0 = trafo(p0)
	x1, y1 = trafo(p1)
	#import time
	#_t = time.clock()
	_sketch.fill_axial_gradient(image.im, gradient.Colors(), x0,y0, x1,y1)
	#_t = time.clock() - _t
	#print 'axial:', _t
	self.draw_pattern_image(image, pos)

    has_radial_gradient = 1
    def RadialGradient(self, gradient, p, r0, r1):
	# p may be PointSpec
	image, trafo, pos = self.get_pattern_image()
	if image is None:
	    return
	x, y = trafo.DocToWin(p)
	r0 = int(round(abs(trafo.DTransform(r0, 0))))
	r1 = int(round(abs(trafo.DTransform(r1, 0))))
	#import time
	#_t = time.clock()
	_sketch.fill_radial_gradient(image.im, gradient.Colors(), x, y, r0, r1)
	#_t = time.clock() - _t
	#print 'radial:', _t
	self.draw_pattern_image(image, pos)


    has_conical_gradient = 1
    def ConicalGradient(self, gradient, p, angle):
	# p may be PointSpec
	image, trafo, pos = self.get_pattern_image()
	if image is None:
	    return
	cx, cy = trafo.DocToWin(p)
	#import time
	#_t = time.clock()
	_sketch.fill_conical_gradient(image.im, gradient.Colors(), cx, cy,
				      -angle)
	#_t = time.clock() - _t
	#print 'conical:', _t
	self.draw_pattern_image(image, pos)

    use_pixmap_tile = 1
    def TileImage(self, tile, trafo):
	# XXX this could be faster with some caching.
        width, height = self.doc_to_win(trafo).DTransform(tile.size)
        width = int(round(width))
        height = int(round(height))
	if self.use_pixmap_tile and trafo.m12 == 0 and trafo.m21 == 0 \
           and width * height < self.widget.width * self.widget.height:
	    # the image is only scaled. Use a tile pixmap
	    #
	    # there are other cases where this could be done:
	    # horizontal/vertical shearing, rotation by 90 degrees,...
	    # This should also be done like in DrawImage, i.e. use the
	    # integer coordintes of the transformed tile to determine if
	    # pixmaps can be used. This would be a little less precise,
	    # though.
	    # degenerate cases
	    if width == 0: width = 1
	    if height == 0: height = 1
	    ximage = self.create_sized_ximage(abs(width), abs(height))
	    pixmap = self.widget.CreatePixmap(abs(width), abs(height),
					      ximage.depth)
	    _sketch.copy_image_to_ximage(self.visual, tile.im, ximage,
					 0, 0, width, height)
	    gc = pixmap.CreateGC()
	    gc.PutImage(ximage, 0, 0, 0, 0, abs(width), abs(height))
	    self.gc.SetForegroundAndFill(pixmap)
	    x, y = trafo.DocToWin(0, 0)
	    self.gc.SetTSOrigin(x, y)
	    self.gc.FillRectangle(0, 0, self.widget.width, self.widget.height)
	else:
	    image, temp_trafo, pos = self.get_pattern_image()
	    if image is None:
		return
	    trafo = temp_trafo(trafo)
	    try:
		_sketch.fill_transformed_tile(image.im, tile.im,
					      trafo.inverse())
		self.draw_pattern_image(image, pos)
	    except SingularMatrix:
		pass


    #
    #	Outline Mode
    #
    #	StartOutlineMode(COLOR) starts drawing everything unfilled with
    #	a solid line of color COLOR.
    #
    #	EndOutlineMode() restores the previous mode.
    #
    #	As long as there are always matching calls to StartOutlineMode
    #	and EndOutlineMode outline modes can be arbitrarily nested. This
    #	allows activating the outline mode globally in the canvas widget
    #	and overriding the color in indiviual layers or drawing only
    #	some layers in outline mode in different colors.

    allow_outline = 1

    def StartOutlineMode(self, color = None):
	if self.allow_outline:
	    self.outline_mode = (self.properties, self.outline_mode)
	    if color is None:
		properties = self.outline_style
	    else:
		properties = PropertyStack()
                properties.SetProperty(fill_pattern = EmptyPattern)
		properties.AddStyle(SolidLine(color))
	    self.set_properties(properties)

    def EndOutlineMode(self):
	if self.allow_outline and self.outline_mode:
	    properties, self.outline_mode = self.outline_mode
	    self.set_properties(properties)

    def IsOutlineActive(self):
	return not not self.outline_mode


    #
    #	Primitives
    #

    def Rectangle(self, trafo, clip = 0):
	self.PushTrafo()
	self.Concat(trafo)
	pts = TransformRectangle(self.doc_to_win, UnitRect)
	self.PopTrafo()
	if type(pts) == TupleType:
	    if self.proc_fill:
		if not clip:
		    self.PushClip()
		self.ClipRect(pts)
		self.properties.ExecuteFill(self, self.fill_rect)
		if not clip:
		    self.PopClip()
		clip = 0
	    elif self.fill:
		self.properties.ExecuteFill(self, self.fill_rect)
		apply(self.gc.FillRectangle, pts)
	    if self.line:
		self.properties.ExecuteLine(self)
		apply(self.gc.DrawRectangle, pts)
	else:
	    if self.proc_fill:
		if not clip:
		    self.PushClip()
		self.ClipPolygon(pts)
		self.properties.ExecuteFill(self, self.fill_rect)
		if not clip:
		    self.PopClip()
		clip = 0
	    elif self.fill:
		self.properties.ExecuteFill(self, self.fill_rect)
		self.gc.FillPolygon(pts, X.Convex, X.CoordModeOrigin)
	    if self.line:
		self.properties.ExecuteLine(self)
		self.gc.DrawLines(pts, X.CoordModeOrigin)
	if clip:
	    if type(pts) == TupleType:
		self.ClipRect(pts)
	    else:
		self.ClipPolygon(pts)

    def RoundedRectangle(self, trafo, radius1, radius2, clip = 0):
	path = _sketch.RoundedRectanglePath(trafo, radius1, radius2)
	self.MultiBezier((path,), None, clip)

    def SimpleEllipse(self, trafo, start_angle, end_angle, arc_type,
		      rect = None, clip = 0):
	trafo2 = self.doc_to_win(trafo)
	if trafo2.m12 == 0.0 and trafo2.m21 == 0.0 and not self.proc_fill \
	   and start_angle == end_angle and not clip:
	    x1, y1 = trafo2.DocToWin(1, 1)
	    x2, y2 = trafo2.DocToWin(-1, -1)
	    if x1 > x2:
		t = x1; x1 = x2; x2 = t
	    if y1 > y2:
		t = y1; y1 = y2; y2 = t
	    w = x2 - x1
	    h = y2 - y1
	    if self.fill:
		self.properties.ExecuteFill(self, self.fill_rect)
		self.gc.FillArc(x1, y1, w, h, 0, 23040) # 360 * 64
	    if self.line:
		self.properties.ExecuteLine(self)
		self.gc.DrawArc(x1, y1, w, h, 0, 23040)
	else:
	    if self.line:
		line = self.activate_line
	    else:
		line = None
	    if self.fill:
		fill = self.activate_fill
	    else:
		fill = None

	    if start_angle != end_angle:
		arc = _sketch.approx_arc(start_angle, end_angle, arc_type)
	    else:
		arc = circle_path
	    # pass rect as None, because trafo2 is not really the
	    # viewport transformation
	    _sketch.draw_multipath(self.gc, trafo2, line, fill,
				   self.PushClip, self.PopClip,
				   self.ClipRegion, None, (arc,),
				   CreateRegion(), self.proc_fill, clip)
	    self.draw_ellipse_arrows(trafo, start_angle, end_angle, arc_type,
				     rect)


    def MultiBezier(self, paths, rect = None, clip = 0):
	if self.line:
	    line = self.activate_line
	else:
	    line = None
	if self.fill:
	    fill = self.activate_fill
	else:
	    fill = None

	_sketch.draw_multipath(self.gc, self.doc_to_win, line, fill,
			       self.PushClip, self.PopClip, self.ClipRegion,
			       rect, paths, CreateRegion(), self.proc_fill,
			       clip)

	if self.line:
	    self.draw_arrows(paths, rect)

    def draw_text_on_gc(self, gc, text, trafo, font, font_size, cache = None):
	self.PushTrafo()
	try:
	    self.Concat(trafo)
	    self.Scale(font_size)
	    up = self.doc_to_win.DTransform(0, 1)
	    if abs(up) >= config.preferences.greek_threshold:
		ptrafo = FlipY(self.doc_to_win)
		xlfd = font.GetXLFD(ptrafo)
		if (ptrafo and (ptrafo.m12 != 0 or ptrafo.m21 != 0
				or ptrafo.m11 > 40 or ptrafo.m11 < 0
				or ptrafo.m22 > 40 or ptrafo.m22 < 0)):
		    xlfd = '%s[%s]' % (xlfd, _sketch.xlfd_char_range(text))
		try:
		    xfont = self.load_font(xlfd, cache)
		except RuntimeError, val:
		    # We must be careful here when reporting this to the
		    # user. If warn pops up a message box and the user
		    # clicks OK, the window gets another expose event
		    # and sketch tries to draw the text again which will
		    # also fail. To avoid infinite loops we try to
		    # report unknown fonts only once for a given font.
		    if not self.unknown_fonts.has_key(font.PostScriptName()):
			warn(USER, _("Cannot load %(font)s:\n%(text)s"),
			     font = `xlfd`, text = val)
			self.unknown_fonts[font.PostScriptName()] = 1
		    # Use a font that will hopefully be always available.
		    # XXX Is there a better way to handle this situation?
		    # We might try times roman with the same size and trafo
		    xfont = self.load_font('fixed', None)
		gc.SetFont(xfont)

		pos = font.TypesetText(text)
		pos = map(self.DocToWin, pos)
		for i in range(len(text)):
		    x, y = pos[i]
		    gc.DrawString(x, y, text[i])
	    else:
		# 'greek'. XXX is this really necessary. It avoids
		# rendering fonts that are too small to read.
		pos = font.TypesetText(text)
		pos = map(self.DocToWin, pos)
		ux, uy = up
		lx = ux / 2; ly = uy / 2
		uppercase = string.uppercase
		draw = gc.DrawLine # we should draw rectangles...
		for i in range(len(text)):
		    x, y = pos[i]
		    if text[i] in uppercase:
			draw(x, y, int(round(x + ux)), int(round(y + uy)))
		    else:
			draw(x, y, int(round(x + lx)), int(round(y + ly)))
	finally:
	    self.PopTrafo()

    def DrawText(self, text, trafo = None, clip = 0, cache = None):
	if text and self.properties.font:
	    if self.fill or clip:
		if self.proc_fill or clip:
		    bitmap, bitmapgc = self.create_clip_bitmap()
		    self.draw_text_on_gc(bitmapgc, text, trafo,
					 self.properties.font,
					 self.properties.font_size,
					 cache)
		    if not clip:
			self.PushClip()
		    self.ClipBitmap(bitmap)
		    self.properties.ExecuteFill(self, self.fill_rect)
		    if not self.proc_fill:
			w, h = bitmap.GetGeometry()[3:5]
			bitmap.CopyPlane(self.widget, self.gc, 0, 0, w, h,
					 0, 0, 1)
		    if not clip:
			self.PopClip()
		else:
		    self.properties.ExecuteFill(self)
		    self.draw_text_on_gc(self.gc, text, trafo,
					 self.properties.font,
					 self.properties.font_size,
					 cache)
	    elif self.IsOutlineActive():
		# in outline mode, draw text filled with the current
		# outline color, because we can't draw outlined text at
		# the moment. We could draw a rectangle, though. (?)
		self.properties.ExecuteLine(self)
		self.draw_text_on_gc(self.gc, text, trafo,
				     self.properties.font,
				     self.properties.font_size,
				     cache)

    def ResetFontCache(self):
	self.font_cache = {}

    def load_font(self, xlfd, cache):
	font_cache = self.font_cache
	complex_text = self.complex_text
        
        if self.failed_fonts.has_key(xlfd):
            # the same xlfd failed before. use fixed as fallback
            # immediately to avoid delays. some servers apparantly take
            # very long to decide that they can't load a font.
            xlfd = 'fixed'

	if cache and cache.has_key(id(self)):
	    old_xlfd, old_font = cache[id(self)]
	    if old_xlfd == xlfd:
		font_cache[xlfd] = old_font
		return old_font

	if complex_text is not None:
	    cache = complex_text.cache
	    key = id(self), complex_text.idx
	    if cache.has_key(key):
		old_xlfd, old_font = cache[key]
		if old_xlfd == xlfd:
		    font_cache[xlfd] = old_font
		    return old_font
	    cache = None

	if font_cache is not None and font_cache.has_key(xlfd):
	    font = font_cache[xlfd]
	else:
            #print 'load font', xlfd
            try:
                font = self.widget.LoadQueryFont(xlfd)
            except RuntimeError:
                self.failed_fonts[xlfd] = 1
                raise

	if font_cache is not None:
	    font_cache[xlfd] = font
	if cache is not None:
	    cache[id(self)] = (xlfd, font)
	elif complex_text is not None:
	    complex_text.cache[(id(self), complex_text.idx)] = (xlfd, font)

	return font

    complex_text = None
    def BeginComplexText(self, clip = 0, cache = None):
	if self.fill or clip or self.IsOutlineActive():
	    if cache is None:
		cache = {}
	    if self.proc_fill or clip:
		bitmap, gc = self.create_clip_bitmap()
		self.complex_text = Empty(bitmap = bitmap, gc = gc,
					  clip = clip, cache = cache, idx = 0)
	    else:
		if self.fill:
		    self.properties.ExecuteFill(self)
		else:
		    # outline mode
		    self.properties.ExecuteLine(self)
		self.complex_text = Empty(gc = self.gc, clip = 0,
					  cache = cache, idx = 0)
	else:
	    self.complex_text = None

    def DrawComplexText(self, text, trafo, font, font_size):
	if self.complex_text is not None:
	    self.draw_text_on_gc(self.complex_text.gc, text, trafo, font,
				 font_size)
	    self.complex_text.idx = self.complex_text.idx + 1

    def EndComplexText(self):
	if self.complex_text is not None:
	    if self.complex_text.clip or self.proc_fill:
		bitmap = self.complex_text.bitmap
		self.PushClip()
		self.ClipBitmap(bitmap)
		self.properties.ExecuteFill(self, self.fill_rect)
		if not self.proc_fill:
		    w, h = bitmap.GetGeometry()[3:5]
		    bitmap.CopyPlane(self.widget, self.gc, 0, 0, w, h,
				     0, 0, 1)
		if not self.complex_text.clip:
		    self.PopClip()
	self.complex_text = None

    def create_ximage(self):
        global use_shm_images
	if not self.ximage:
	    w = self.widget
	    if use_shm_images and not shm_images_supported:
		warn(INTERNAL,
		     'tried to use unsupported shared memory images\n')
		use_shm_images = 0
	    if use_shm_images:
                try:
                    self.ximage = w.ShmCreateImage(w.depth, X.ZPixmap,
                                                   None, w.width, w.height, 1)
                except:
                    # Creating a shared memory image failed. Print a
                    # message and don't use shmimages again. A likely
                    # reason for this is that the test for shmimages in
                    # pax succeeded but the ShmCreateImage here fails
                    # because the limit for shm segments is too low, as
                    # it is by default on Solaris.
                    warn(INTERNAL, _("Can't create shared memory image: %s"),
                         sys.exc_info()[1])
                    use_shm_images = 0
            if not self.ximage:
		self.ximage = self.create_sized_ximage(w.width, w.height)

    def create_sized_ximage(self, width, height):
	w = self.widget
	depth = w.depth
	if depth > 16:
	    bpl = 4 * width
	elif depth > 8:
	    bpl = ((2 * width + 3) / 4) * 4
	elif depth == 8:
	    bpl = ((width + 3) / 4) * 4
	else:
	    raise SketchError('unsupported depth for images')
	return w.CreateImage(w.depth, X.ZPixmap, 0, None, width, height,
			     32, bpl)


    def DrawImage(self, image, trafo, clip = 0):
	w, h = image.size
	if self.IsOutlineActive():
	    self.PushTrafo()
	    self.Concat(trafo)
	    self.DrawRectangle(Point(0, 0), Point(w, h))
	    self.PopTrafo()
	    return
	self.create_ximage()
	ximage = self.ximage
	if use_shm_images and self.IsClipping() and self.images_drawn:
	    # force a shmimage to be drawn if complex clipping is done
	    # or ShmPutImage requests might be in the queue.
	    self.widget.Sync()
	    self.images_drawn = 0
	llx, lly = self.DocToWin(trafo.offset())
	lrx, lry = self.DocToWin(trafo(w, 0))
	ulx, uly = self.DocToWin(trafo(0, h))
	urx, ury = self.DocToWin(trafo(w, h))
	if llx == ulx and lly == lry:
	    if llx < lrx:
		sx = llx;	w = lrx - llx + 1
	    else:
		sx = lrx;	w = lrx - llx - 1
	    if uly < lly:
		sy = uly;	h = lly - uly + 1
	    else:
		sy = lly;	h = lly - uly - 1

	    _sketch.copy_image_to_ximage(self.visual, image.im, ximage,
					 sx, sy, w, h)
	    if w < 0:	w = -w
	    if h < 0:	h = -h
	    if not clip:
		self.PushClip()
	    self.ClipRect((sx, sy, w, h))
	else:
	    self.PushTrafo()
	    self.Concat(trafo)
	    self.Concat(Trafo(1, 0, 0, -1, 0, h))
	    inverse = self.win_to_doc
	    dtw = self.DocToWin
	    ulx, uly = dtw(0, 0)
	    urx, ury = dtw(0, h)
	    llx, lly = dtw(w, 0)
	    lrx, lry = dtw(w, h)
	    self.PopTrafo()
	    sx = min(ulx, llx, urx, lrx)
	    ex = max(ulx, llx, urx, lrx)
	    sy = min(uly, lly, ury, lry)
	    ey = max(uly, lly, ury, lry)

	    if type(self.clip_region) == PaxRegionType:
		cx, cy, cw, ch = self.clip_region.ClipBox()
		cex = cx + cw; cey = cy + ch
		if cx >= ex or cex <= sx or cy >= ey or cey <= sy:
		    return
		if cx  > sx and cx  < ex:	sx = cx
		if cex < ex and cex > sx:	ex = cex
		if cy  > sy and cy  < ey:	sy = cy
		if cey < ey and cey > sy:	ey = cey
	    w = ex - sx
	    h = ey - sy

	    region = self.widget.CreateRegion()
	    _sketch.transform_to_ximage(self.visual, inverse,
					image.im, ximage, sx, sy, w, h, region)
	    if not clip:
		self.PushClip()
	    self.ClipRegion(region)

	if sx+w <= 0 or sx >= ximage.width or sy+h <= 0 or sy >= ximage.height:
	    if not clip:
		self.PopClip()
	    return

	if sx < 0:
	    w = w + sx
	    sx = 0
	if sx + w > ximage.width:
	    w = ximage.width - sx
	if sy < 0:
	    h = h + sy
	    sy = 0
	if sy + h > ximage.height:
	    h = ximage.height - sy

	if use_shm_images:
	    self.gc.ShmPutImage(ximage, sx, sy, sx, sy, w, h, 0)
	    self.images_drawn = 1
	else:
	    self.gc.PutImage(ximage, sx, sy, sx, sy, w, h)
	if not clip:
	    self.PopClip()


    def DrawEps(self, data, trafo):
	if not data.image or self.IsOutlineActive():
	    w, h = data.Size()
	    self.PushTrafo()
	    self.Concat(trafo)
	    self.DrawRectangle(Point(0, 0), Point(w, h))
	    self.PopTrafo()
	else:
	    resolution = config.preferences.eps_preview_resolution
	    self.DrawImage(data.image, trafo(Scale(72.0 / resolution)))

    #
    #

    def WindowResized(self, width, height):
	self.ximage = None
	SimpleGC.WindowResized(self, width, height)


    #
    #

    def DrawGrid(self, orig_x, orig_y, xwidth, ywidth, rect):
	# Draw a grid with a horitontal width XWIDTH and vertical width
	# YWIDTH whose origin is at (ORIG_X, ORIG_Y). RECT gives the
	# region of the document for which the grid has to be drawn.
	# RECT is usually the parameter of the same name of the
	# Draw/DrawShape methods of the various graphics objects and the
	# document/layer Note: This functions assumes that doc_to_win is
	# in its initial state
	self.SetProperties(self.grid_style)
	xwinwidth = self.LengthToWinFloat(xwidth)
	if not xwinwidth:
	    if __debug__:
		pdebug(None, 'GraphicsDevice.DrawGrid: zero winwidth')
	    return
	ywinwidth = self.LengthToWinFloat(ywidth)
	if not ywinwidth:
	    if __debug__:
		pdebug(None, 'GraphicsDevice.DrawGrid: zero winwidth')
	    return
	# make the minimum distance between drawn points at least 5
	# pixels XXX: should be configurable
	if xwinwidth < 5:
	    xwinwidth = (int(5.0 / xwinwidth) + 1) * xwinwidth
	    xwidth = self.LengthToDoc(xwinwidth)
	if ywinwidth < 5:
	    ywinwidth = (int(5.0 / ywinwidth) + 1) * ywinwidth
	    ywidth = self.LengthToDoc(ywinwidth)
	startx = int((rect.left - orig_x) / xwidth) * xwidth + orig_x
	starty = int((rect.top - orig_y) / ywidth) * ywidth + orig_y
	winx, winy = self.DocToWinPoint((startx, starty))
	nx = int((rect.right - rect.left) / xwidth) + 2
	ny = int((rect.top - rect.bottom) / ywidth) + 2
	if self.line:
	    self.properties.ExecuteLine(self)
	_sketch.DrawGrid(self.gc, winx, winy, xwinwidth, ywinwidth, nx, ny)

    def DrawGuideLine(self, point, horizontal):
	if self.line:
	    self.properties.ExecuteLine(self)
	self.gc.line_style = X.LineOnOffDash
	self.gc.dashes = 5
	x, y = self.DocToWin(point)
	if horizontal:
	    self.gc.DrawLine(0, y, self.widget.width, y)
	else:
	    self.gc.DrawLine(x, 0, x, self.widget.height)
	self.gc.line_style = X.LineSolid

    def DrawPageOutline(self, width, height):
	# Draw the outline of the page whose size is given by width and
	# height. The page's lower left corner is at (0,0) and its upper
	# right corner at (width, height) in doc coords. The outline is
	# drawn as a rectangle with a thin shadow.
	self.gc.line_width = 0
	self.gc.line_style = X.LineSolid
	left, bottom = self.DocToWin(0, 0)
	right, top = self.DocToWin(width, height)
	sw = 5	# shadow width	XXX: should be configurable ?
	w = right - left
	h = bottom - top
	self.SetFillColor(StandardColors.gray)
	self.gc.FillRectangles([(left + sw, bottom, w + 1, sw + 1),
				(right, top + sw, sw + 1, h + 1)])
	self.SetFillColor(StandardColors.black)
	self.gc.DrawRectangle(left, top, w, h)




#
# Class InvertingDevice
#
# Draws objects always in outline mode, regardless of the object's
# properties. Also draws with function = GXxor.
#
# This class defines a few additional drawing methods that are used by
# the primitives Rectangle and PolyBezier during interactive creation
# and dragging
#

# DummyLineStyle is needed for the InvertingDevice and the HitTestDevice
class DummyAttr(PropertyStack):
    def ExecuteLine(self, gc):
	pass


dummyLineStyle = DummyAttr(SolidLine(color_0))



class InvertingDevice(GraphicsDevice):

    normal_line_style = X.LineSolid
    handle_line_style = X.LineOnOffDash
    caret_line_style = X.LineSolid
    caret_line_width = 2

    def __init__(self):
	GraphicsDevice.__init__(self)
	self.handle_size = 3
	self.small_handle_size = 2
	self.properties = dummyLineStyle
	self.fill = 0
	self.line = 1
	self.gc = None
        self.font_cache = {}

    def init_gc(self, widget, **gcargs):
	self.visual = color.skvisual
        line_width = config.preferences.editor_line_width
	self.gc = widget.CreateGC(foreground = ~0,
				  function = X.GXxor,
				  background = 0,
				  line_width = line_width,
				  line_style = self.normal_line_style)
	self.widget = widget

    # make sure that the properties are not changed
    def SetProperties(self, properties, rect = None):
	pass

    def IsOutlineActive(self):
	return 1

    # Bezier and Line are currently only needed for the bezier objects
    # during a drag
    def Bezier(self, p1, p2, p3, p4):
	dtw = self.DocToWin
	pts = dtw(p1) + dtw(p2) + dtw(p3) + dtw(p4)
	apply(_sketch.DrawBezier, (self.gc,) + pts)

    def Line(self, p1, p2):
	if self.line:
	    startx,	starty	= self.DocToWin(p1)
	    endx,	endy	= self.DocToWin(p2)
	    self.gc.DrawLine(startx, starty, endx, endy)

    # draw a rectangular 'handle' at P. The size of the handle is given
    # by self.handle_size and is always in window coordinates (i.e. is
    # independent of scaling)

    def DrawRectHandle(self, p, filled = 1):
	x, y = self.DocToWin(p)
	size = self.handle_size
	x = x - size
	y = y - size
	if filled:
	    self.gc.FillRectangle(x, y, 2 * size + 1, 2 * size + 1)
	else:
	    self.gc.DrawRectangle(x, y, 2 * size, 2 * size)

    def DrawSmallRectHandle(self, p, filled = 1):
	x, y = self.DocToWin(p)
	size = self.small_handle_size
	x = x - size
	y = y - size
	if filled:
	    self.gc.FillRectangle(x, y, 2 * size + 1, 2 * size + 1)
	else:
	    self.gc.DrawRectangle(x, y, 2 * size, 2 * size)

    def DrawCircleHandle(self, p, filled = 1):
	x, y = self.DocToWin(p)
	size = self.handle_size
	x = x - size
	y = y - size
	if filled:
            # 23040 = 360 * 64
	    self.gc.FillArc(x, y, 2 * size + 1, 2 * size + 1, 0, 23040)
	else:
	    self.gc.DrawArc(x, y, 2 * size, 2 * size, 0, 23040)

    def DrawSmallCircleHandle(self, p, filled = 1):
	x, y = self.DocToWin(p)
	size = self.small_handle_size
	x = x - size
	y = y - size
	if filled:
            # 23040 = 360 * 64
	    self.gc.FillArc(x, y, 2 * size + 1, 2 * size + 1, 0, 23040)
	else:
	    self.gc.DrawArc(x, y, 2 * size, 2 * size, 0, 23040)

    def DrawSmallRectHandleList(self, pts, filled = 1):
	pts = map(self.doc_to_win.DocToWin, pts)
	size = self.small_handle_size
	size2 = 2 * size
	if filled:
	    size = size + 1
	rects = []
	pts.sort()
	lx = ly = None
	for x, y in pts:
	    if y != ly or x != lx:
		rects.append((x - size, y - size, size2, size2))
		lx = x
		ly = y
	if rects:
	    if filled:
		self.gc.FillRectangles(rects)
	    else:
		self.gc.DrawRectangles(rects)

    def DrawHandleLine(self, start, end):
	self.gc.line_style = self.handle_line_style
	self.DrawLine(start, end)
	self.gc.line_style = self.normal_line_style

    def DrawRubberRect(self, start, end):
	self.gc.line_style = self.handle_line_style
	self.DrawRectangle(start, end)
	self.gc.line_style = self.normal_line_style

    def DrawPixmapHandle(self, p, pixmap):
	x, y = self.DocToWin(p)
	width, height = pixmap.GetGeometry()[3:5]
	x = x - width / 2
	y = y - height / 2
	pixmap.CopyPlane(self.widget, self.gc, 0, 0, width, height, x, y, 1)

    def DrawCaretHandle(self, p, up):
	line_width = self.gc.line_width
	self.gc.line_width = self.caret_line_width
	self.gc.line_style = self.caret_line_style
	self.DrawLine(p, p + up)
	self.gc.line_style = self.normal_line_style
	self.gc.line_width = line_width


#
#	Class HitTestDevice
#

pixmap_width_2 = 4
pixmap_width = 2 * pixmap_width_2 + 1


hit_properties = PropertyStack()
hit_properties.SetProperty(fill_pattern = SolidPattern(color_1))
hit_properties.AddStyle(SolidLine(color_1, width = 3))

class HitTestDevice(GraphicsDevice):

    outline_style = hit_properties

    def __init__(self):
	GraphicsDevice.__init__(self)
	self.properties = hit_properties
	self.fill = 1
	self.line = 1
	self.gc = None

    def init_gc(self, widget, **gcargs):
	self.pixmap = widget.CreatePixmap(pixmap_width, pixmap_width, 1)
	self.gc = self.pixmap.CreateGC(foreground = 1, line_width = 3)
	self.visual = color.skvisual

    # make sure that the properties are not changed
    def SetProperties(self, properties, rect = None):
	if properties.HasLine():
	    self.line_width = properties.line_width
	else:
	    self.line_width = 0

    #
    #
    def SetViewportTransform(self, scale, doc_to_win, win_to_doc):
	GraphicsDevice.SetViewportTransform(self, scale, doc_to_win,
					    win_to_doc)
	self.hit_radius_doc = self.LengthToDoc(self.hit_radius)
	hit_properties.SetProperty(line_width = self.hit_radius_doc * 2)
    #
    # Detect various `hits'
    #

    hit_radius = 2

    def SetHitRadius(self, radius):
	self.hit_radius = radius

    def HitRadiusDoc(self):
	return self.LengthToDoc(self.hit_radius)

    def HitRectAroundPoint(self, p):
	rad = self.HitRadiusDoc()
	return Rect(p.x - rad, p.y - rad, p.x + rad, p.y + rad)

    def LineHit(self, start, end, p, line_width = 0):
	radius = self.hit_radius / self.scale
	if line_width:
	    w = line_width / 2
	    if radius < w:
		radius = w
	# check bounding box
	if p.x < min(start.x, end.x) - radius:
	    return 0
	if p.x > max(start.x, end.x) + radius:
	    return 0
	if p.y < min(start.y, end.y) - radius:
	    return 0
	if p.y > max(start.y, end.y) + radius:
	    return 0
	# check if line is hit
	try:
	    d = end - start
	    len = abs(d)
	    if len < 1:
		return abs(start.x - p.x) <= radius \
		       and abs(start.y - p.y) <= radius
	    off = p - start
	    dist = abs((float(off.x) * d.y - float(off.y) * d.x) / len)
	    linepos = (off * d) / len
	    return dist <= radius and linepos > 0 and linepos < len
	except OverflowError:
	    warn(INTERNAL, 'HitTestDevice.LineHit: start = %s end = %s p = %s',
		 start, end, p)
	    return 0

    def ParallelogramHit(self, p, trafo, maxx, maxy, filled, properties=None,
			 ignore_outline_mode = 0):
	filled = filled and (ignore_outline_mode or not self.outline_mode)
	if filled:
	    try:
		inverse = trafo.inverse()
		x, y = inverse(p)
		return 0 <= x <= maxx and 0 <= y <= maxy
	    except SingularMatrix:
		if properties is not None and properties.HasLine():
		    properties = defaultLineStyle
		    return self.ParallelogramHit(p, trafo, maxx, maxy,
						 0, properties)

	if self.outline_mode or (properties is not None
				 and properties.HasLine()):
	    p1 = trafo.offset()
	    p2 = trafo(0, maxy)
	    p3 = trafo(maxx, 0)
	    p4 = trafo(maxx, maxy)
	    if self.outline_mode:
		line_width = 0
	    else:
		line_width = properties.line_width
	    return self.LineHit(p1, p2, p, line_width)\
		   or self.LineHit(p1, p3, p, line_width) \
		   or self.LineHit(p2, p4, p, line_width) \
		   or self.LineHit(p3, p4, p, line_width)

    def SimpleEllipseHit(self, p, trafo, start_angle, end_angle, arc_type,
			 properties, filled, ignore_outline_mode = 0):
	# Hmm, the ellipse is not that simple anymore, maybe we should
	# change the name?
	filled = filled and (ignore_outline_mode or not self.outline_mode)
	try:
	    inverse = trafo.inverse()
	    p2 = inverse(p)
	    dist, phi = p2.polar()
	    has_line = properties.HasLine() or self.outline_mode
	    if has_line:
		# Check whether p is on the outline of the complete
		# ellipse. This check is not entirely correct, but it
		# works well enough for now.
		if not self.outline_mode:
		    line_width = properties.line_width
		else:
		    line_width = 0
		d = max(line_width / 2, self.HitRadiusDoc())
		d = abs(inverse.DTransform(Point(d, 0)))
		border_hit = abs(1.0 - dist) < d
	    else:
		border_hit = 0
	    if start_angle == end_angle:
		# The most common case: a complete ellipse
		if filled and dist <= 1.0:
		    # p is inside of the ellipse -> Hit!
		    return 1
		# Either ellipse is not filled or dist > 1.0. NOw it
		# only depends on the outline.
		return border_hit
	    else:
		# The ellipse is not complete. Now it depends on the
		# arc_type.
		if phi < 0:
		    phi = phi + pi + pi # map phi into the range 0 - 2*PI
		if start_angle < end_angle:
		    between = start_angle <= phi <= end_angle
		else:
		    between = start_angle <= phi or phi <= end_angle
		center = trafo.offset()
		start = Polar(start_angle)
		end = Polar(end_angle)
		if arc_type == ArcPieSlice:
		    if between:
			# p is somewhere in the painted sector. Just
			# like for a full ellipse:
			if filled and dist <= 1:
			    return 1
			return border_hit
		    else:
			# p is outside of the painted sector. It might
			# still be on the lines:
			if has_line:
			    if (self.LineHit(center, trafo(start), p,
					     line_width)
				or self.LineHit(center, trafo(end), p,
						line_width)):
				return 1
			# no line was hit
			return 0
		else:
		    # ArcArc or ArcChord.
		    if filled:
			# this is identical for both arc_types.
			# Strategy: p is inside of the ellipse if it is
			# in the intersection of the full ellipse the
			# half plane defined by the line through start
			# and end (the plane to the left of the vector
			# (start - end)).
			v = start - end
			d = p2 - end
			in_plane = (v.x * d.y - v.y * d.x) > 0
			if dist <= 1 and in_plane:
			    return 1
		    #
		    if between and border_hit:
			return 1
		    if has_line and arc_type == ArcChord:
			return self.LineHit(trafo(start), trafo(end), p,
					    line_width)
	    return 0
	except SingularMatrix:
	    # ellipse degenerates into a line
	    # XXX we should use the eigenvectors. The following code is
	    # incorrect.
	    start = trafo.offset()
	    right = Point(trafo.m11, trafo.m21)
	    up = Point(trafo.m12, trafo.m22)
	    if abs(up) > abs(right):
		dir = up
	    else:
		dir = right
	    return self.LineHit(start - dir, start + dir, p,
				properties.line_width)

    def MultiBezierHit(self, paths, p, properties, filled,
		       ignore_outline_mode = 0):
	x, y = self.DocToWin(p)
	filled = filled and (ignore_outline_mode or not self.outline_mode)
	result = _sketch.test_transformed(paths, self.doc_to_win, x, y,
					  filled)
	if properties.HasLine():
	    line_width = properties.line_width
	else:
	    line_width = 0
	if result or self.outline_mode or self.LengthToWin(line_width) <= 1:
	    return result
	odtw = self.doc_to_win
	self.doc_to_win = Trafo(odtw.m11, odtw.m21, odtw.m12, odtw.m22,
				-x + pixmap_width_2 + odtw.v1,
				-y + pixmap_width_2 + odtw.v2)
	top_left = self.WinToDoc(x - pixmap_width_2 - 1,
				 y - pixmap_width_2 - 1)
	bot_right = self.WinToDoc(x + pixmap_width_2 + 1,
				  y + pixmap_width_2 + 1)
	rect = Rect(top_left, bot_right)
	self.gc.function = X.GXclear
	self.gc.FillRectangle(0, 0, pixmap_width + 1, pixmap_width + 1)
	self.gc.function = X.GXcopy
	self.fill = 0
	line_width = max(line_width, hit_properties.line_width)
	undo = self.properties.SetProperty(line_width = line_width,
					 line_pattern = SolidPattern(color_1))
	self.MultiBezier(paths, rect)
	self.doc_to_win = odtw
	Undo(undo)
	self.fill = 1
	return _sketch.GetPixel(self.gc, pixmap_width_2, pixmap_width_2)


#
#	Initialization that can only be done after widgets were realized
#

_init_from_widget_done = 0

def check_for_shm_images(widget):
    global shm_images_supported
    shm_images_supported = 0
    try:
        img = widget.ShmCheckExtension()
    except RuntimeError, exc:
        print "Exception in ShmCheckExtension:", exc
        img = None
    if img is not None:
	if img.depth not in (15, 16, 24, 32, 8):
	    # XXX: warn
	    print 'depth =', img.depth
	    return

	if img.format != X.ZPixmap:
	    # XXX: warn
	    print 'format =', img.format
	    return

	shm_images_supported = 1


def InitFromWidget(widget):
    global _init_from_widget_done, use_shm_images
    if not _init_from_widget_done:
	color.InitFromWidget(widget) # make certain that color is initialized
	check_for_shm_images(widget)
	if shm_images_supported:
	    warn(INTERNAL, 'shared memory images supported')
	    use_shm_images = 1
	else:
	    warn(INTERNAL, 'shared memory images not supported')
	    use_shm_images = 0
    _init_from_widget_done = 1

