LEFT | RIGHT |
(no file at all) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 # This file is part of LilyPond, the GNU music typesetter. |
| 4 # |
| 5 # Copyright (C) 2012 Joe Neeman <joeneeman@gmail.com> |
| 6 # |
| 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. |
| 11 # |
| 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. |
| 16 # |
| 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/>. |
| 19 |
| 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. |
| 24 |
| 25 from threading import Thread |
| 26 from math import isinf |
| 27 import gtk |
| 28 import gobject |
| 29 import goocanvas |
| 30 import sys |
| 31 import re |
| 32 |
| 33 class GtkSkylineCanvas (goocanvas.Canvas): |
| 34 """A Canvas for displaying skylines.""" |
| 35 def __init__ (self): |
| 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') |
| 42 |
| 43 self.colors = ('black', 'red', 'green', 'blue', 'maroon', 'olive', 'teal
') |
| 44 self.cur_color_index = 0 |
| 45 |
| 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: |
| 50 return |
| 51 |
| 52 scale_x = allocation.width / width |
| 53 scale_y = allocation.height / height |
| 54 scale = min (scale_x, scale_y) |
| 55 self.set_scale (scale) |
| 56 |
| 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 |
| 65 |
| 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) |
| 68 |
| 69 def add_skyline (self, lines): |
| 70 """Adds a skyline to the current canvas, in a new color. |
| 71 |
| 72 The canvas will be rescaled, if necessary, to make room for the |
| 73 new skyline.""" |
| 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] |
| 77 |
| 78 color = self.colors[self.cur_color_index] |
| 79 self.cur_color_index = (self.cur_color_index + 1) % len (self.colors) |
| 80 |
| 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) |
| 88 |
| 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, |
| 93 stroke_color=color, |
| 94 line_width=0.05) |
| 95 self.rescale (self.get_allocation ()) |
| 96 |
| 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.""" |
| 101 |
| 102 def run (self): |
| 103 gtk.gdk.threads_init () |
| 104 self.window = None |
| 105 self.canvas = None |
| 106 gtk.main () |
| 107 |
| 108 # This should only be called from the Gtk main loop. |
| 109 def _destroy_window (self, window): |
| 110 sys.exit (0) |
| 111 |
| 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 () |
| 120 |
| 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) |
| 125 |
| 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)) |
| 129 |
| 130 thread = SkylineWindowThread () |
| 131 thread.setDaemon (True) |
| 132 thread.start () |
| 133 |
| 134 def lines(infile): |
| 135 line = infile.readline() |
| 136 while len(line) > 0: |
| 137 yield line[:-1] |
| 138 line = infile.readline() |
| 139 |
| 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) |
| 143 |
| 144 # The main loop just reads lines from stdin and feeds them to the |
| 145 # display. |
| 146 current_skyline = [] |
| 147 for line in lines(sys.stdin): |
| 148 if not line: |
| 149 thread.add_skyline(current_skyline) |
| 150 current_skyline = [] |
| 151 continue |
| 152 |
| 153 m = re.search (line_re, line) |
| 154 if m is None: |
| 155 print('line did not match') |
| 156 else: |
| 157 pts = map(float, m.groups()) |
| 158 if not any(map(isinf, pts)): |
| 159 current_skyline.append(pts) |
| 160 |
LEFT | RIGHT |