#!/usr/bin/python
#
# $Id: draw-proto.py,v 1.1 2000/08/08 09:47:47 petli Exp $
#
# examples/draw.py -- protocol test application.
#
#    Copyright (C) 2000 Peter Liljenberg <petli@ctrl-c.liu.se>
#
#    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.
#
#    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


import sys
import os

# Change path so we find Xlib
sys.path.insert(1, os.path.join(sys.path[0], '..'))

from Xlib import X
from Xlib.protocol import display
from Xlib.protocol.request import *

# Application window (only one)
class Window:
    def __init__(self, display):
	self.d = display
	self.objects = []

	# Find which screen to open the window on
	self.screen = self.d.info.roots[self.d.default_screen]

	# Allocate ids to the window and gc
	self.window = self.d.allocate_resource_id()
	self.gc = self.d.allocate_resource_id()

	# Create a window
	CreateWindow(self.d, None,
		     self.screen.root_depth,
		     self.window,
		     self.screen.root,
		     50, 50, 300, 200, 2,
		     X.InputOutput,
		     X.CopyFromParent,

		     # special attribute values
		     background_pixel = self.screen.white_pixel,
		     event_mask = (X.ExposureMask |
				   X.StructureNotifyMask |
				   X.ButtonPressMask |
				   X.ButtonReleaseMask |
				   X.Button1MotionMask),
		     colormap = X.CopyFromParent)

	# Create a gc for drawing
	CreateGC(self.d, None,
		 self.gc,
		 self.window,

		 # special attribute values
		 foreground = self.screen.black_pixel,
		 background = self.screen.white_pixel)

	# Map the window, making it visible
	MapWindow(self.d, None, self.window)

    # Main loop, handling events
    
    def loop(self):
	current = None
	while 1:
	    e = self.d.next_event()

	    # Window has been destroyed, quit
	    if e.type == X.DestroyNotify:
		sys.exit(0)

	    # Some part of the window has been exposed, 
	    # redraw all the objects.
	    if e.type == X.Expose:
		for o in self.objects:
		    o.expose(e)

	    # Left button pressed, start to draw
	    if e.type == X.ButtonPress and e.detail == 1:
		current = Movement(self, e)
		self.objects.append(current)

	    # Left button released, finish drawing
	    if e.type == X.ButtonRelease and e.detail == 1 and current:
		current.finish(e)
		current = None

	    # Mouse movement with button pressed, draw 
	    if e.type == X.MotionNotify and current:
		current.motion(e)

# A drawed objects, consisting of either a single
# romboid, or two romboids connected by a winding line

class Movement:
    def __init__(self, win, ev):
	self.win = win

	self.left = ev.event_x - 5
	self.right = ev.event_x + 5
	self.top = ev.event_y - 5
	self.bottom = ev.event_y + 5
	
	self.time = ev.time
	self.lines = [(ev.event_x, ev.event_y)]
	
	self.first = Romboid(self.win, ev)
	self.last = None

    def motion(self, ev):
	# Find all the mouse coordinates since the
	# last event received
	
	r = GetMotionEvents(self.win.d,
			    window = self.win.window,
			    start = self.time,
			    stop = ev.time)

	if not r.events:
	    return

	# Record the previous last coordinate, and append
	# the new coordinates
	
	firstline = len(self.lines) - 1

	# Discard the first coordinate if that is identical to
	# the last recorded coordinate

	pos = r.events[0]
	if (pos.x, pos.y) == self.lines[-1]:
	    events = r.events[1:]
	else:
	    events = r.events

	# Append all coordinates
	for pos in events:
	    x = pos.x
	    y = pos.y

	    if x < self.left:
		self.left = x
	    if x > self.right:
		self.right = x

	    if y < self.top:
		self.top = y
	    if y > self.bottom:
		self.bottom = y

	    self.lines.append((x, y))

	# Append the event coordinate, if that is different from the
	# last movement coordinate
	if (ev.event_x, ev.event_y) != self.lines[-1]:
	    self.lines.append((ev.event_x, ev.event_y))
	
	# Draw a line between the new coordinates
	PolyLine(self.win.d, None,
		 X.CoordModeOrigin,
		 self.win.window,
		 self.win.gc,
		 self.lines[firstline:])

	self.time = ev.time

    def finish(self, ev):
	self.motion(ev)
	if len(self.lines) > 1:
	    self.last = Romboid(self.win, ev)

	    self.left = min(ev.event_x - 5, self.left)
	    self.right = max(ev.event_x + 5, self.right)
	    self.top = min(ev.event_y - 5, self.top)
	    self.bottom = max(ev.event_y + 5, self.bottom)

    def expose(self, ev):
	# We should check if this object is in the exposed
	# area, but I can't be bothered right now, so just
	# redraw on the last Expose in every batch
	
	if ev.count == 0:
	    self.first.draw()
	    if self.last:
		# Redraw all the lines
		PolyLine(self.win.d, None,
			 X.CoordModeOrigin,
			 self.win.window,
			 self.win.gc,
			 self.lines)
		self.last.draw()
		
	
# A romboid, drawed around the Movement endpoints
class Romboid:
    def __init__(self, win, ev):
	self.win = win
	self.x = ev.event_x
	self.y = ev.event_y
	self.draw()

    def draw(self):
	# Draw the segments of the romboid
	PolyLine(self.win.d, None,
		 X.CoordModePrevious,
		 self.win.window,
		 self.win.gc,
		 [(self.x, self.y - 5),
		  (5, 5),
		  (-5, 5),
		  (-5, -5),
		  (5, -5)])


if __name__ == '__main__':
    Window(display.Display()).loop()
    
				   
    
