##############################################################################
# ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
#
# Copyright (c) 2001-2009 Thanasis Stamos, August 23, 2009
# URL: http://thancad.sourceforge.net
# e-mail: cyberthanasis@excite.com
#
# This program 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.
#
# This program 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 (www.gnu.org/licenses/gpl.html).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""\
ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
This module defines functionality necessary for user lowlevel interaction in
a drawing window.
"""
from math import atan2,pi,fabs,hypot
import Tkinter
from p_ggen import Struct
from thantkconst import *
from thanvar import Canc,thanLogTk,calcRoadNode,calcRoadNodeR,tkRoadNode,tkRoadNodeR,Win32
from thanopt import thancadconf
from thandefs.thanatt import ThanAttCol
_AVOIDTAG = frozenset(("e0", "edrag"))
class ThanTkGuiLowGet(Tkinter.Canvas):
"Lowlevel input from a Tk canvas."
#============================================================================
def __init__(self, proj, *args, **kw):
"Initialise base class and then this class."
Tkinter.Canvas.__init__(self, proj[2], *args, **kw)
self.__sb = proj[2].thanStatusBar
self.BSEL = proj[2].BSEL
#-------Do some local initialisation
self.__ncrossfirst = 0
self.__nfirst = 0
# dc = self.thanCanvas
dc = self
dc.bind("<Key>", self.__onChar) # This should be deleted, or at least redirected to thanCom
dc.bind("<Double-Button-1>", self.__onDclick)
dc.bind("<Button-1>", self.__onClick)
dc.bind("<Button-3>", self.__onClickr)
dc.bind("<Motion>", self.__onMotion)
dc.bind("<B1-Motion>", self.__onMotionDrag)
dc.bind("<ButtonRelease-1>", self.__onReleaseDrag)
if Win32:
dc.bind("<MouseWheel>", self.__mouseWheel)
proj[2].bind("<MouseWheel>", self.__mouseWheelp) # Hack for Windows: Yeh, Windows "just works", no need for hacks
else:
dc.bind("<Button-4>", self.__down_zoomin)
dc.bind("<Button-5>", self.__up_zoomout)
if not Win32:
self.bind("<Page_Up>", self.thanPanpageup)
self.bind("<Shift-Page_Up>", self.__donothing)
self.bind("<Page_Down>", self.thanPanpagedown)
self.bind("<Shift-Page_Down>",self.__donothing)
self.bind("<Control-Up>", self.thanPanpageup)
self.bind("<Control-Down>", self.thanPanpagedown)
self.bind("<Control-Left>", self.thanPanpageleft)
self.bind("<Control-Right>", self.thanPanpageright)
self.bind("<F7>", self.__sb.thanToggleCoord)
self.thanState = self.thanStateDrag = THAN_STATE_NONE
dc.update_idletasks() # _idletasks breaks WinDoze (98?) support. Skotistika
mx = dc.winfo_width() - 1
my = dc.winfo_height() - 1
self.thanXcu = dc.canvasx(mx / 2)
self.thanYcu = dc.canvasy(my / 2)
self.thanCrosHairs = [(CrosHair,), (CrosHairCros,60), (CrosHairCros,40),
(CrosHairCros,20), (CrosHairRect,), (CrosHairDummy,)]
self.thanIcrosHair = [0] # Set big croshair
self.thanCh = CrosHair(dc)
self.after(250, self.thanCh.resize) # Give the Canvas a fourth of a second, to stabilise
self.__osnap = Struct()
self.__osnap.size = proj[2].BOSN # size of osnap signs
self.__osnap.types = thancadconf.thanOsnapModes
self.__osnap.active = "ena" in self.__osnap.types
self.__osnap.preempt = False
self.__osnap.items = ()
self.__osnap.tcol = ThanAttCol(thancadconf.thanColOsn).thanTk
self.__osnap.selems = proj[2].thanSelems # Elements which may be snapped to.
self.__osnap.selem = None # Snapable element which is near cursor.
self.__wheel = Struct()
self.__wheel.preempt = False
self.__wheel.idle = 0
self.__wheel.task = None
# The following is needed in case the user turns the mouse weel
# just after the drawing is opened
self.__x1 = self.__y1 = self.__x2 = self.__y2 = self.__r1 = self.__t1 = None
self.thanEconoRaster = False # Economise raster regens in zoom (wheel)
self.thanFloatMenu = None
self.thanProj = proj
self.win = proj[2]
#============================================================================
def __mouseWheel(self, evt):
"Mouse wheel up or down; for windows only."
if evt.delta < 0: self.__up_zoomout(evt)
else: self.__up_zoomout(evt, fact=1.25)
def __mouseWheelp(self, evt):
"Mouse wheel up or down when triggered on the parent; for windows only."
self.update_idletasks() # so that .winfo_xxx() works
evt.x = evt.x_root - self.winfo_rootx()
evt.y = evt.y_root - self.winfo_rooty()
if evt.delta < 0: self.__up_zoomout(evt)
else: self.__up_zoomout(evt, fact=1.25)
def __up_zoomout(self, evt, fact=0.8):
"Mouse wheel up - zoom out."
if self.__wheel.preempt:
thanLogTk.warning("__up_zoomout called preemptively; returning immediately.")
return
self.__wheel.preemt = True
if self.__wheel.idle == 0:
self.__wheel.idle = 1
self.__wheel.task = self.after(500, self.__wheelClear)
self.__wheel.preemt = False
return
x = self.canvasx(evt.x); y = self.canvasy(evt.y) # canvas units
if self.__wheel.idle == 1:
if self.thanState == THAN_STATE_ZOOMDYNAMIC: return #The user already zooms (RealTime)
self.after_cancel(self.__wheel.task)
self.thanCh.thanDisable() #Zoom is clearer without croshair
if self.thanEconoRaster:
for (item1,item2),im in self.win.thanImages.iteritems():
self.delete(item2) # Delete Images (they can't be zoomed), but not rectangles
# self.__prepareZoom(x, y)
self.__wheel.idle = 2
self.after_cancel(self.__wheel.task)
self.scale(Tkinter.ALL, x, y, fact, fact)
if self.__x1 != None:
self.__x1 = x + (self.__x1-x)*fact
self.__y1 = y + (self.__y1-y)*fact
if self.__x2 != None:
self.__x2 = x + (self.__x2-x)*fact
self.__y2 = y + (self.__y2-y)*fact
if self.__r1 != None:
self.__r1 *= fact
self.__notifyScale(x, y, 1.0/fact)
self.__wheel.task = self.after(500, self.__notifyWheelEnd)
self.__wheel.preemt = False
def __notifyScale(self, xc, yc, fact):
"Notify drawing that the zoom has changed."
# self.__calcZoom()
self.__resultCoor(xc, yc)
w = self.win.thanDoZoomDyn(self.thanLastResult[0], fact)
v = self.thanProj[1].viewPort
v[:] = w
if not self.thanEconoRaster:
self.win.thanAutoRegen(regenImages=True) # Regen images only if zoom realtime is finished
def __wheelClear(self):
"Clear attempt to use the wheel."
self.__wheel.idle = 0
def __notifyWheelEnd(self):
"Notify drawing that the zoom has changed."
self.thanCh.thanEnable()
if self.thanEconoRaster:
self.win.thanAutoRegen(regenImages=True) # Regen images only if zoom realtime is finished
self.__wheel.idle = 0
def __down_zoomin(self, evt):
"Mouse wheel down - zoom in."
self.__up_zoomout(evt, fact=1.25)
#============================================================================
def thanPanpagedown (self, evt): return self.__panPage(evt, 0, -1)
def thanPanpageleft (self, evt): return self.__panPage(evt, -1, 0)
def thanPanpageright(self, evt): return self.__panPage(evt, 1, 0)
def thanPanpageup (self, evt): return self.__panPage(evt, 0, 1)
def __donothing(self, evt): pass
def __panPage(self, evt, ix, iy):
"Page up,down,left,right."
dx, dy = self.win.thanPanPage(ix, iy)
if dx != 0.0 and dy != 0.0: return "break"
# if self.__x1 != None:
# self.__x1 += dx
# self.__y1 += dy
# if self.__x2 != None:
# self.__x2 += dx
# self.__y2 += dy
return "break"
#============================================================================
def __onMotionDrag(self, event):
"Well, here is what should be drawn each time mouse moves while it is pressed."
#-------If not dragging or user is not pressing mouse key, do nothing
if self.thanStateDrag == THAN_STATE_NONE or \
self.thanStateDrag == THAN_STATE_DRAG2BEGIN:
# self.__onMotion(event)
return
#-------Initial values
dc = self
x = dc.canvasx(event.x); y = dc.canvasy(event.y) # canvas units
cc = list(self.thanProj[1].thanVar["elevation"])
cc[:2] = self.win.thanCt.local2Global(x, y) # User coordinates (transformed from canvas coordinates)
self.__sb.thanCoorMouse(cc)
#-------The first drag must be at least 3 points (the user accidentally dragged!)
if self.thanStateDrag == THAN_STATE_DRAGFIRST:
if abs(x-self.__x2) < 3 and abs(y-self.__y2) < 3: return
self.__prepareZoom(x, y)
self.thanCh.thanDisable() # Disable croshair (dragging is clearer)
if self.thanState == THAN_STATE_ZOOMDYNAMIC:
for (item1,item2),im in self.win.thanImages.iteritems():
dc.delete(item2) # Delete Images (they can't be zoomed), but not rectangles
self.thanStateDrag = THAN_STATE_DRAGGING
#-------The user is dragging now
if self.thanState == THAN_STATE_PANDYNAMIC:
dx = -int(x - self.thanXcu) # pixels
dy = -int(y - self.thanYcu) # pixels
dc.xview(Tkinter.SCROLL, dx, Tkinter.UNITS)
dc.yview(Tkinter.SCROLL, dy, Tkinter.UNITS)
x += dx # Because the view window changed, local coords of elements (and croshair)
y += dy # did not change with these commands. Thus modify croshair coordinates,
# so that croshair remains at the same "view" position
elif self.thanState == THAN_STATE_ZOOMDYNAMIC:
dy = int(y - self.thanYcu) # pixels
fact = 1.03, 1.03
if dy < 0: fact = 0.97, 0.97
args = (Tkinter.ALL,) + self.__zoomorigc + fact
dc.scale(*args)
#-------Ending values
self.thanXcu = x; self.thanYcu = y
def __prepareZoom(self, x, y):
"Makes sentinel elements to prepare for zoom computation."
self.update_idletasks() # _idletasks breaks WinDoze (98?) support. Skotistika
self.__xa = self.canvasx(0); self.__ya = self.canvasy(0)
self.__zoomorigc = x, y # canvas units
self.__zooma = self.create_line(x-10000, y-10000, x-10001, y-10001) # sentinel element
self.__zoomb = self.create_line(x+10000, y+10000, x+10001, y+10001) # sentinel element
#============================================================================
def __onReleaseDrag(self, event):
"Well, here is what should be done after the end of dragging."
if self.thanStateDrag == THAN_STATE_NONE: return
if self.thanStateDrag == THAN_STATE_DRAGFIRST: return
if self.thanStateDrag != THAN_STATE_DRAGGING:
thanLogTk.warning("tklowget: onReleaseDrag was trigered before onClick: probably, the click was lost!")
#-------Initial values
# dc = self.thanCanvas
dc = self
x = dc.canvasx(event.x); y = dc.canvasy(event.y)
if self.thanState == THAN_STATE_PANDYNAMIC:
dc.update_idletasks() # _idletasks breaks WinDoze (98?) support. Skotistika
xa = dc.canvasx(0); ya = dc.canvasy(0)
self.__resultCoorRel0(xa-self.__xa, ya-self.__ya)
self.thanCh.thanEnable(x, y) # Enable, resize and redraw the croshair
self.thanState = self.thanStateDrag = THAN_STATE_NONE
elif self.thanState == THAN_STATE_ZOOMDYNAMIC:
if self.__zooma == None:
thanLogTk.warning("tklowget: Release drag event triggered for no reason!")
return
self.__calcZoom()
self.thanCh.thanEnable(x, y) # Enable, resize and redraw the croshair
self.thanState = self.thanStateDrag = THAN_STATE_NONE
else:
assert False, "Unknown drag state="+str(self.thanState)
#-------Ending values
self.thanXcu = x; self.thanYcu = y
def __resultCoorRel0(self, dxp, dyp):
"Convert relative pixel coordiantes to user data units."
self.thanLastResult = ([0.0]*self.thanProj[1].thanVar["dimensionality"], None)
self.thanLastResult[0][:2] = self.win.thanCt.local2GlobalRel(dxp, dyp)
def __resultCoor(self, xp, yp):
"Convert relative pixel coordiantes to user data units."
self.thanLastResult = (list(self.thanProj[1].thanVar["elevation"]), None)
self.thanLastResult[0][:2] = self.win.thanCt.local2Global(xp, yp)
def __calcZoom(self):
"Computes the zoom factor using the sentinel elements."
ca = self.coords(self.__zooma)
cb = self.coords(self.__zoomb)
self.__debugZoom(ca, cb) # Only for debugging
# self.thanLastResult = self.__zoomorigc + (20000.0 / (cb[0] - ca[0]), )
self.__resultCoor(*self.__zoomorigc)
self.thanLastResult = (self.thanLastResult[0], 20000.0 / (cb[0] - ca[0]))
def __debugZoom(self, ca, cb):
"Check that the center of zoom remained in place; code only for debug; not needed in production."
cc = (ca[0]+cb[0])*0.5, (ca[1]+cb[1])*0.5
cor = self.__zoomorigc
if abs(cc[0]-cor[0])+abs(cc[1]-cor[1]) > 0.5:
thanLogTk.warning("Zoom center should be: %f %f but is: %f %f \n ca=%f %f\n cb=%f %f"\
% (self.__zoomorigc[0], self.__zoomorigc[1], cc[0], cc[1], ca[0], ca[1], cb[0], cb[1]))
#============================================================================
def __onMotion(self, event):
"Well, here is what should be drawn each time mouse moves."
#-------Initial values
# dc = self.thanCanvas
self.__osnap.active = "ena" in self.__osnap.types
dc = self
x = dc.canvasx(event.x); y = dc.canvasy(event.y)
#-------Cross hair, status line
self.thanCh.draw(x, y)
cc = list(self.thanProj[1].thanVar["elevation"])
cc[:2] = self.win.thanCt.local2Global(x, y) # User coordinates (transformed from canvas coordinates)
self.__sb.thanCoorMouse(cc)
#-------Move selected itmes with cursor
# if self.thanState == THAN_STATE_MOVE:
# ca = dc.coords(self.__zooma)
# dx, dy = (x-10000)-ca[0], (y-10000)-ca[1]
# dc.move("edrag", dx, dy)
if self.thanState == THAN_STATE_MOVE:
ca = dc.coords(self.__zooma)
dx, dy = (x-10000)-ca[0], (y-10000)-ca[1]
if self.__cc2 != None:
disproj = dx*self.__cc2[0] - dy*self.__cc2[1] # The move is (reverse y due to coordinate systems)..
dx = disproj*self.__cc2[0] # ..to the direction unit vector..
dy = -disproj*self.__cc2[1] # .. self.__x1, self.__y1
# orthoxy(self, xa, ya, xb, yb): call this instead
dc.move("edrag", dx, dy)
#-------Draw dragged line
elif self.thanState == THAN_STATE_LINE:
if self.__nfirst:
i1 = self.__dragged
self.__dragged = dc.create_line(self.__x1, self.__y1, x, y, fill="blue")
dc.delete(i1)
else:
self.__dragged = dc.create_line(self.__x1, self.__y1, x, y, fill="blue")
self.__nfirst = 1
#-------Draw 2 dragged lines
elif self.thanState == THAN_STATE_LINE2:
if self.__nfirst:
draggedp = self.__dragged
self.__dragged = [dc.create_line(self.__x1, self.__y1, x, y, fill="blue"),
dc.create_line(self.__x2, self.__y2, x, y, fill="blue"),
]
for i1 in draggedp: dc.delete(i1)
else:
self.__dragged = [dc.create_line(self.__x1, self.__y1, x, y, fill="blue"),
dc.create_line(self.__x2, self.__y2, x, y, fill="blue"),
]
self.__nfirst = 1
#-------Draw dragged line of certain length. __r1 is the length
elif self.thanState == THAN_STATE_POLAR:
dx1 = x - self.__x1; dy1 = y - self.__y1
r = hypot(dx1, dy1)
if r == 0: x1 = x; y1 = y
else: x1 = self.__x1 + dx1*self.__r1/r; y1 = self.__y1 + dy1*self.__r1/r
if self.__nfirst:
i1 = self.__dragged
self.__dragged = dc.create_line(self.__x1, self.__y1, x1, y1, fill="blue")
dc.delete(i1)
else:
self.__dragged = dc.create_line(self.__x1, self.__y1, x1, y1, fill="blue")
self.__nfirst = 1
#-------Draw dragged circle
elif self.thanState == THAN_STATE_CIRCLE:
r = hypot(x-self.__x1, y-self.__y1)
if self.__nfirst:
i1 = self.__dragged
self.__dragged = dc.create_oval(self.__x1-r, self.__y1+r, self.__x1+r, self.__y1-r,
outline = "blue")
dc.delete(i1)
else:
self.__dragged = dc.create_arc(self.__x1-r, self.__y1-r, self.__x1+r, self.__y1+r,
outline = "blue")
self.__nfirst = 1
#-------Draw dragged arc. __r1 is the radius and __t1 is theta1
elif self.thanState == THAN_STATE_ARC+"10000":
theta1 = self.__t1 / pi * 180
theta2 = atan2(-(y-self.__y1), x-self.__x1) / pi * 180
if self.__nfirst:
i1 = self. __dragged
self.__dragged = dc.create_arc(self.__x1-self.__r1, self.__y1+self.__r1,
self.__x1+self.__r1, self.__y1-self.__r1,
start=theta1, extent=(theta2-theta1)% 360.0, style=Tkinter.ARC, outline = "blue")
dc.delete(i1)
else:
self.__dragged = dc.create_arc(self.__x1-self.__r1, self.__y1+self.__r1,
self.__x1+self.__r1, self.__y1-self.__r1,
start=theta1, extent=(theta2-theta1)%360.0, style=Tkinter.ARC, outline = "blue")
self.__nfirst = 1
#-------Draw dragged arc. __r1 is the radius and __t1 is theta1
elif self.thanState == THAN_STATE_ARC:
theta1 = self.__t1 / pi * 180
theta2 = atan2(-(y-self.__y1), x-self.__x1) / pi * 180
if self.__nfirst:
draggedp = self. __dragged
self.__dragged = (dc.create_arc(self.__x1-self.__r1, self.__y1+self.__r1,
self.__x1+self.__r1, self.__y1-self.__r1,
start=theta1, extent=(theta2-theta1)%360.0, style=Tkinter.ARC,
outline = "blue"),
dc.create_line(self.__x1, self.__y1, x, y, fill="blue"),
)
for i2 in draggedp: dc.delete(i2)
else:
self.__dragged = (dc.create_arc(self.__x1-self.__r1, self.__y1+self.__r1,
self.__x1+self.__r1, self.__y1-self.__r1,
start=theta1, extent=(theta2-theta1)%360.0, style=Tkinter.ARC,
outline = "blue"),
dc.create_line(self.__x1, self.__y1, x, y, fill="blue"),
)
self.__nfirst = 1
#-------Draw dragged rectangle. __t1 is the command
elif self.thanState == THAN_STATE_RECTANGLE:
out = "blue"; fil = "darkblue"
# if self.__t1 == "w": stip = fi = "blue"
if self.__t1 == "c": out = "cyan"; fil = "darkcyan"
elif self.__t1 == "cw" and x < self.__x1: out = "cyan"; fil = "darkcyan"
# elif self.__t1 == "cw":
# if x > self.__x1: stip = fi = "blue"
# else: stip = fi = "cyan" # stipple="gray25"
elif self.__t1 == None: out = "blue"; fil = None
if self.__nfirst:
i1 = self.__dragged
self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x, y, outline=out, fill=fil, stipple="gray25")
# self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x, y, outline=out, fill=fil)
dc.delete(i1)
# if self.__t1 != None: dc.lower(self.__dragged)
else:
self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x, y, outline=out, fill=fil, stipple="gray25")
# self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x, y, outline=out, fill=fil)
self.__nfirst = 1
# if self.__t1 != None: dc.lower(self.__dragged)
#-------Draw dragged rectangle with fixed signed __t1=height/width (
elif self.thanState == THAN_STATE_RECTRATIO:
stip = "blue"
dx = abs(x - self.__x1); dy = abs(y - self.__y1)
if dx > dy: dy = dx*self.__t1
else: dx = dy/self.__t1
x2 = self.__x1+dx; y2 = self.__y1-dy
if self.__nfirst:
i1 = self.__dragged
self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x2, y2, outline=stip)
dc.delete(i1)
else:
self.__dragged = dc.create_rectangle(self.__x1, self.__y1, x2, y2, outline=stip)
self.__nfirst = 1
elif self.thanState == THAN_STATE_ROADP:
if self.__nfirst:
draggedp = self.__dragged
self.__dragged, ct = tkRoadNode(self.__x1, self.__y1, self.__x2, self.__y2,
x, y, self.__r1, dc, fill="blue", tags=())
for i2 in draggedp: dc.delete(i2)
else:
self.__dragged, ct = tkRoadNode(self.__x1, self.__y1, self.__x2, self.__y2,
x, y, self.__r1, dc, fill="blue", tags=())
self.__nfirst = 1
elif self.thanState == THAN_STATE_ROADR:
if self.__nfirst:
draggedp = self.__dragged
self.__dragged, ct = tkRoadNodeR(self.__x1, self.__y1, self.__x2, self.__y2,
self.__x3, self.__y3, x, y, dc, self.win.thanCt, fill="blue", tags=())
for i2 in draggedp: dc.delete(i2)
else:
self.__dragged, ct = tkRoadNodeR(self.__x1, self.__y1, self.__x2, self.__y2,
self.__x3, self.__y3, x, y, dc, self.win.thanCt, fill="blue", tags=())
self.__nfirst = 1
#-------Ending values
self.thanXcu = x
self.thanYcu = y
if self.thanState != THAN_STATE_NONE and self.__osnap.active:
self.after(10, self.__osnapFind)
def orthoxy(self, xa, ya, xb, yb):
"Force the coordinates of the mouse parallel to a direction; usually ortho mode."
if not self.__idirs: return xb, yb
dx = xb - xa
dy = yb - ya
dis = 0.0
for idir1 in self.__idirs:
disproj = dx*idir1[0] - dy*idir1[1] # The negative sign in y is due to local coordinate systems
if disproj > dismax:
dismax = disproj
idirmax = idir1
return xa + dismax*idirmax[0], -dismax*idirmax[1]
#============================================================================
def __osnapFind(self):
"Finds end, mid, center etc near the mouse cursor."
if self.__osnap.preempt:
thanLogTk.error("tklowget: osnapFind: preemptive call. It shouldn't happen.")
return
self.__osnap.preempt = True
dc = self
tagel = self.thanProj[1].thanTagel
ct = self.win.thanCt
bpix, hpix = self.BSEL/2, self.BSEL/2
x1, y1 = self.thanXcu-bpix, self.thanYcu-hpix
x2, y2 = self.thanXcu+bpix, self.thanYcu+hpix
items = dc.find_overlapping(x1, y1, x2, y2)
otypes = self.__osnap.types
ccu = list(self.thanProj[1].thanVar["elevation"])
ccu[:2] = ct.local2Global(self.thanXcu, self.thanYcu)
if "ele" in otypes:
selems = self.__osnap.selems
for item in items:
tags = dc.gettags(item)
# Tkinter may automatically add the tag 'current' in any element
# so the next test (which should succeed) does not succeed.
# Thus we have to check for "e0" AND "edrag"
if len(tags) < 2: continue # We avoid current (rubber line)
if tags[0] in _AVOIDTAG: continue # We avoid current compound element (we shouldn't really)
e = tagel[tags[0]]
if e in selems:
self.delete("e0")
self.__osnap.selem = e
e1 = e.thanClone()
e1.thanTags = ("e0",)
than = self.thanProj[2].than
col1 = than.outline
than.outline = self.__osnap.tcol
e1.thanTkDraw(than)
dc.itemconfig("e0", width=5)
than.outline = col1
self.__osnap.preempt = False
return
self.__osnap.selem = None
self.delete("e0")
self.__osnap.preempt = False
return
elif "int" in otypes:
ps = self.__osnapint(dc, tagel, otypes, items, ccu, self.__cc1)
else:
ps = []
for item in items:
# tags = dc.itemcget(item, "tags").split()
if dc.type(item) == "image": continue # Ignore Tk images; only the bounding rectangle counts
tags = dc.gettags(item)
if len(tags) < 2: continue # We avoid current (rubber line)
if tags[0] in _AVOIDTAG: continue # We avoid current compound element (we shouldn't really)
e = tagel[tags[0]]
p = e.thanOsnap(self.thanProj, otypes, ccu, None, self.__cc1)
if p != None:
ps.append(p)
if len(ps) > 2: break
for item1 in self.__osnap.items: dc.delete(item1)
if len(ps) < 1:
self.__osnap.items = ()
self.__osnap.preempt = False
return
p = min(ps)
b, h = ct.global2LocalRel(p[0], p[0])
if b > 10*self.BSEL:
self.__osnap.items = ()
self.__osnap.preempt = False
return
t = self.__osnap.type = p[1]
cc = self.__osnap.cc = p[2]
x, y = ct.global2Local(cc[0], cc[1])
self.__osnap.x = x
self.__osnap.y = y
b = h = self.__osnap.size/2
tcol = self.__osnap.tcol
if t == "end":
self.__osnap.items = \
( dc.create_rectangle(x-b, y-h, x+b, y+h, width=3, outline=tcol, fill=""),
)
elif t == "mid":
self.__osnap.items = \
( dc.create_polygon(x-b, y-b, x+b, y-b, x, y+b, width=3, outline=tcol, fill=""),
)
elif t == "cen":
self.__osnap.items = \
( dc.create_oval(x-b, y-b, x+b, y+b, width=3, outline=tcol, fill=""),
)
elif t == "nod":
self.__osnap.items = \
( dc.create_oval(x-b, y-b, x+b, y+b, width=3, outline=tcol, fill=""),
dc.create_line(x-b, y-b, x+b, y+b, width=2, fill=tcol),
dc.create_line(x-b, y+b, x+b, y-b, width=2, fill=tcol),
)
elif t == "qua":
self.__osnap.items = \
( dc.create_polygon(x-b,y, x,y+b, x+b,y, x,y-b, width=2, outline=tcol, fill=""),
)
elif t == "int":
self.__osnap.items = \
( dc.create_line(x-b, y-b, x+b, y+b, width=2, fill=tcol),
dc.create_line(x-b, y+b, x+b, y-b, width=2, fill=tcol),
)
elif t == "tan":
bb = 0.8*b
self.__osnap.items = \
( dc.create_oval(x-bb, y-bb, x+bb, y+bb, width=3, outline=tcol, fill=""),
dc.create_line(x-b, y-b, x+b, y-b, width=2, fill=tcol),
)
elif t == "nea":
self.__osnap.items = \
( dc.create_polygon(x-b, y-b, x+b, y+b, x-b, y+b, x+b, y-b, width=2, outline=tcol, fill=""),
)
elif t == "per":
self.__osnap.items = \
( dc.create_line(x-b, y-b, x-b, y+b, x+b, y+b, width=3, fill=tcol),
dc.create_line(x-b, y, x, y, x, y+b, width=2, fill=tcol),
)
self.__osnap.preempt = False
def __osnapint(self, dc, tagel, otypes, items, xcu, ycu, cc1):
"Finds int near the mouse cursor."
ps = []
it = iter(items)
for item in it: # Search for first element of intersection
# tags = dc.itemcget(item, "tags").split()
tags = dc.gettags(item)
if len(tags) < 2: continue # We avoid current (rubber line)
if tags[0] in _AVOIDTAG: continue # We avoid current compound element (we shouldn't really)
e = tagel[tags[0]]
break
else: return ps
etried = False
for item in it: # Search for second element of intersection
# tags = dc.itemcget(item, "tags").split()
tags = dc.gettags(item)
if len(tags) < 2: continue # We avoid current (rubber line)
if tags[0] in _AVOIDTAG: continue # We avoid current compound element (we shouldn't really)
etried = True
e2 = tagel[tags[0]]
p = e.thanOsnap(self.thanProj, otypes, xcu, ycu, e2, x1, y1)
if p != None: ps.append(p); break
e = e2
if etried: return ps
p = e.thanOsnap(self.thanProj, otypes, xcu, ycu, None, x1, y1)
if p != None: ps.append(p)
return ps
#============================================================================
CLICKDEFSTATES = frozenset((THAN_STATE_LINE, THAN_STATE_LINE2, THAN_STATE_POLAR,
THAN_STATE_CIRCLE, THAN_STATE_ARC, THAN_STATE_RECTANGLE))
def __onClick(self, event):
"Well, here is what should be done when mouse clicks."
dc = self
# dc.focus_set() # This is needed otherwise text controls gets characters?
x = dc.canvasx(event.x); y = dc.canvasy(event.y) # Click coordinates
if self.thanStateDrag != THAN_STATE_NONE: # Check for dragging
if self.thanStateDrag == THAN_STATE_DRAG2BEGIN:
self.__x2 = x
self.__y2 = y
self.thanStateDrag = THAN_STATE_DRAGFIRST
return
if self.__osnap.active and self.__osnap.items != (): # If object snap, change click coordinates
x = self.__osnap.x # Canvas coordinates
y = self.__osnap.y # Canvas coordinates
cc = self.__osnap.cc # User coordinates (from object snap)
else:
cc = list(self.thanProj[1].thanVar["elevation"])
cc[:2] = self.win.thanCt.local2Global(x, y) # User coordinates (transformed from canvas coordinates)
self.__sb.thanCoorClick(cc) # Draw status text
if self.thanState == THAN_STATE_POINT: # The user defined a point
self.thanLastResult = cc, None
self.thanState = THAN_STATE_NONE
elif self.thanState in self.CLICKDEFSTATES: # The user defined a line, circle, arc, rectangle
try: self.__dragged[0]
except: dc.delete(self.__dragged)
else: [dc.delete(i1) for i1 in self.__dragged]
del self.__dragged
self.thanLastResult = cc, None
self.thanState = THAN_STATE_NONE
elif self.thanState == THAN_STATE_MOVE:
# dc.delete(self.__zooma) # __zooma has the tag "edrag"
dc.delete("edrag")
self.thanLastResult = cc, None
self.thanState = THAN_STATE_NONE
elif self.thanState == THAN_STATE_RECTRATIO:
# dx = abs(x - self.__x1); dy = abs(y - self.__y1)
# if dx > dy: dy = dx*self.__t1
# else: dx = dy/self.__t1
# self.__resultCoor(self.__x1+dx, self.__y1-dy)
dx = fabs(cc[0] - self.__cc1[0])
dy = fabs(cc[1] - self.__cc1[1])
if dx > dy: dy = dx*self.__t1
else: dx = dy/self.__t1
cc[0] = self.__cc1[0] + dx
cc[1] = self.__cc1[1] + dy
self.thanLastResult = cc, None
self.thanState = THAN_STATE_NONE
elif self.thanState in (THAN_STATE_ROADP, THAN_STATE_ROADR):
if self.__nfirst:
for i1 in self.__dragged: dc.delete(i1)
self.thanLastResult = cc, None
self.thanState = THAN_STATE_NONE
elif self.thanState == THAN_STATE_SNAPELEM:
self.thanLastResult = self.__osnap.selem, None
self.delete("e0")
self.thanState = THAN_STATE_NONE
self.thanXcu = x; self.thanYcu = y
#============================================================================
def __onClickr(self, event):
"Well, here is what should be done when right mouse clicks."
#-------Initial values
# dc = self.thanCanvas
dc = self
# dc.focus_set() # This is needed otherwise text controls gets characters
x = dc.canvasx(event.x)
y = dc.canvasy(event.y)
#-------Check for dragging
if self.thanStateDrag != THAN_STATE_NONE:
if self.thanState == THAN_STATE_ZOOMDYNAMIC:
self.win.thanScheduler.thanSchedule(self.win.thanGudCommandBegin, "panrealtime")
self.win.thanCom.thanOnCharEsc(event) # Finish zoom dynamic
elif self.thanState == THAN_STATE_PANDYNAMIC:
self.win.thanScheduler.thanSchedule(self.win.thanGudCommandBegin, "zoomrealtime")
self.win.thanCom.thanOnCharEsc(event) # Finish pan dynamic
#-------Check for road:point
elif self.thanState == THAN_STATE_ROADP:
if self.__nfirst:
for i1 in self.__dragged: dc.delete(i1)
self. __resultCoor(x, y)
self.thanLastResult = (self.thanLastResult[0], "r")
self.thanState = THAN_STATE_NONE
#-------Check for road:radius
elif self.thanState == THAN_STATE_ROADR:
self.__onClick(event) # It is like if the user pressed left click
#-------Check for idle
elif self.thanState == THAN_STATE_NONE:
if self.thanFloatMenu != None and self.thanFloatMenu.winfo_ismapped():
self.thanFloatMenu.unpost()
self.thanFloatMenu = self.__createFloatMenu()
self.thanFloatMenu.post(event.x_root, event.y_root)
def __createFloatMenu(self):
m = Tkinter.Menu(self, tearoff=False)
fbeg = self.win.thanGudCommandBegin
m.add_command(label="Repeat last command", command=lambda fbeg=fbeg: fbeg(""))
m.add_command(label="Real time zoom", command=lambda fbeg=fbeg: fbeg("zoomrealtime"))
m.add_command(label="Real time pan", command=lambda fbeg=fbeg: fbeg("panrealtime"))
# m1 = Menu(m, tearoff=False)
# m1.add_checkbutton(label="zoom when window changes", variable=self.zoomWhenConf)
# m1.add_checkbutton(label="regen when window changes", variable=self.regenWhenConf)
# m1.add_separator()
# m1.add_checkbutton(label="regen when zoom", variable=self.regenWhenZoom)
# m1.add_checkbutton(label="regen when zoom all", variable=self.regenWhenZoomall)
# m1.add_checkbutton(label="center when zoom", variable=self.centerWhenZoom)
# m1.add_separator()
# m1.add_checkbutton(label="show menu bar", variable=self.showMenubar)
# m1.add_checkbutton(label="show tool bar", variable=self.showToolbar)
# m.add_cascade(label="options", menu=m1)
return m
#============================================================================
def __onDclick(self, event):
"On double click change to the next available croshair."
self.thanRotateCroshair(self.canvasx(event.x), self.canvasy(event.y))
def thanRotateCroshair(self, x1=None, y1=None):
"Changes the croshair."
self.thanIcrosHair[-1] = (self.thanIcrosHair[-1] + 1) % len(self.thanCrosHairs)
self.thanSetCroshair(self.thanIcrosHair[-1], x1, y1)
def thanPushCroshair(self, i, x1=None, y1=None):
"Change the cursor temporarily."
i %= len(self.thanCrosHairs)
self.thanIcrosHair.append(i)
self.thanSetCroshair(self.thanIcrosHair[-1], x1, y1)
def thanPopCroshair(self, x1=None, y1=None):
"Change to the previous cursor."
if len(self.thanIcrosHair) < 1: return
del self.thanIcrosHair[-1]
self.thanSetCroshair(self.thanIcrosHair[-1], x1, y1)
def thanSetCroshair(self, i, x1=None, y1=None):
"Set the croshair."
i %= len(self.thanCrosHairs)
if x1 == None:
x1 = self.thanCh.thanX1p
y1 = self.thanCh.thanY1p
clas = self.thanCrosHairs[i][0]
args = self.thanCrosHairs[i][1:]
self.thanCh.thanDisable()
self.thanCh = clas(self, *args)
self.thanCh.resize(x1, y1)
#============================================================================
def __onChar(self, event):
"Well, here is what should be done when user presses a key."
thanLogTk.error("onChar(): Unexpected Canvas <key> event redirected to ThanCom.")
self.win.thanTkSetFocus()
self.win.thanCom.thanOnChar(event)
#============================================================================
def thanPrepare(self, state, cc1=None, cc2=None, cc3=None, r1=None, t1=None, idirs=()):
"Sets the appropriate state and lets gui take on."
ct = self.win.thanCt
self.__x1, self.__y1 = None, None
if cc1 != None: self.__x1, self.__y1 = ct.global2Local(cc1[0], cc1[1]) # Transform to local coordinates
self.__x2, self.__y2 = None, None
if cc2 != None: self.__x2, self.__y2 = ct.global2Local(cc2[0], cc2[1]) # Transform to local coordinates
self.__x3, self.__y3 = None, None
if cc3 != None: self.__x3, self.__y3 = ct.global2Local(cc3[0], cc3[1]) # Transform to local coordinates
self.__r1, self.__t1 = r1, t1
if r1 != None: self.__r1, r = ct.global2LocalRel(r1, r1) # Transform to local coordinates
self.__cc1 = cc1
self.__cc2 = cc2
self.__cc3 = cc3
self.__rr1 = r1
dc = self
if state > THAN_STATE_DRAGFOLLOWS:
self.thanStateDrag = THAN_STATE_DRAG2BEGIN
elif state == THAN_STATE_MOVE:
self.__zooma = self.__zoomb = dc.create_line(self.__x1-10000, self.__y1-10000,\
self.__x1-10001, self.__y1-10001, tags="edrag") # sentinel element
# self.win.thanStatusxy(self.thanXcu, self.thanYcu)
self.__nfirst = 0
self.thanState = state
if state == THAN_STATE_POINT1: self.thanState = THAN_STATE_POINT
dc.config(cursor=thanCursor.get(state, ""))
if state == THAN_STATE_ROADR:
# hopefully everything is set, and the following event will trigger __onMotion to do the drawing work
ro = calcRoadNode(self.__x1, self.__y1, self.__x2, self.__y2, self.__x3, self.__y3, self.__r1)
dc.event_generate("<Motion>", when="head", x=ro.pm.x, y=ro.pm.y, warp=1) # Set mouse position to the middle of the circular curve
elif state == THAN_STATE_ROADP and cc3 != None:
# hopefully everything is set, and the following event will trigger __onMotion to do the drawing work
dc.event_generate("<Motion>", when="head", x=self.__x3, y=self.__y3, warp=1) # Set mouse position to the prveious c3 point
def thanCleanup(self):
"Cleanup after we got the gui result."
# self.win.thanStatusxy(self.thanXcu, self.thanYcu)
dc = self
dc.config(cursor="")
self.thanLastResult = Canc, None
try: self.__dragged
except: pass
else:
try: self.__dragged[0]
except: dc.delete(self.__dragged)
else: [dc.delete(i1) for i1 in self.__dragged]
del self.__dragged
try: self.__zooma
except: pass
else:
dc.delete(self.__zooma) # delete sentinel
dc.delete(self.__zoomb) # delete sentinel
del self.__zooma, self.__zoomb
dc.delete("edrag") # In case the user issued MOVE command, but the commandline answered
for item1 in self.__osnap.items: dc.delete(item1)
self.__osnap.items = ()
if self.thanFloatMenu != None and self.thanFloatMenu.winfo_ismapped():
self.thanFloatMenu.unpost()
self.thanState = self.thanStateDrag = THAN_STATE_NONE
#============================================================================
def thanGudIdle(self):
"Returns true if not in the middle of a command."
return self.thanState == THAN_STATE_NONE
#============================================================================
def thanTkClear(self):
"Clears a window."
dc = self
dc.delete(Tkinter.ALL) # Clear window
self.thanCh.resize()
#============================================================================
def thanGudCoorChanged(self):
"System of canvas changed; redraw croshair."
self.thanCh.resize()
def destroy(self):
"Deletes circular references."
del self.__sb, self.win, self.thanProj
Tkinter.Canvas.destroy(self)
def __delete__(self):
"Show that self is really deleted."
print "thantklowget:", self, "has been freed."
#############################################################################
#############################################################################
class CrosHair(object):
"Draws, moves, deletes and maintains a croshair in a canvas."
def __init__(self, dc):
"Initialise the croshair."
self.thanDc = dc
self.thanExists = 0
self.thanX1p = None
self.thanY1p = None
self.thanOn = True
self.resize()
def destroy(self):
"Break circular references."
del self.thanDc
def resize(self, x1=None, y1=None):
"Recomputes the size of the croshair."
dc = self.thanDc
dc.update_idletasks() # _idletasks breaks WinDoze (98?) support. Skotistika
maxx = dc.winfo_width() - 1
maxy = dc.winfo_height() - 1
minx = 0
miny = 0
self.thanMinx = dc.canvasx(minx)
self.thanMiny = dc.canvasy(miny)
self.thanMaxx = dc.canvasx(maxx)
self.thanMaxy = dc.canvasy(maxy)
if self.thanExists:
# self.thanX1p = dc.coords(self.thanCh1)[0]
# self.thanY1p = dc.coords(self.thanCh2)[1]
dc.delete(self.thanCh1)
dc.delete(self.thanCh2)
self.thanExists = 0
if x1 == None: x1 = self.thanX1p; y1 = self.thanY1p
if x1 < self.thanMinx or x1 > self.thanMaxx or \
y1 < self.thanMiny or y1 > self.thanMaxy:
x1 = self.thanMinx + (self.thanMaxx - self.thanMinx) / 2
y1 = self.thanMiny + (self.thanMaxy - self.thanMiny) / 2
self.draw(x1, y1)
self.thanX1p = x1; self.thanY1p = y1
def draw(self, x1, y1):
"Draws a croshair and deletes previous croshair."
if self.thanOn:
dc = self.thanDc
if self.thanExists:
# dc.move(self.thanCh1, x1-self.thanX1p, 0)
# dc.move(self.thanCh2, 0, y1-self.thanY1p)
dc.coords(self.thanCh1, x1, self.thanMiny, x1, self.thanMaxy,)
dc.coords(self.thanCh2, self.thanMinx, y1, self.thanMaxx, y1)
dc.lift(self.thanCh1)
dc.lift(self.thanCh2)
else:
self.thanCh1 = dc.create_line(x1, self.thanMiny, x1, self.thanMaxy, fill="red")
self.thanCh2 = dc.create_line(self.thanMinx, y1, self.thanMaxx, y1, fill="green")
self.thanExists = 1
self.thanX1p = x1
self.thanY1p = y1
def delete(self):
"Deletes the croshair from the canvas - if it exists."
if self.thanExists:
dc = self.thanDc
dc.delete(self.thanCh1)
dc.delete(self.thanCh2)
self.thanExists = 0
def thanEnable(self, x1=None, y1=None):
"Enable the croshair."
self.thanOn = True
self.resize(x1, y1)
def thanDisable(self):
"Disable the croshair."
self.thanOn = False
self.delete()
#############################################################################
#############################################################################
class CrosHairRect(CrosHair):
"Draws, moves, deletes and maintains a limited croshair in a canvas."
def __init__(self, dc, size=None):
"Take size as an argument."
if size == None: size = dc.BSEL
self.dx = self.dy = size/2
CrosHair.__init__(self, dc)
def draw(self, x1, y1):
"Draws a croshair and deletes previous croshair."
if self.thanOn:
dc = self.thanDc
if self.thanExists:
dc.coords(self.thanCh1, x1-self.dx, y1-self.dy, x1+self.dx, y1+self.dy)
dc.lift(self.thanCh1)
else:
self.thanCh1 = dc.create_rectangle(x1-self.dx, y1-self.dy, x1+self.dx, y1+self.dy, outline="red")
self.thanCh2 = None
self.thanExists = 1
self.thanX1p = x1
self.thanY1p = y1
#############################################################################
#############################################################################
class CrosHairCros(CrosHair):
"Draws, moves, deletes and maintains a limited croshair in a canvas."
def __init__(self, dc, size=40):
"Take size as an argument."
self.dx = self.dy = size
CrosHair.__init__(self, dc)
def draw(self, x1, y1):
"Draws a croshair and deletes previous croshair."
if self.thanOn:
dc = self.thanDc
if self.thanExists:
# dc.move(self.thanCh1, x1-self.thanX1p, 0)
# dc.move(self.thanCh2, 0, y1-self.thanY1p)
dc.coords(self.thanCh1, x1, y1-self.dy, x1, y1+self.dy)
dc.coords(self.thanCh2, x1-self.dx, y1, x1+self.dx, y1)
dc.lift(self.thanCh1)
dc.lift(self.thanCh2)
else:
self.thanCh1 = dc.create_line(x1, y1-self.dy, x1, y1+self.dy, fill="red")
self.thanCh2 = dc.create_line(x1-self.dx, y1, x1+self.dx, y1, fill="green")
self.thanExists = 1
self.thanX1p = x1
self.thanY1p = y1
#############################################################################
#############################################################################
class CrosHairDummy(CrosHair):
"A croshair that does nothing."
def draw(self, x1, y1):
"Draws a croshair and deletes previous croshair."
if self.thanOn:
if self.thanExists:
pass
else:
self.thanCh1 = None
self.thanCh2 = None
self.thanExists = 1
self.thanX1p = x1
self.thanY1p = y1
#############################################################################
#############################################################################
#MODULE LEVEL CODE. IT IS EXECUTED ONLY ONCE
if __name__ == "__main__":
print __doc__
|