2008-10-11 03:22:26 +00:00
|
|
|
#!/usr/bin/env python
|
2008-10-11 15:04:27 +00:00
|
|
|
"""
|
|
|
|
trackplacer -- map journey track editor.
|
|
|
|
|
|
|
|
usage: trackplacesr [-vh?] [filename]
|
|
|
|
|
|
|
|
If the filename is not specified, tracplacer will pop up a file selector.
|
|
|
|
|
|
|
|
Can be started with a map image, in which case we're editing a new journey.
|
|
|
|
Can be started with a track file. A track file is a text file interpreted
|
|
|
|
line-by-line; each line is interpreted as whitespace-separated fields.
|
|
|
|
The first field of the first line must be FILE, and the second field
|
|
|
|
of that line must be valid filename. Subsequent lines must have three
|
|
|
|
fields each: an action tag (JOURNEY, BATTLE, or REST) and two numeric
|
|
|
|
coordinate fields.
|
|
|
|
|
|
|
|
A journey is an object containing a map file name and a (possibly empty)
|
|
|
|
track. This program exists to visually edit journeys.
|
|
|
|
|
|
|
|
The -v option enables verbose logging to standard error.
|
|
|
|
|
|
|
|
The -h or -? options display this summary.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys, exceptions, getopt
|
2008-10-11 03:22:26 +00:00
|
|
|
|
|
|
|
import pygtk
|
|
|
|
pygtk.require('2.0')
|
|
|
|
import gtk
|
|
|
|
|
|
|
|
import wesnoth.wmltools
|
|
|
|
|
2008-10-11 12:34:44 +00:00
|
|
|
# All dependencies on the shape of the data tree live here
|
2008-10-11 03:22:26 +00:00
|
|
|
default_map = "data/core/images/maps/wesnoth.png"
|
2008-10-11 12:34:44 +00:00
|
|
|
journey_icon = "data/core/images/misc/new-journey.png"
|
|
|
|
battle_icon = "data/core/images/misc/new-battle.png"
|
|
|
|
rest_icon = "data/core/images/misc/flag-red.png"
|
|
|
|
|
|
|
|
class ReadException(exceptions.Exception):
|
|
|
|
"Exception thrown while reading a track file."
|
|
|
|
def __init__(self, message, filename, lineno):
|
|
|
|
self.message = message
|
|
|
|
self.filename = filename
|
|
|
|
self.lineno = lineno
|
|
|
|
|
|
|
|
class JourneyTrack:
|
|
|
|
"Represent a journey track on a map."
|
|
|
|
def __init__(self):
|
|
|
|
self.filename = None # Map background of the journey
|
|
|
|
self.track = [] # List of (action, x, y) tuples
|
|
|
|
def write(self, fp):
|
|
|
|
"Record a journey track."
|
|
|
|
fp.write("FILE %s\n" % self.filename)
|
|
|
|
for location in self.track:
|
|
|
|
fp.write("%s %d %d\n" % location)
|
|
|
|
def read(self, fp):
|
|
|
|
"Initialize a journey from map and track information."
|
|
|
|
if type(fp) == type(""):
|
|
|
|
try:
|
|
|
|
fp = open(fp)
|
|
|
|
except FileError:
|
|
|
|
raise ReadException("cannot read", fp)
|
|
|
|
if self.track:
|
|
|
|
raise ReadException("reading with track nonempty", fp.name, 1)
|
|
|
|
if fp.name.endswith(".png") or fp.name.endswith(".jpg"):
|
|
|
|
self.filename = fp.name
|
|
|
|
return
|
|
|
|
if not fp.name.endswith(".trk"):
|
|
|
|
raise ReadException("cannot read this filetype", fp.name, 0)
|
|
|
|
header = fp.readline().split()
|
|
|
|
if header[0] != 'FILE':
|
|
|
|
raise ReadException("missing FILE element", fp.name, 1)
|
|
|
|
else:
|
|
|
|
self.filename = header[1]
|
|
|
|
while (i, line) in enumerate(fp):
|
|
|
|
fields = line.split()
|
|
|
|
if len(fields) != 3:
|
|
|
|
raise ReadException("ill-formed line", fp.name, i+1)
|
|
|
|
(tag, x, y) = fields
|
|
|
|
if tag not in ("JOURNEY", "BATTLE", "REST"):
|
|
|
|
raise ReadException("invalid tag field", fp.name, i+1)
|
|
|
|
try:
|
|
|
|
x = int(x)
|
|
|
|
y = int(y)
|
|
|
|
except ValuError:
|
|
|
|
raise ReadException("invalid coordinate field", fp.name, i+1)
|
|
|
|
self.track.append((tag, x, y))
|
|
|
|
def __getitem__(self, n):
|
|
|
|
return self.track[n]
|
|
|
|
def __setitem__(self, n, v):
|
|
|
|
self.track[n] = v
|
|
|
|
def pop(self):
|
|
|
|
self.track.pop()
|
2008-10-11 03:22:26 +00:00
|
|
|
|
|
|
|
class ModalFileSelector:
|
2008-10-11 23:07:34 +00:00
|
|
|
def __init__(self, default, blocker=False):
|
2008-10-11 03:22:26 +00:00
|
|
|
self.default = default
|
|
|
|
self.filename = None
|
|
|
|
# Create a new file selection widget
|
|
|
|
self.filew = gtk.FileSelection("File selection")
|
2008-10-11 23:07:34 +00:00
|
|
|
self.filew.set_modal(True);
|
2008-10-11 03:22:26 +00:00
|
|
|
|
2008-10-11 23:07:34 +00:00
|
|
|
if blocker:
|
|
|
|
self.filew.connect("destroy", lambda w: sys.exit(0))
|
|
|
|
|
|
|
|
self.filew.ok_button.connect("clicked", self.selection_ok)
|
|
|
|
self.filew.cancel_button.connect("clicked", self.selection_canceled)
|
2008-10-11 03:22:26 +00:00
|
|
|
self.filew.set_filename(self.default)
|
2008-10-11 13:27:57 +00:00
|
|
|
self.filew.run()
|
2008-10-11 03:22:26 +00:00
|
|
|
|
2008-10-11 23:07:34 +00:00
|
|
|
def selection_canceled(self, widget):
|
|
|
|
self.filename = None
|
|
|
|
self.filew.destroy()
|
2008-10-11 03:22:26 +00:00
|
|
|
|
2008-10-11 23:07:34 +00:00
|
|
|
def selection_ok(self, widget):
|
|
|
|
self.filename = self.filew.get_filename()
|
|
|
|
self.filew.destroy()
|
2008-10-11 03:22:26 +00:00
|
|
|
|
2008-10-11 12:34:44 +00:00
|
|
|
class TrackEditor:
|
2008-10-11 15:04:27 +00:00
|
|
|
def __init__(self, filename=None, verbose=False):
|
|
|
|
self.verbose = verbose
|
2008-10-11 12:34:44 +00:00
|
|
|
# Initialize our info about the map and track
|
|
|
|
self.journey = JourneyTrack()
|
2008-10-11 15:04:27 +00:00
|
|
|
self.journey.read(filename)
|
2008-10-11 23:41:41 +00:00
|
|
|
# Backing pixmap for drawing area
|
|
|
|
self.pixmap = None
|
|
|
|
|
2008-10-12 02:30:34 +00:00
|
|
|
# Grab the map into a pixmap
|
2008-10-11 12:59:35 +00:00
|
|
|
self.log("about to read map %s" % self.journey.filename)
|
2008-10-11 12:34:44 +00:00
|
|
|
try:
|
2008-10-12 02:30:34 +00:00
|
|
|
self.map = gtk.gdk.pixbuf_new_from_file(self.journey.filename)
|
|
|
|
except gtk.Gerror:
|
2008-10-11 12:59:35 +00:00
|
|
|
self.fatal_error("Error while reading background map %s" % self.journey.filename)
|
|
|
|
# Now get the icons we'll need for scribbling on the map with.
|
|
|
|
try:
|
2008-10-11 12:34:44 +00:00
|
|
|
self.journey_image = gtk.Image()
|
|
|
|
self.journey_image.set_from_file(journey_icon)
|
2008-10-11 12:59:35 +00:00
|
|
|
self.battle_image = gtk.Image()
|
2008-10-11 12:34:44 +00:00
|
|
|
self.battle_image.set_from_file(battle_icon)
|
|
|
|
self.rest_image = gtk.Image()
|
|
|
|
self.rest_image.set_from_file(rest_icon)
|
2008-10-11 12:59:35 +00:00
|
|
|
except:
|
|
|
|
self.fatal_error("error while reading icons")
|
2008-10-12 02:30:34 +00:00
|
|
|
# Window-layout time
|
2008-10-11 15:11:16 +00:00
|
|
|
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
|
|
|
window.set_name ("Test Input")
|
|
|
|
|
|
|
|
vbox = gtk.VBox(False, 0)
|
|
|
|
window.add(vbox)
|
|
|
|
vbox.show()
|
|
|
|
|
|
|
|
window.connect("destroy", lambda w: gtk.main_quit())
|
|
|
|
|
|
|
|
# Create the drawing area
|
2008-10-12 02:30:34 +00:00
|
|
|
self.drawing_area = gtk.DrawingArea()
|
|
|
|
self.drawing_area.set_size_request(self.map.get_width(), self.map.get_height())
|
|
|
|
vbox.pack_start(self.drawing_area, True, True, 0)
|
2008-10-11 15:11:16 +00:00
|
|
|
|
2008-10-12 02:30:34 +00:00
|
|
|
self.drawing_area.show()
|
2008-10-11 15:11:16 +00:00
|
|
|
|
|
|
|
# Signals used to handle backing pixmap
|
2008-10-12 02:30:34 +00:00
|
|
|
self.drawing_area.connect("expose_event", self.expose_event)
|
|
|
|
self.drawing_area.connect("configure_event", self.configure_event)
|
2008-10-11 15:11:16 +00:00
|
|
|
|
|
|
|
# Event signals
|
2008-10-12 02:30:34 +00:00
|
|
|
self.drawing_area.connect("motion_notify_event", self.motion_notify_event)
|
|
|
|
self.drawing_area.connect("button_press_event", self.button_press_event)
|
2008-10-11 15:11:16 +00:00
|
|
|
|
2008-10-12 02:30:34 +00:00
|
|
|
self.drawing_area.set_events(gtk.gdk.EXPOSURE_MASK
|
2008-10-11 15:11:16 +00:00
|
|
|
| gtk.gdk.LEAVE_NOTIFY_MASK
|
|
|
|
| gtk.gdk.BUTTON_PRESS_MASK
|
|
|
|
| gtk.gdk.POINTER_MOTION_MASK
|
|
|
|
| gtk.gdk.POINTER_MOTION_HINT_MASK)
|
|
|
|
|
|
|
|
# .. And a quit button
|
|
|
|
button = gtk.Button("Quit")
|
|
|
|
vbox.pack_start(button, False, False, 0)
|
|
|
|
|
|
|
|
button.connect_object("clicked", lambda w: w.destroy(), window)
|
|
|
|
button.show()
|
|
|
|
|
|
|
|
window.show()
|
|
|
|
|
|
|
|
gtk.main()
|
|
|
|
|
2008-10-11 12:59:35 +00:00
|
|
|
self.log("initialization successful")
|
|
|
|
|
2008-10-12 02:30:34 +00:00
|
|
|
def refresh_map(self, x=0, y=0, xs=-1, ys=-1):
|
|
|
|
"Refresh part of the drawing area with the apprpriate map rectangle."
|
|
|
|
if xs == -1:
|
|
|
|
xs = self.map.get_width() - x
|
|
|
|
if ys == -1:
|
|
|
|
ys = self.map.get_height() - y
|
|
|
|
#self.map.copy_area(x, y, xs, ys, self.pixmap, x, y)
|
|
|
|
|
2008-10-11 23:41:41 +00:00
|
|
|
# Create a new backing pixmap of the appropriate size
|
|
|
|
def configure_event(self, widget, event):
|
|
|
|
x, y, width, height = widget.get_allocation()
|
|
|
|
self.pixmap = gtk.gdk.Pixmap(widget.window, width, height)
|
2008-10-12 02:30:34 +00:00
|
|
|
self.pixmap.draw_rectangle(widget.get_style().white_gc,
|
|
|
|
True, 0, 0, width, height)
|
|
|
|
#self.refresh_map()
|
2008-10-11 23:41:41 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
# Redraw the screen from the backing pixmap
|
|
|
|
def expose_event(self, widget, event):
|
|
|
|
x , y, width, height = event.area
|
|
|
|
widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
|
|
|
|
self.pixmap, x, y, x, y, width, height)
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Draw a rectangle on the screen
|
|
|
|
def draw_brush(self, widget, x, y):
|
|
|
|
rect = (int(x-5), int(y-5), 10, 10)
|
|
|
|
self.pixmap.draw_rectangle(widget.get_style().black_gc, True,
|
|
|
|
rect[0], rect[1], rect[2], rect[3])
|
|
|
|
widget.queue_draw_area(rect[0], rect[1], rect[2], rect[3])
|
|
|
|
|
|
|
|
def button_press_event(self, widget, event):
|
|
|
|
if event.button == 1 and self.pixmap != None:
|
|
|
|
self.draw_brush(widget, event.x, event.y)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def motion_notify_event(self, widget, event):
|
|
|
|
if event.is_hint:
|
|
|
|
x, y, state = event.window.get_pointer()
|
|
|
|
else:
|
|
|
|
x = event.x
|
|
|
|
y = event.y
|
|
|
|
state = event.state
|
|
|
|
|
|
|
|
if state & gtk.gdk.BUTTON1_MASK and self.pixmap != None:
|
|
|
|
self.draw_brush(widget, x, y)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2008-10-11 12:59:35 +00:00
|
|
|
def log(self, msg):
|
|
|
|
"Notify user of error and die."
|
2008-10-11 15:04:27 +00:00
|
|
|
if self.verbose:
|
2008-10-11 12:59:35 +00:00
|
|
|
print >>sys.stderr, "trackplacer:", msg
|
|
|
|
|
|
|
|
def fatal_error(self, msg):
|
|
|
|
"Notify user of error and die."
|
2008-10-11 15:04:27 +00:00
|
|
|
w = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
|
2008-10-11 13:22:04 +00:00
|
|
|
w.set_markup(msg)
|
|
|
|
w.run()
|
2008-10-11 12:59:35 +00:00
|
|
|
sys.exit(1)
|
2008-10-11 03:22:26 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2008-10-11 15:04:27 +00:00
|
|
|
(options, arguments) = getopt.getopt(sys.argv[1:], "hv?", ['verbose', 'help'])
|
|
|
|
verbose = False
|
|
|
|
for (opt, val) in options:
|
|
|
|
if opt in ('-?', '-h', '--help'):
|
|
|
|
print __doc__
|
|
|
|
sys.exit(0)
|
|
|
|
elif opt in ('-v', '--verbose'):
|
|
|
|
verbose=True
|
|
|
|
|
2008-10-11 03:22:26 +00:00
|
|
|
wesnoth.wmltools.pop_to_top("trackplacer")
|
2008-10-11 15:04:27 +00:00
|
|
|
if arguments:
|
|
|
|
TrackEditor(filename=arguments[0], verbose=verbose)
|
|
|
|
else:
|
|
|
|
TrackEditor(ModalFileSelector(default_map).filename, verbose=verbose)
|