#
# Copyright (c) 2002, 2003, 2004 Art Haas
#
# This file is part of PythonCAD.
#
# PythonCAD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PythonCAD 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# the GTK code for displaying a drawing
#

from __future__ import division

import math

import pygtk
pygtk.require('2.0')
import gtk
import gobject

import gtkmenus
import gtkdimension
import gtklayers
import gtkpycad
import gtktext

import Generic.image
import Generic.graphicobject
import Generic.point
import Generic.segment
import Generic.circle
import Generic.arc
import Generic.hcline
import Generic.vcline
import Generic.acline
import Generic.cline
import Generic.ccircle
import Generic.conobject
import Generic.color
import Generic.layer
import Generic.segjoint
import Generic.leader
import Generic.polyline
import Generic.text
import Generic.dimension
import Generic.tools
import Generic.globals
import Generic.keywords
import Generic.prompt

#
# Global variables
#

Generic.globals.gtkcolors = {}
Generic.globals.gtklinetypes = {}

def draw_layer(gtkimage, objlist, lyr):
    _conobjs = []
    _vobjs = []
    pts = []
    gc = gtkimage.getGC()
    gc.set_function(gtk.gdk.COPY)
    da = gtkimage.getDA()
    active_layer = gtkimage.getActiveLayer()
    if active_layer is lyr:
        active = True
    else:
        active = False
    window = da.window
    width, height = gtkimage.getSize()
    xmin, ymin, xmax, ymax = gtkimage.getView()
    _upp = gtkimage.getUnitsPerPixel()
    pixmap = gtkimage.getPixmap()
    draw_points = gtkimage.getOption('HIGHLIGHT_POINTS')
    _inactive_layer_color = gtkimage.getOption('INACTIVE_LAYER_COLOR')
    for obj in objlist:
        if isinstance(obj, Generic.conobject.ConstructionObject):
            _conobjs.append(obj)
        elif isinstance(obj, Generic.point.Point):
            pts.append(obj)
        else:
            _vobjs.append(obj)
    #
    # do the construction lines/circles now. They all
    # have the same style bits, so the gc can be set
    # once and used for all the objects
    #

    gc.set_line_attributes(1, gtk.gdk.LINE_DOUBLE_DASH,
                           gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER)
    _constyle = Generic.conobject.ConstructionObject.__defstyle
    _dashlist = _constyle.getLinetype().getList()
    if _dashlist is not None:
        gc.set_dashes(0, _dashlist)
    _cline_color = _constyle.getColor()
    if not active:
        _cline_color = _inactive_layer_color
    if gtkimage.hasGTKColor(_cline_color):
        color = gtkimage.getGTKColor(_cline_color)
    else:
        cstring = str(_cline_color)
        color = da.get_colormap().alloc_color(cstring)
        gtkimage.saveGTKColor(_cline_color, color)
    gc.set_foreground(color)
    gc.set_function(gtk.gdk.COPY)
    _segs = []
    for _cobj in _conobjs:
        if isinstance(_cobj, Generic.hcline.HCLine):
            _x, _y = _cobj.getLocation().getCoords()
            _px, _py = gtkimage.coordToPixTransform(_x, _y)
            _segs.append((0, _py, width, _py))
        elif isinstance(_cobj, Generic.vcline.VCLine):
            _x, _y = _cobj.getLocation().getCoords()
            _px, _py = gtkimage.coordToPixTransform(_x, _y)
            _segs.append((_px, 0, _px, height))
        elif isinstance(_cobj, Generic.acline.ACLine):
            _coords = _cobj.clipToRegion(xmin, ymin, xmax, ymax)
            if _coords is not None:
                _x1, _y1, _x2, _y2 = _coords
                _px1, _py1 = gtkimage.coordToPixTransform(_x1, _y1)
                _px2, _py2 = gtkimage.coordToPixTransform(_x2, _y2)
                _segs.append((_px1, _py1, _px2, _py2))
        elif isinstance(_cobj, Generic.cline.CLine):
            _coords = _cobj.clipToRegion(xmin, ymin, xmax, ymax)
            if _coords is not None:
                _x1, _y1, _x2, _y2 = _coords
                _px1, _py1 = gtkimage.coordToPixTransform(_x1, _y1)
                _px2, _py2 = gtkimage.coordToPixTransform(_x2, _y2)
                _segs.append((_px1, _py1, _px2, _py2))
        elif isinstance(_cobj, Generic.ccircle.CCircle):
            _x, _y = _cobj.getCenter().getCoords()
            _radius = _cobj.getRadius()
            _px, _py = gtkimage.coordToPixTransform(_x, _y)
            _rx, _ry = gtkimage.coordToPixTransform((_x + _radius), _y)
            _pxrad = _rx - _px
            _pxmin = _px - _pxrad
            _pymin = _py - _pxrad
            _cw = _ch = _pxrad * 2
            pixmap.draw_arc(gc, gtk.FALSE, _pxmin, _pymin, _cw, _ch,
                            0, (360*64))
        else:
            pass
    if len(_segs):
        # print "drawing %d construction segments ..." % len(_segs)
        pixmap.draw_segments(gc, _segs)
        del _segs[:]
    if len(pts) and draw_points:
        _single = gtkimage.getOption('SINGLE_POINT_COLOR')
        _multi = gtkimage.getOption('MULTI_POINT_COLOR')
        gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT,
                               gtk.gdk.JOIN_MITER)
        for _pt in pts:
            _users = _pt.getUsers()
            _gc_color = _single
            if len(_users) > 1:
                _nd = 0
                for _uref in _users:
                    _user = _uref()
                    if not isinstance(_user, Generic.dimension.Dimension):
                        _nd = _nd + 1
                    if _nd > 1:
                        _gc_color = _multi
                        break
            if gtkimage.hasGTKColor(_gc_color):
                _color = gtkimage.getGTKColor(_gc_color)
            else:
                cstring = str(_gc_color)
                _color = da.get_colormap().alloc_color(cstring)
                gtkimage.saveGTKColor(_gc_color, _color)
            gc.set_foreground(_color)
            _p1x, _p1y = gtkimage.coordToPixTransform(_pt.x, _pt.y)
            pixmap.draw_rectangle(gc, gtk.FALSE,
                                  (_p1x - 5), (_p1y - 5), 10, 10)
    for obj in _vobjs:
        if (isinstance(obj, Generic.graphicobject.GraphicObject) or
            isinstance(obj, Generic.graphicobject.old_GraphicObject)):
            _dashlist = obj.getLinetype().getList()
            if _dashlist is not None:
                _linetype = gtk.gdk.LINE_DOUBLE_DASH
                gc.set_dashes(0, _dashlist)
            else:
                _linetype = gtk.gdk.LINE_SOLID
            if active:
                _c = obj.getColor()
            else:
                _c = _inactive_layer_color
            if gtkimage.hasGTKColor(_c):
                color = gtkimage.getGTKColor(_c)
            else:
                cstring = str(_c)
                color = da.get_colormap().alloc_color(cstring)
                gtkimage.saveGTKColor(_c, color)
            # _t = obj.getThickness()
            _thickness = int(obj.getThickness()/_upp)
            if _thickness == 0:
                _thickness = 1
            gc.set_foreground(color)
            gc.set_line_attributes(_thickness, _linetype,
                                   gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER)
        if isinstance(obj, Generic.segment.Segment):
            _coords = obj.clipToRegion(xmin, ymin, xmax, ymax)
            if _coords is not None:
                _x1, _y1, _x2, _y2 = _coords
                _p1x, _p1y = gtkimage.coordToPixTransform(_x1, _y1)
                _p2x, _p2y = gtkimage.coordToPixTransform(_x2, _y2)
                pixmap.draw_line(gc, _p1x, _p1y, _p2x, _p2y)
        elif isinstance(obj, Generic.arc.Arc):
            _x, _y = obj.getCenter().getCoords()
            _r = obj.getRadius()
            _px, _py = gtkimage.coordToPixTransform(_x, _y)
            _rx, _ry = gtkimage.coordToPixTransform((_x + _r), _y)
            _radius = _rx - _px
            _pxmin = _px - _radius
            _pymin = _py - _radius
            _cw = _ch = _radius * 2
            _sa = obj.getStartAngle()
            _ea = obj.getEndAngle()
            if abs(_sa - _ea) < 1e-10:
                pixmap.draw_arc(gc, gtk.FALSE, _pxmin, _pymin, _cw, _ch,
                                0, (360*64))
            else:
                if _sa > _ea:
                    _sweep = 360.0 - (_sa - _ea)
                else:
                    _sweep = _ea - _sa
                pixmap.draw_arc(gc, gtk.FALSE, _pxmin, _pymin, _cw, _ch,
                                int(_sa * 64), int(_sweep * 64))
        elif isinstance(obj, Generic.circle.Circle):
            _x, _y = obj.getCenter().getCoords()
            _r = obj.getRadius()
            _px, _py = gtkimage.coordToPixTransform(_x, _y)
            _rx, _ry = gtkimage.coordToPixTransform((_x + _r), _y)
            _radius = _rx - _px
            _pxmin = _px - _radius
            _pymin = _py - _radius
            _cw = _ch = _radius * 2
            pixmap.draw_arc(gc, gtk.FALSE, _pxmin, _pymin, _cw, _ch,
                            0, (360*64))
        elif isinstance(obj, Generic.segjoint.Chamfer):
            _p1, _p2 = obj.getMovingPoints()
            _p1x, _p1y = gtkimage.coordToPixTransform(_p1.x, _p1.y)
            _p2x, _p2y = gtkimage.coordToPixTransform(_p2.x, _p2.y)
            pixmap.draw_line(gc, _p1x, _p1y, _p2x, _p2y)
        elif isinstance(obj, Generic.segjoint.Fillet):
            _cx, _cy = obj.getCenter()
            _pcx, _pcy = gtkimage.coordToPixTransform(_cx, _cy)
            _p1, _p2 = obj.getMovingPoints()
            _p1x, _p1y = gtkimage.coordToPixTransform(_p1.x, _p1.y)
            _p2x, _p2y = gtkimage.coordToPixTransform(_p2.x, _p2.y)
            _r = obj.getRadius()
            _rx, _ry = gtkimage.coordToPixTransform((_cx + _r), _cy)
            _radius = _rx - _pcx
            print "radius: %d" % _radius
            _pxmin = _pcx - _radius
            _pymin = _pcy - _radius
            _cw = _ch = _radius * 2
            _sa1, _sa2 = obj.getAngles()
            _amin = min(_sa1, _sa2)
            _amax = max(_sa1, _sa2)
            if _amax - _amin > 180.0:
                _a1 = _amax
                _a2 = _amin
            else:
                _a1 = _amin
                _a2 = _amax
            print "a1: %g" % _a1
            print "a2: %g" % _a2
            if _a1 > _a2:
                _sweep = 360 - (_a1 - _a2)
            else:
                _sweep = _a2 - _a1
            pixmap.draw_arc(gc, gtk.FALSE, _pxmin, _pymin, _cw, _ch,
                            int(_a1 * 64), int(_sweep * 64))
        elif isinstance(obj, Generic.leader.Leader):
            _pts = []
            _p1, _p2, _p3 = obj.getPoints()
            _p1x, _p1y = gtkimage.coordToPixTransform(_p1.x, _p1.y)
            _pts.append((_p1x, _p1y))
            _p2x, _p2y = gtkimage.coordToPixTransform(_p2.x, _p2.y)
            _pts.append((_p2x, _p2y))
            _p3x, _p3y = gtkimage.coordToPixTransform(_p3.x, _p3.y)
            _pts.append((_p3x, _p3y))
            pixmap.draw_lines(gc, _pts)
            _apts = []
            _apts.append((_p3x, _p3y))
            _pts = obj.getArrowPoints()
            _a1x, _a1y = gtkimage.coordToPixTransform(_pts[0], _pts[1])
            _apts.append((_a1x, _a1y))
            _a2x, _a2y = gtkimage.coordToPixTransform(_pts[2], _pts[3])
            _apts.append((_a2x, _a2y))
            pixmap.draw_polygon(gc, gtk.TRUE, _apts)
        elif isinstance(obj, Generic.polyline.Polyline):
            _pts = []
            for _pt in obj.getPoints():
                _x, _y = _pt.getCoords()
                _px, _py = gtkimage.coordToPixTransform(_x, _y)
                _pts.append((_px, _py))
            pixmap.draw_lines(gc, _pts)
        elif isinstance(obj, Generic.dimension.HorizontalDimension):
            gtkdimension.draw_linear_dimension(gtkimage, obj, active)
        elif isinstance(obj, Generic.dimension.VerticalDimension):
            gtkdimension.draw_linear_dimension(gtkimage, obj, active)
        elif isinstance(obj, Generic.dimension.LinearDimension):
            gtkdimension.draw_linear_dimension(gtkimage, obj, active)
        elif isinstance(obj, Generic.dimension.RadialDimension):
            gtkdimension.draw_radial_dimension(gtkimage, obj, active)
        elif isinstance(obj, Generic.dimension.AngularDimension):
            gtkdimension.draw_angular_dimension(gtkimage, obj, active)
        elif isinstance(obj, Generic.text.TextBlock):
            gtktext.draw_textblock(gtkimage, obj, active)
        else:
            print "skipping obj: " + `obj`

    #
    # reset line thickness to 1
    #

    gc.set_line_attributes(1, gtk.gdk.LINE_DOUBLE_DASH,
                           gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER)

def da_expose_event(widget, event, gtkimage):
    _pixmap = gtkimage.getPixmap()
    _x, _y, _width, _height = event.area
    _gc = widget.get_style().fg_gc[gtk.STATE_NORMAL]
    # _gc.set_function(gtk.gdk.COPY)
    widget.window.draw_drawable(_gc, _pixmap, _x, _y, _x, _y, _width, _height)
    return gtk.TRUE

def da_realize_event(widget, gtkimage):
    _win = widget.window
    _width, _height = _win.get_size()
    gtkimage.setSize(_width, _height)
    widget.set_size_request(100,100)
    _gc = _win.new_gc()
    _gc.set_exposures(gtk.TRUE)
    gtkimage.setGC(_gc)

def da_configure_event(widget, event, gtkimage):
    _win = widget.window
    _width, _height = _win.get_size()
    _disp_width, _disp_height = gtkimage.getSize()
    if _disp_width != _width or _disp_height != _height:
        gtkimage.setSize(_width, _height)
        _pixmap = gtk.gdk.Pixmap(_win, _width, _height)
        _gc = widget.get_style().fg_gc[gtk.STATE_NORMAL]
        _pixmap.draw_rectangle(_gc, gtk.TRUE, 0, 0, _width, _height)
        gtkimage.setPixmap(_pixmap)
        _xmin, _ymin = gtkimage.getView()[:2]
        if _xmin is None or _ymin is None:
            _xmin = 1.0
            _ymin = 1.0
        _upp = gtkimage.getUnitsPerPixel()
        gtkimage.setView(_xmin, _ymin, _upp)
        gtkimage.redraw()
    return gtk.TRUE

def set_toolpoint(gtkimage, event):
    _x = event.x
    _y = event.y
    _tx, _ty = gtkimage.pixToCoordTransform(_x, _y)
    gtkimage.setPoint(_tx, _ty)

def da_general_event(widget, event, gtkimage):
    _type = event.type
    _tool = gtkimage.getTool()
    if _type == gtk.gdk.BUTTON_PRESS:
        set_toolpoint(gtkimage, event)
        button = event.button
        if button == 1:
            if _tool is not None and _tool.hasHandler("button_press"):
                handler = _tool.getHandler("button_press")
                handler(gtkimage, widget, event, _tool)
        elif button == 2:
            # print "button2 press event"
            pass
        elif button == 3:
            # print "button3 press event"
            pass
    elif _type == gtk.gdk.BUTTON_RELEASE:
        set_toolpoint(gtkimage, event)
        button = event.button
        if button == 1:
            if _tool is not None and _tool.hasHandler("button_release_event"):
                handler = _tool.getHandler("button_release_event")
                handler(gtkimage, widget, event, _tool)
    elif _type == gtk.gdk.MOTION_NOTIFY:
        set_toolpoint(gtkimage, event)
        if _tool is not None and _tool.hasHandler("motion_notify"):
            handler = _tool.getHandler("motion_notify")
            handler(gtkimage, widget, event, _tool)
    elif _type == gtk.gdk.KEY_PRESS:
        _key = event.keyval
        if (_key == gtk.keysyms.Page_Up or
            _key == gtk.keysyms.Page_Down or
            _key == gtk.keysyms.Left or
            _key == gtk.keysyms.Right or
            _key == gtk.keysyms.Up or
            _key == gtk.keysyms.Down):
            # print "Got special key"
            pass # handle moving the drawing in some fashion ...
        elif _key == gtk.keysyms.Escape:
            gtkimage.reset()
        else:
            _entry = gtkimage.getEntry()
            _entry.grab_focus()
            if _key == gtk.keysyms.Tab:
                return gtk.TRUE
            else:
                return _entry.event(event)
    elif _type == gtk.gdk.ENTER_NOTIFY:
        set_toolpoint(gtkimage, event)
    elif _type == gtk.gdk.LEAVE_NOTIFY:
        set_toolpoint(gtkimage, event)
    else:
        # print "Got type %d" % _type
        pass
    return gtk.FALSE

def da_focus_in_event_cb(widget, event, gtkimage):
    print "in da_focus_in_event_cb() ..."
    return gtk.FALSE

def da_focus_out_event_cb(widget, event, gtkimage):
    print "in da_focus_out_event_cb() ..."
    return gtk.FALSE

def entry_event(widget, gtkimage):
    print "entry_event() ..."
    _entry = gtkimage.getEntry()
    _text = _entry.get_chars(0,-1).strip()
    if len(_text):
        _text = _text.lower()
        if _text == 'end' or _text == 'stop':
            _entry.delete_text(0,-1)
            gtkimage.reset()
        else:
            _tool = gtkimage.getTool()
            if _tool is not None and _tool.hasHandler("entry_event"):
                print "getting tool handler ..."
                handler = _tool.getHandler("entry_event")
                try:
                    handler(gtkimage, widget, _tool)
                except: # need better error handling ...
                    print "exception called ..."
                    pass
            else:
                _cmds = Generic.keywords.defaultglobals
                _entry.delete_text(0,-1)
                # print "text is '%s'" % _text
                if _text in _cmds:
                    # print "valid command"
                    _opt = _cmds[_text]
                    # print "opt: '%s'" % _opt
                    _cmd = Generic.prompt.lookup(_opt)
                    # print "cmd: '%s'" % _cmd
                    eval(_cmd) # this will go away ...
                else:
                    print "Command Error; See http://www.pythoncad.org/commands.html for reference page."
    #
    # set the focus back to the TreeView widget
    #
    gtkimage.getTreeView().grab_focus()
    return gtk.FALSE

def tree_view_key_press_cb(widget, event, gtkimage):
    _key = event.keyval
    if (_key == gtk.keysyms.Page_Up or
        _key == gtk.keysyms.Page_Down or
        _key == gtk.keysyms.Left or
        _key == gtk.keysyms.Right or
        _key == gtk.keysyms.Up or
        _key == gtk.keysyms.Down):
        # print "Got special key"
        pass # need to handle adjusting view in drawing area
    elif _key == gtk.keysyms.Tab:
        # print "got tab key ..."
        gtkimage.getDA().grab_focus()
        return gtk.TRUE
    elif _key == gtk.keysyms.Escape:
        gtkimage.reset()
    else:
        _entry = gtkimage.getEntry()
        _entry.grab_focus()
        return _entry.event(event)
    return gtk.FALSE

def close_image(widget, gtkimage):
    """Close a window containing a GTKImage.

close_image(gtkimage)
    """
    gtkimage.close()
    gtkpycad.del_image(gtkimage)
    return gtk.FALSE

def tree_view_button_cb(widget, event, gtkimage):
    _retval = gtk.FALSE
    _button = event.button
    _x = int(event.x)
    _y = int(event.y)
    if event.window is widget.get_bin_window():
        _model = widget.get_model()
        _path, _col, _cx, _cy = widget.get_path_at_pos(_x, _y)
        _iter = _model.get_iter(_path)
        _layer = _model.get_value(_iter, 1)
        if _button == 3:
            _menu = gtklayers.make_layer_menu(gtkimage, _layer)
            _menu.popup(None, None, None, _button, event.time)
            _retval = gtk.TRUE
    return _retval

def reordered_cb(model, path, iter, new_order):
    print "in reordered_cb()"
    print "model: " + `model`
    print "path: " + `path`
    print "iter: " + `iter`
    print "new_order: " + `new_order`

# def columns_changed_cb(tree_view, col, prev_col, next_col, gtkimage):
def columns_changed_cb(tree_view, gtkimage):
    print "in columns_changed_cb()"
    print "tree_view: " + `tree_view`
    # print "col: " + `col`
    # print "prev_col: " + `prev_col`
    # print "next_col: " + `next_col`
    # return gtk.FALSE

def cell_edited_cb(cell_renderer, path, text, model):
    _iter = model.get_iter_from_string(path)
    _lyr = model.get_value(_iter, 1)
    _lyr.setName(text)
    model.set(_iter, 0, text)

def tree_select_cb(selection, gtkimage):
    if selection is not None:
        _model, _iter = selection.get_selected()
        if _iter is not None:
            _lyr = _model.get_value(_iter, 1)
            gtkimage.setActiveLayer(_lyr)

def model_find_layer(model, lyr, iter=None):
    _iter = iter
    if _iter is None:
        _iter = model.get_iter_first()
    _path = None
    _mlyr = model.get_value(_iter, 1)
    print "model layer: " + _mlyr.getName()
    if _mlyr is lyr:
        _path = model.get_path(_iter)
    else:
        if model.iter_has_child(_iter):
            _child = model.iter_children(_iter)
            while _child is not None:
                _path = model_find_layer(model, lyr, _child)
                if _path is not None:
                    break
                _child = model.iter_next(_child)
    return _path

def model_add_layer(model, path, iter, lyr):
    _parent = lyr.getParentLayer()
    _mlayer = model.get_value(iter, 1)
    _val = gtk.FALSE
    if _mlayer is _parent:
        _iter = model.append(iter)
        model.set(_iter, 0, lyr.getName())
        model.set(_iter, 1, lyr)
        _val = gtk.TRUE
    return _val

def model_del_layer(model, path, iter, lyr):
    _mlayer = model.get_value(iter, 1)
    _val = gtk.FALSE
    if _mlayer is lyr:
        model.remove(iter)
        _val = gtk.TRUE
    return _val

def model_rename_layer(model, path, iter, lyr):
    _mlayer = model.get_value(iter, 1)
    _val = gtk.FALSE
    if _mlayer is lyr:
        model.set(iter, 0, lyr.getName())
        _val = gtk.TRUE
    return _val

def _test_focus(widget):
    if isinstance(widget, gtk.Container):
        print "container: " + `widget`
        widget.foreach(_test_focus)
    else:
        print "widget: " + `widget`
        if (widget.flags() & gtk.CAN_FOCUS):
            print "widget can accept focus"
            if widget.is_focus():
                print "is focus widget"
            else:
                print "not focus widget"
        else:
            print "widget can not accept focus"

class GTKImage(Generic.image.Image):
    """The GTK wrapping around an Image

The GTKImage class is derived from the Image class, so it shares all
the attributes and methods of that class. The GTKImage class has the
following addtional methods:

close(): Close a GTKImage.
getWindow(): Get the GTK Window used in the GTKImage.
getEntry(): Get the GTK Entry used in the GTKImage.
getTreeView(): Get the GTK TreeView used in the GTKImage.
getDA(): Get the GTK Drawing Area used in the GTKImage.
{get/set}Pixmap(): Get/Set the GTK Pixmap used in the GTKImage.
{get/set}Prompt(): Get/Set the prompt.
{get/set}Tool(): Get/Set the tool used for working in the GTKImage.
{get/set}UnitsPerPixel(): Get/Set this display parameter.
{get/set}View(): Get/Set the current view seen in the GTKImage.
getTolerance(): Get the current drawing tolerance.
{get/set}GC(): Get/Set the graphic context used in the GTKImage.
{get/set}Point(): Get/Set the current coordinates of the tool.
{get/set}Size(): Get/Set the size of the drawing area.
pixToCoordTransform(): Convert pixels to x/y coordinates.
coordToPixTransform(): Convert x/y coordinates to pixels.
{has/get/save}GTKColor(): Functions for dealing with allocated GTK colors.
refresh(): Redraw the screen using the current pixmap.
redraw(): Recalculate the visible entities and redraw the screen.
    """
    def __init__(self):
        Generic.image.Image.__init__(self)
        self.__window = gtk.Window()
        self.__window.set_title("Untitled")
        self.__window.connect("destroy", close_image, self)
        _width = min(1024, int(0.8 * float(gtk.gdk.screen_width())))
        _height = min(768, int(0.8 * float(gtk.gdk.screen_height())))
        self.__window.set_default_size(_width, _height)

        main_vbox = gtk.VBox(gtk.FALSE, 2)
        main_vbox.set_border_width(2)
        self.window.add(main_vbox)

        #
        # accelerators
        #

        self.__accel = gtk.AccelGroup()
        self.__window.add_accel_group(self.__accel)

        #
        # menu bar
        #

        self.__mb = gtk.MenuBar()
        main_vbox.pack_start(self.__mb, gtk.FALSE, gtk.FALSE)

        self.__mdict = gtkmenus.fill_menubar(self.__mb, self)

        #
        # drawing window has Horizontal Pane:
        # left side: stuff for layer display
        # right side: drawing area
        #

        pane = gtk.HPaned()
        main_vbox.pack_start(pane)

        frame1 = gtk.Frame()
        pane.pack1(frame1, gtk.TRUE, gtk.FALSE)
        pane.set_position(100)

        #
        # layer display stuff
        #

        self.__sw = gtk.ScrolledWindow()
        self.__sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)

        self.__model = gtk.TreeStore(gobject.TYPE_STRING,
                                     gobject.TYPE_PYOBJECT)
        self.__model.connect("rows-reordered", reordered_cb)
        iter = self.__model.append(None)
        top_layer = self.getTopLayer()
        self.__model.set(iter, 0, top_layer.getName())
        self.__model.set(iter, 1, top_layer)
        self.__tree_view = gtk.TreeView(self.__model)
        self.__tree_view.set_reorderable(gtk.TRUE) # drag-and-drop
        # self.__tree_view.connect("columns-changed",
                                 # columns_changed_cb,
                                 # self)
        self.__tree_view.connect("button_press_event",
                                 tree_view_button_cb, self)
        self.__tree_view.connect("key_press_event",
                                 tree_view_key_press_cb, self)
        # self.__tree_view.set_headers_visible(gtk.FALSE)
        # self.__tree_view.expand_all()
        select = self.__tree_view.get_selection()
        select.set_mode(gtk.SELECTION_SINGLE)
        select.connect("changed", tree_select_cb, self)
        renderer = gtk.CellRendererText()
        # renderer.set_property("editable", gtk.TRUE)
        renderer.connect("edited", cell_edited_cb, self.__model)
        column = gtk.TreeViewColumn("Layers", renderer, text=0)
        self.__tree_view.append_column(column)

        self.__sw.add(self.__tree_view)
        frame1.add(self.__sw)

        #
        # drawing area
        #

        self.__disp_width = None
        self.__disp_height = None
        self.__units_per_pixel = 1.0
        self.__da = gtk.DrawingArea()

        black = gtk.gdk.color_parse('black')
        self.__da.modify_fg(gtk.STATE_NORMAL, black)
        self.__da.modify_bg(gtk.STATE_NORMAL, black)
        pane.pack2(self.__da, gtk.TRUE, gtk.FALSE)
        self.__da.set_flags(gtk.CAN_FOCUS)

        self.__da.connect("event", da_general_event, self)
        self.__da.connect("expose_event", da_expose_event, self)
        self.__da.connect("realize", da_realize_event, self)
        self.__da.connect("configure_event", da_configure_event, self)
        # self.__da.connect("focus_in_event", da_focus_in_event_cb, self)
        # self.__da.connect("focus_out_event", da_focus_out_event_cb, self)

        self.__da.set_events(gtk.gdk.EXPOSURE_MASK |
                             gtk.gdk.LEAVE_NOTIFY_MASK |
                             gtk.gdk.BUTTON_PRESS_MASK |
                             gtk.gdk.BUTTON_RELEASE_MASK |
                             gtk.gdk.ENTER_NOTIFY_MASK|
                             gtk.gdk.LEAVE_NOTIFY_MASK|
                             gtk.gdk.KEY_PRESS_MASK |
                             gtk.gdk.KEY_RELEASE_MASK |
                             gtk.gdk.FOCUS_CHANGE_MASK |
                             gtk.gdk.POINTER_MOTION_MASK)

        lower_hbox = gtk.HBox(gtk.FALSE, 2)
        main_vbox.pack_start(lower_hbox, gtk.FALSE, gtk.FALSE)

        self.__prompt = gtk.Label("Enter Command:")
        lower_hbox.pack_start(self.__prompt, gtk.FALSE, gtk.FALSE)

        #
        # where the action is taking place
        #

        self.__coords = gtk.Label('(0,0)')
        lower_hbox.pack_end(self.__coords, gtk.FALSE, gtk.FALSE)

        self.__point_x = 0.0
        self.__point_y = 0.0

        #
        # command entry area
        #

        self.__entry = gtk.Entry()
        main_vbox.pack_start(self.__entry, gtk.FALSE, gtk.FALSE)
        self.entry.connect("activate", entry_event, self)

        #
        # the Pixmap and GraphicContext for the drawing
        #

        self.__pixmap = None
        self.__gc = None

        #
        # our tool used to draw stuff
        #

        self.__tool = None
        self.__oldtool = Generic.tools.Tool()

        #
        # the viewable region and tolerance in the drawing
        #

        self.__xmin = None
        self.__ymin = None
        self.__xmax = None
        self.__ymax = None
        self.__tolerance = 1e-10

        #
        # set the background color
        #

        # self.setOption('BACKGROUND_COLOR', Generic.color.Color(0,0,0))

        # _test_focus(self.__window)

    def close(self):
        """Release the entites stored in the drawing.

close()
        """
        Generic.image.Image.close(self)
        self.__window.destroy()

    def getAccel(self):
        """Return the gtk.AccelGroup in the GTKImage.

getAccel()
        """
        return self.__accel

    accel = property(getAccel, None, None, "Accel group in the GTKImage.")

    def getWindow(self):
        """Return the gtk.Window in the GTKImage.

getWindow()
        """
        return self.__window

    window = property(getWindow, None, None, "Main GTK Window for a GTKImage.")

    def getEntry(self):
        """Return the gtk.Entry in the GTKImage.

getEntry()
        """
        return self.__entry

    entry = property(getEntry, None, None, "Entry box for a GTKImage.")

    def getTreeView(self):
        """Return the gtk.TreeView in the GTKImage.

getTreeView()
        """
        return self.__tree_view

    tree_view = property(getTreeView, None, None, "TreeView for a GTKImage.")

    def getDA(self):
        """Return the gtk.DrawingArea in the GTKImage.

getDA()
        """
        return self.__da

    da = property(getDA, None, None, "DrawingArea for a GTKImage.")

    def getPixmap(self):
        """Return the Pixmap for the GTKImage.

getPixmap()
        """
        return self.__pixmap

    def setPixmap(self, pixmap):
        """Set the Pixmap for the GTKImage.

setPixmap(pixmap)
        """
        self.__pixmap = pixmap

    pixmap = property(getPixmap, setPixmap, None, "Pixmap for a GTKImage.")

    def getPrompt(self):
        """Return the current prompt string.

getPrompt()
        """
        return self.__prompt.get_label()

    def setPrompt(self, prompt):
        """Set the current prompt string.

setPrompt(prompt)
        """
        if not isinstance(prompt, (str, unicode)):
            raise TypeError, "Invalid prompt: " + `prompt`
        self.__prompt.set_text(prompt)

    prompt = property(getPrompt, setPrompt, None, "Prompt string.")

    def setTool(self, tool):
        """Replace the tool in the image with a new Tool.

setTool(tool)

The argument "tool" should be an instance of a Tool object.
        """
        if not isinstance(tool, Generic.tools.Tool):
            raise TypeError, "Invalid tool: " + str(tool)
        self.__tool = tool

    def getTool(self):
        """Return the current Tool used in the drawing.

getTool()
        """
        return self.__tool

    tool = property(getTool, None, None, "Tool for adding/modifying entities.")

    def setOption(self, key, value):
        """

setOption(key, value)

The "key" must be a string, and "value" can be anything.
Using the same key twice will result on the second value
overwriting the first.

This method extends the Generic.image.Image::setOption() method and
handles options that are related to the graphical appearance
of the image.
        """
        Generic.image.Image.setOption(self, key, value)
        if key == 'BACKGROUND_COLOR':
            _bcol = str(value)
            _col = gtk.gdk.color_parse(_bcol)
            self.__da.modify_fg(gtk.STATE_NORMAL, _col)
            self.__da.modify_bg(gtk.STATE_NORMAL, _col)
            self.redraw()

    def getUnitsPerPixel(self):
        """Return the current value of units/pixel.

getUnitsPerPixel()
        """
        return self.__units_per_pixel

    def setUnitsPerPixel(self, upp):
        """Set the current value of units/pixel.

setUnitsPerPixel(upp)

The argument "upp" should be a positive float value.
        """
        _upp = upp
        if not isinstance(_upp, float):
            _upp = float(upp)
        if _upp < 1e-10:
            raise ValueError, "Invalid scale value: %g" % _upp
        self.__units_per_pixel = _upp
        self.__tolerance = _upp * 5.0

    def setView(self, xmin, ymin, scale=None):
        """Set the current visible area in a drawing.

setView(xmin, ymin[, scale])

xmin: Minimum visible x-coordinate
ymin: Minimum visible y-coordinate

The optional argument "scale" defaults to the current
value of units/pixel (set the {get/set}UnitsPerPixel()
method. This value must be a positive float.
        """
        _xmin = xmin
        if not isinstance(_xmin, float):
            _xmin = float(xmin)
        _ymin = ymin
        if not isinstance(_ymin, float):
            _ymin = float(ymin)
        _scale = scale
        if _scale is None:
            _scale = self.__units_per_pixel
        if not isinstance(_scale, float):
            _scale = float(scale)
        if _scale < 1e-10:
            raise ValueError, "Invalid scale value: %g" % _scale
        _xmax = _xmin + (_scale * self.__disp_width)
        _ymax = _ymin + (_scale * self.__disp_height)
        if _scale != self.__units_per_pixel:
            self.__units_per_pixel = _scale
        self.__tolerance = self.__units_per_pixel * 5.0
        self.__xmin = _xmin
        self.__ymin = _ymin
        self.__xmax = _xmax
        self.__ymax = _ymax

    def getView(self):
        """Return the current visible area in a drawing.

getView()

This method returns a tuple with four float values:

(xmin, ymin, xmax, ymax)

If the view has never been set, each of these values
will be None.
        """
        return (self.__xmin, self.__ymin, self.__xmax, self.__ymax)

    view = property(getView, setView, None, "The visible area in a drawing.")

    def setActiveLayer(self, l=None):
        """Set the active Layer in the Image.

setActiveLayer(l)

This method extends the image::setActiveLayer() method.
        """
        try:
            Generic.image.Image.setActiveLayer(self, l)
            self.redraw()
        except: # need better error handling
            return
        model = self.__model
        path = model_find_layer(model, l)
        assert path is not None, "Didn't find Layer in model!"
        ppath = path[:-1]
        while len(ppath):
            if not self.__tree_view.row_expanded(ppath):
                self.__tree_view.expand_row(ppath, gtk.FALSE)
            ppath = ppath[:-1]
        select = self.__tree_view.get_selection()
        select.select_path(path)

    def addLayer(self, l):
        """Add a new Layer as a Child of the active layer's parent.

addLayer(l)

This method extends the image::addLayer() method.
        """
        try:
            Generic.image.Image.addLayer(self, l)
        except: # need better error handling
            return
        model = self.__model
        model.foreach(model_add_layer, l)
        path = model_find_layer(model, l)
        assert path is not None, "Didn't find Layer in model!"
        ppath = path[:-1]
        while len(ppath):
            if not self.__tree_view.row_expanded(ppath):
                self.__tree_view.expand_row(ppath, gtk.FALSE)
            ppath = ppath[:-1]
        select = self.__tree_view.get_selection()
        select.select_path(path)

    def addChildLayer(self, l, p=None):
        """Add a new Layer as a child to another layer.

addChildLayer(l [, p])

There is a one required argument:

l: The child Layer

There is one optional argument:

p: The new parent Layer of the child

The default parent is the currently active layer.

This method extends the image::addChildLayer() method.
        """
        try:
            Generic.image.Image.addChildLayer(self, l, p)
        except: # need better error handling ...
            return
        _model = self.__model
        _model.foreach(model_add_layer, l)
        _path = model_find_layer(_model, l)
        assert _path is not None, "Didn't find Layer in model!"
        _ppath = _path[:-1]
        while len(_ppath):
            if not self.__tree_view.row_expanded(_ppath):
                self.__tree_view.expand_row(_ppath, gtk.FALSE)
            _ppath = _ppath[:-1]
        _select = self.__tree_view.get_selection()
        _select.select_path(_path)

    def delLayer(self, l):
        """Remove a Layer from the drawing.

delLayer(l)

This method extends the image::delLayer() method.
        """
        try:
            Generic.image.Image.delLayer(self, l)
        except:
            return
        model = self.__model
        path = model_find_layer(model, l)
        assert path is not None, "Didn't find Layer in model!"
        ppath = path[:-1]
        while len(ppath):
            if not self.__tree_view.row_expanded(ppath):
                self.__tree_view.expand_row(ppath, gtk.FALSE)
            ppath = ppath[:-1]
        select = self.__tree_view.get_selection()
        select.select_path(path[:-1])
        model.foreach(model_del_layer, l)

    def renameLayer(self, layer):
        """Rename a Layer in the drawing.

renameLayer(layer)

This method is used to update the Layer display
        """
        if not isinstance(layer, Generic.layer.Layer):
            raise TypeError, "Invalid layer: " + `layer`
        self.__model.foreach(model_rename_layer, layer)

    def addObject(self, obj, l=None):
        """Add an object to the Drawing.

addObject(obj [, l])

This method extends the image::addObject() method.
        """
        try:
            Generic.image.Image.addObject(self, obj, l)
        except StandardError, e:
            print "Failed to add object: " + `obj` + ": " + str(e)
        #
        # it may be good to call redraw() here instead of
        # the multitude of places where it is called now ...
        #
        # self.redraw()

    def delObject(self, obj, l=None):
        """Remove an object from the Drawing.

delObject(obj [, l])

This method extends the image::delObject() method.
        """
        try:
            Generic.image.Image.delObject(self, obj, l, inc)
        except StandardError, e:
            print "Failed to delete object: " + `obj` + str(e)

    def getTolerance(self):
        """Return the current drawing tolerance.

getTolerance()
        """
        return self.__tolerance

    tolerance = property(getTolerance, None, None, "Drawing tolerance.")

    def getGC(self):
        """Return the GraphicContext allocated for the GTKImage.

getGC()
        """
        return self.__gc

    def setGC(self, gc):
        """Set the GraphicContext for the GTKImage.

setGC(gc)
        """
        if not isinstance(gc, gtk.gdk.GC):
            raise TypeError, "Invalid GC object: " + `gc`
        if self.__gc is None:
            self.__gc = gc

    gc = property(getGC, None, None, "GraphicContext for the GTKImage.")

    def getPoint(self):
        """Get the current point where the tool is located.

getPoint()

This function returns a tuple with two floats

(x,y)

x: x-coordinate
y: y-coordinate
        """
        return (self.__point_x, self.__point_y)

    def setPoint(self, x, y):
        """Store the point where the tool currently is located at.

setPoint(x,y)

The x and y arguments should be floats.
        """
        _x = x
        _y = y
        try:
            if not isinstance(_x, float):
                _x = float(x)
            if not isinstance(_y, float):
                _y = float(y)
            cstring = "%.4f, %.4f" % (_x,_y)
            self.__coords.set_text(cstring)
            self.__point_x = _x
            self.__point_y = _y
        except: # need better error handling
            pass

    point = property(getPoint, setPoint, None, "Current tool location.")

    def getSize(self):
        """Return the size of the DrawingArea window.

getSize()
        """
        return (self.__disp_width, self.__disp_height)

    def setSize(self, width, height):
        """Set the size of the DrawingArea window.

setSize(width, height)
        """
        _width = width
        if not isinstance(_width, int):
            _width = int(width)
        if _width < 0:
            raise ValueError, "Invalid drawing area width: %d" % _width
        _height = height
        if not isinstance(_height, int):
            _height = int(height)
        if _height < 0:
            raise ValueError, "Invalid drawing area height: %d" % _height
        self.__disp_width = _width
        self.__disp_height = _height

    def pixToCoordTransform(self, xp, yp):
        """Convert from pixel coordinates to x-y coordinates.

pixToCoordTransform(xp, yp)

The function arguments are:

xp: pixel x value
yp: pixel y value

The function returns a tuple holding two float values
        """
        _upp = self.__units_per_pixel
        _xc = self.__xmin + (xp * _upp)
        _yc = self.__ymax - (yp * _upp)
        return (_xc, _yc)

    def coordToPixTransform(self, xc, yc):
        """Convert from x-y coordinates to pixel coordinates

coordToPixTransform(xc, yc)

The function arguments are:

xc: x coordinate
yp: y coordinate

The function returns a tuple holding two integer values
        """
        _upp = self.__units_per_pixel
        _xp = int((xc - self.__xmin)/_upp)
        _yp = int((self.__ymax - yc)/_upp)
        return _xp, _yp

    def saveGTKColor(self, color, gtkcolor):
        """Store an allocated color.

saveGTKColor(color, gtkcolor)

color: A Color object
gtkcolor: The allocated color returned from GTK
        """
        if not isinstance(color, Generic.color.Color):
            raise TypeError, "Invalid Color object: " + `color`
        if color not in Generic.globals.gtkcolors:
            Generic.globals.gtkcolors[color] = gtkcolor

    def getGTKColor(self, color):
        """Retrieve the allocated GTK color for a Color object.

getGTKColor(color)

Argument "color" must be a Color object. This method returns None
if there is not a stored GTK color for the Color object.
        """
        if not isinstance(color, Generic.color.Color):
            raise TypeError, "Invalid Color object: " + `color`
        _val = None
        if Generic.globals.gtkcolors.has_key(color):
            _val = Generic.globals.gtkcolors[color]
        return _val

    def hasGTKColor(self, color):
        """See if an allocated GTK color exists for a Color object.

hasGTKColor(color)

Argument "color" must be a Color object.
        """
        if not isinstance(color, Generic.color.Color):
            raise TypeError, "Invalid Color object: " + `color`
        return color in Generic.globals.gtkcolors

    def fitImage(self):
        """Redraw the image so all entities are visible in the window.

fitImage()
        """
        _fw = float(self.__disp_width)
        _fh = float(self.__disp_height)
        _xmin, _ymin, _xmax, _ymax = self.getExtents()
        _xdiff = abs(_xmax - _xmin)
        _ydiff = abs(_ymax - _ymin)
        _xmid = (_xmin + _xmax)/2.0
        _ymid = (_ymin + _ymax)/2.0
        _xs = _xdiff/_fw
        _ys = _ydiff/_fh
        if _xs > _ys:
            _scale = _xs * 1.05 # make it a little larger
        else:
            _scale = _ys * 1.05 # make it a little larger
        _xm = _xmid - (_fw/2.0) * _scale
        _ym = _ymid - (_fh/2.0) * _scale
        self.setView(_xm, _ym, _scale)
        self.redraw()

    def getMenuKey(self, menuitem):
        """
        """
        _mid = id(menuitem)
        return self.__mdict.get(_mid)
    
    def refresh(self):
        """This method does a screen refresh.

refresh()

If entities in the drawing have been added, removed, or
modified, use the redraw() method.
        """
        _da = self.__da
        if (_da.flags() & gtk.MAPPED):
            _gc = _da.get_style().fg_gc[gtk.STATE_NORMAL]
            _gc.set_function(gtk.gdk.COPY)
            _da.queue_draw()

    def redraw(self):
        """This method draws all the objects visible in the window.

redraw()
        """
        _da = self.__da
        if (_da.flags() & gtk.MAPPED):
            _xmin = self.__xmin
            _ymin = self.__ymin
            _xmax = self.__xmax
            _ymax = self.__ymax
            _gc = _da.get_style().fg_gc[gtk.STATE_NORMAL]
            self.__pixmap.draw_rectangle(_gc, gtk.TRUE, 0, 0,
                                         self.__disp_width, self.__disp_height)
            _active_layer = self.getActiveLayer()
            _layers = [self.getTopLayer()]
            while (len(_layers)):
                _layer = _layers.pop()
                if _layer is not _active_layer and _layer.isVisible():
                    _objs = _layer.objsInRegion(_xmin, _ymin, _xmax, _ymax)
                    draw_layer(self, _objs, _layer)
                _layers.extend(_layer.getSublayers())
            if _active_layer.isVisible():
                _objs = _active_layer.objsInRegion(_xmin, _ymin, _xmax, _ymax)
                draw_layer(self, _objs, _active_layer)
            _da.queue_draw() # generate an expose event

    def reset(self):
        """Set the image to an initial drawing state.

reset()
        """
        self.__tool = None
        self.setPrompt("Enter command")
