3 # This file is part of LilyPond, the GNU music typesetter.
5 # Copyright (C) 2012 Joe Neeman <joeneeman@gmail.com>
7 # LilyPond is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # LilyPond is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 # A GTK+ program for debugging skylines. The program reads a sequence
21 # of line segments from stdin (one line segment per line of stdin, in the format
22 # '(x1, y1) (x2, y2)'). A skyline is terminated by an empty line, which
23 # causes the skyline to be displayed on the screen.
25 from threading import Thread
26 from math import isinf
33 class GtkSkylineCanvas (goocanvas.Canvas):
34 """A Canvas for displaying skylines."""
36 super (GtkSkylineCanvas, self).__init__ ()
37 self.connect ('size-allocate', GtkSkylineCanvas.rescale)
38 self.x_min = float ('inf')
39 self.x_max = float ('-inf')
40 self.y_min = float ('inf')
41 self.y_max = float ('-inf')
43 self.colors = ('black', 'red', 'green', 'blue', 'maroon', 'olive', 'teal')
44 self.cur_color_index = 0
46 def rescale (self, allocation):
47 width = (self.x_max - self.x_min + 1) * 1.1
48 height = (self.y_max - self.y_min + 1) * 1.1
49 if width <= 0 or height <= 0:
52 scale_x = allocation.width / width
53 scale_y = allocation.height / height
54 scale = min (scale_x, scale_y)
55 self.set_scale (scale)
57 center_x = (self.x_max + self.x_min) / 2
58 center_y = (self.y_max + self.y_min) / 2
59 actual_width = allocation.width / scale
60 actual_height = allocation.height / scale
61 actual_min_x = center_x - actual_width / 2
62 actual_max_x = center_x + actual_width / 2
63 actual_min_y = center_y - actual_height / 2
64 actual_max_y = center_y + actual_height / 2
66 self.set_bounds (actual_min_x, actual_min_y, actual_max_x, actual_max_y)
67 self.scroll_to (actual_min_x, actual_min_y)
69 def add_skyline (self, lines):
70 """Adds a skyline to the current canvas, in a new color.
72 The canvas will be rescaled, if necessary, to make room for the
74 # Flip vertically, because goocanvas thinks higher numbers are
75 # further down, while lilypond thinks they're further up.
76 lines = [(x1, -y1, x2, -y2) for (x1, y1, x2, y2) in lines]
78 color = self.colors[self.cur_color_index]
79 self.cur_color_index = (self.cur_color_index + 1) % len (self.colors)
81 # Update the bounding box of the skylines.
82 x_vals = [s[0] for s in lines] + [s[2] for s in lines]
83 y_vals = [s[1] for s in lines] + [s[3] for s in lines]
84 self.x_min = min ([self.x_min] + x_vals)
85 self.x_max = max ([self.x_max] + x_vals)
86 self.y_min = min ([self.y_min] + y_vals)
87 self.y_max = max ([self.y_max] + y_vals)
89 # Add the lines to the canvas.
90 root = self.get_root_item ()
91 for (x1, y1, x2, y2) in lines:
92 goocanvas.polyline_new_line (root, x1, y1, x2, y2,
95 self.rescale (self.get_allocation ())
97 # We want to run the gtk main loop in a separate thread so that
98 # the main thread can be responsible for reading stdin.
99 class SkylineWindowThread (Thread):
100 """A thread that runs a Gtk.Window displaying a skyline."""
103 gtk.gdk.threads_init ()
108 # This should only be called from the Gtk main loop.
109 def _destroy_window (self, window):
112 # This should only be called from the Gtk main loop.
113 def _setup_window (self):
114 if self.window is None:
115 self.window = gtk.Window ()
116 self.canvas = GtkSkylineCanvas ()
117 self.window.add (self.canvas)
118 self.window.connect ("destroy", self._destroy_window)
119 self.window.show_all ()
121 # This should only be called from the Gtk main loop.
122 def _add_skyline (self, lines):
123 self._setup_window ()
124 self.canvas.add_skyline (lines)
126 def add_skyline (self, lines):
127 # Copy the lines, just in case someone modifies them.
128 gobject.idle_add (self._add_skyline, list (lines))
130 thread = SkylineWindowThread ()
131 thread.setDaemon (True)
135 line = infile.readline()
138 line = infile.readline()
140 point_re_str = r'\(([a-z.0-9-]*) *,([a-z0-9.-]*)\)'
141 line_re_str = point_re_str + r' +' + point_re_str
142 line_re = re.compile (line_re_str)
144 # The main loop just reads lines from stdin and feeds them to the
147 for line in lines(sys.stdin):
149 thread.add_skyline(current_skyline)
153 m = re.search (line_re, line)
155 print('line did not match')
157 pts = map(float, m.groups())
158 if not any(map(isinf, pts)):
159 current_skyline.append(pts)