Author: esr
Date: Tue Oct 14 04:03:58 2008
New Revision: 30148

URL: http://svn.gna.org/viewcvs/wesnoth?rev=30148&view=rev
Log:
trackplacer: Major refactoring step.

Modified:
    trunk/data/tools/trackplacer

Modified: trunk/data/tools/trackplacer
URL: 
http://svn.gna.org/viewcvs/wesnoth/trunk/data/tools/trackplacer?rev=30148&r1=30147&r2=30148&view=diff
==============================================================================
--- trunk/data/tools/trackplacer (original)
+++ trunk/data/tools/trackplacer Tue Oct 14 04:03:58 2008
@@ -50,7 +50,7 @@
 Design and implementation by Eric S. Raymond, October 2008.
 '''
 
-import sys, os, re, time, exceptions, getopt
+import sys, os, re, math, time, exceptions, getopt
 
 import pygtk
 pygtk.require('2.0')
@@ -71,20 +71,29 @@
 icon_presentation_order = ("JOURNEY", "BATTLE", "REST")
 segmenters = ("BATTLE","REST")
 
-# Size of the rectangle around the mouse pointer within which the code
-# will seek tracking dots. Should be about (N-1)/2 where N is the
-# pixel radius of the largest icon.  For this and other reasons, it's
-# helpful if the tracking dot and other icons all have a square
-# aspect ratio and an odd number of pixels on the sise, so each has
-# a well-defined center pixel.
-vision_distance = 12
-
 class IOException(exceptions.Exception):
     "Exception thrown while reading a track file."
     def __init__(self, message, path, lineno=None):
         self.message = message
         self.path = path
         self.lineno = lineno
+
+# Basic functions for bashing points and rectangles
+
+def distance(x1, y1, x2, y2):
+    "Euclidean distance."
+    return math.sqrt((x1 - x2)**2 + abs(y1 - y2)**2)
+
+def within(x, y, (l, t, r, d)):
+    "Is point within specified rectangle?"
+    if x >= l and x < l + r and y >= t and y < t + d:
+        return True
+    return False
+
+def overlaps((x, y, xd, yd), rect):
+    "Do two rectangles overlap?"
+    return within(x, y, rect) or within(x+xd-1, y, rect) or \
+           within(x, y+yd-1, rect) or within(x+xd-1, y+yd+1, rect)
 
 class JourneyTrack:
     "Represent a journey track on a map."
@@ -167,34 +176,29 @@
         return self.track[n]
     def __setitem__(self, n, v):
         self.track[n] = v
-    def find(self, x, y, d=vision_distance):
-        "Find all track actions near a given point."
+    def neighbors(self, x, y):
+        "Return list of neighbors, enumerated and sorted by distance."
+        candidates = zip(range(len(self.track)), self.track)
+        candidates.sort(lambda (i1, (a1, x1, y1)), (i2, (a2, x2, y2)): 
cmp(distance(x, y, x1, y1), distance(x, y, x2, y2)))
+        return candidates
+
+    def find(self, x, y):
+        "Find all track actions at given point."
         candidates = []
-        ind = 0
-        for (tag, xt, yt) in self.track:
-            if x >= xt - d and x <= xt + d and y >= yt - d and y <= yt + d:
-                candidates.append(ind)
-            ind += 1
+        for (i, (tag, xt, yt)) in enumerate(self.track):
+            if x == xt and y == yt:
+                candidates.append(i)
         return candidates
-    def append(self, tag, x, y):
+    def insert(self, (action, x, y)):
         "Append a feature to the track."
-        self.track.append((tag, x, y))
+        # FIXME: implement smarter insertion
+        self.track.append((action, x, y))
     def remove(self, x, y):
         "Remove a feature from the track."
         ind = self.find(x, y)
         if ind:
             # Prefer to delete the most recent feature
             self.track = self.track[:ind[-1]] + self.track[ind[-1]+1:]
-    def snap_to(self, x, y, d=vision_distance):
-        "Snap a location to a nearby track feature, if there is one."
-        candidates = []
-        for (tag, xt, yt) in self.track:
-            if x >= xt - d and x <= xt + d and y >= yt - d and y <= yt + d:
-                candidates.append((xt, yt))
-        if len(candidates) == 1:
-            return candidates[0]
-        else:
-            return None
     def __str__(self):
         return self.mapfile + ": " + `self.track`
 
@@ -249,7 +253,7 @@
             self.last_read = path
         self.log("Initial track is %s" % self.journey)
         self.action = "JOURNEY"
-        self.last_x = self.last_y = None 
+        self.selected = None
 
         # Backing pixmap for drawing area
         self.pixmap = None
@@ -428,20 +432,45 @@
             ys = self.map_height - y
         self.pixmap.draw_drawable(self.default_gc, self.map, x, y, x, y, xs, 
ys)
 
-    def erase_feature(self, widget, x, y, action):
+    def box(self, (action, x, y)):
+        "Compute the bounding box for an icon of type ACTION at X, Y."
+        return self.action_dictionary[action].bounding_box(x, y)
+
+    def snap_to(self, x, y):
+        "Snap a location to the nearest feature whose bounding box holds it."
+        self.log("Neighbors of %d, %d are %s" % (x, y, 
self.journey.neighbors(x, y)))
+        for (i, item) in self.journey.neighbors(x, y):
+            if within(x, y, self.box(item)):
+                return i
+        else:
+            return None
+
+    def neighbors(self, (action, x, y)):
+        "Return all track items with bounding boxes overlapping this one:"
+        rect = self.action_dictionary[action].bounding_box(x, y)
+        return filter(lambda item: overlaps(rect, self.box(item)),
+                      self.journey.track)
+
+    def erase_feature(self, widget, (action, x, y)):
         "Erase specified icon from the map."
-        icon = self.action_dictionary[action]
-        rect = icon.bounding_box(x, y)
-        self.log("Erasing action=%s, dest=%s" % (icon.action, rect))
-        self.refresh_map(*rect)
-        widget.queue_draw_area(*rect)
-
-    def draw_feature(self, widget, x, y, action):
+        neighbors = self.neighbors((action, x, y))
+        for (na, nx, ny) in neighbors:
+            rect = self.box((na, nx, ny))
+            self.log("Erasing action=%s, dest=%s" % (na, rect))
+            self.refresh_map(*rect)
+            widget.queue_draw_area(*rect)
+        for (na, nx, ny) in neighbors:
+            if x != nx and y != ny:
+                self.log("Redrawing action=%s, dest=%s" % (icon.action, rect))
+                self.draw_feature(widget, (naction, nx, ny)) 
+
+    def draw_feature(self, widget, (action, x, y)):
         "Draw specified icon on the map."
-        icon = self.action_dictionary[action]
-        rect = icon.bounding_box(x, y)
-        self.log("Drawing action=%s, dest=%s" % (icon.action, rect))
-        self.pixmap.draw_pixbuf(self.default_gc, icon.icon, 0, 0, *rect)
+        rect = self.box((action, x, y))
+        self.log("Drawing action=%s, dest=%s" % (action, rect))
+        self.pixmap.draw_pixbuf(self.default_gc,
+                                self.action_dictionary[action].icon,
+                                0, 0, *rect)
         widget.queue_draw_area(*rect)
         
     def configure_event(self, widget, event):
@@ -450,8 +479,8 @@
         self.pixmap = gtk.gdk.Pixmap(widget.window, width, height)
         self.default_gc = self.drawing_area.get_style().fg_gc[gtk.STATE_NORMAL]
         self.refresh_map()
-        for (action, x, y) in self.journey.track:
-            self.draw_feature(widget, x, y, action)
+        for item in self.journey.track:
+            self.draw_feature(widget, item)
         return True
 
     def expose_event(self, widget, event):
@@ -465,26 +494,22 @@
         if event.button == 1 and self.pixmap != None:
             x = int(event.x)
             y = int(event.y)
-            feature = self.journey.snap_to(x, y)
+            self.selected = self.snap_to(x, y)
             # Skip the redraw in half the cases
-            self.log("Action %s at (%d, %d): feature = %s" % (self.action, x, 
y, feature))
-            if (feature == None) and (self.action == "DELETE"):
+            self.log("Action %s at (%d, %d): feature = %s" % (self.action, x, 
y, self.selected))
+            if (self.selected == None) and (self.action == "DELETE"):
                 return
-            if (feature != None) and (self.action != "DELETE"):
+            if (self.selected != None) and (self.action != "DELETE"):
                 return
             # Actual drawing and mutation of the journey track happens here
-            if not feature and self.action != "DELETE":
-                self.draw_feature(widget, x, y, self.action)
-                self.journey.track.append((self.action, x, y))
-                self.last_x = x
-                self.last_y = y
-            elif feature and self.action == "DELETE":
-                candidates = self.journey.find(x, y)
-                if len(candidates) == 1:
-                    # Prefer to delete the most recent candidate
-                    (action, x, y) = self.journey.track[candidates[-1]]
-                    self.erase_feature(widget, x, y, action)
-                    self.journey.remove(x, y)
+            if not self.selected and self.action != "DELETE":
+                self.draw_feature(widget, (self.action, x, y))
+                self.journey.insert((self.action, x, y))
+            elif self.selected != None and self.action == "DELETE":
+                (action, x, y) = self.journey.track[self.selected]
+                self.log("Deletion snapped to feature %d %s" % 
(self.selected,(action,x,y)))
+                self.erase_feature(widget, (action, x, y))
+                self.journey.remove(x, y)
             self.log("Track is %s" % self.journey)
         return True
 
@@ -498,17 +523,14 @@
         state = event.state
 
         # This code enables dragging icons.
-        if state & gtk.gdk.BUTTON1_MASK and self.pixmap != None:            
-            if self.last_x and self.last_y:
-                candidates = self.journey.find(self.last_x, self.last_y)
-                if candidates:
-                    most_recent_candidate = candidates[-1]
-                    (action, lx, ly) = 
self.journey.track[most_recent_candidate]
-                    self.erase_feature(widget, lx, ly, action)
-                    self.journey.track[most_recent_candidate] = (action, x, y)
-                    self.draw_feature(widget, x, y, action)
-                    self.last_x, self.last_y = x, y
-                    self.log("Track is %s" % self.journey)
+        # if state & gtk.gdk.BUTTON1_MASK and self.pixmap != None:            
+        #    if candidates:
+        #        most_recent_candidate = candidates[-1]
+        #        (action, lx, ly) = self.journey.track[most_recent_candidate]
+        #        self.erase_feature(widget, lx, ly, action)
+        #        self.journey.track[most_recent_candidate] = (action, x, y)
+        #        self.draw_feature(widget, x, y, action)
+        #        self.log("Track is %s" % self.journey)
         return True
 
     #


_______________________________________________
Wesnoth-commits mailing list
[email protected]
https://mail.gna.org/listinfo/wesnoth-commits

Reply via email to