summaryrefslogtreecommitdiff
path: root/scripts/auxiliar/skyline_viewer.py
blob: 83e45f80f7b9001cb6fe5589cc76e959b3857b22 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python

# This file is part of LilyPond, the GNU music typesetter.
#
# Copyright (C) 2012 Joe Neeman <joeneeman@gmail.com>
#
# LilyPond 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 3 of the License, or
# (at your option) any later version.
#
# LilyPond 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 LilyPond.  If not, see <http://www.gnu.org/licenses/>.

# A GTK+ program for debugging skylines. The program reads a sequence
# of line segments from stdin (one line segment per line of stdin, in the format
# '(x1, y1) (x2, y2)'). A skyline is terminated by an empty line, which
# causes the skyline to be displayed on the screen.

from threading import Thread
from math import isinf
import gtk
import gobject
import goocanvas
import sys
import re

class GtkSkylineCanvas (goocanvas.Canvas):
    """A Canvas for displaying skylines."""
    def __init__ (self):
        super (GtkSkylineCanvas, self).__init__ ()
        self.connect ('size-allocate', GtkSkylineCanvas.rescale)
        self.x_min = float ('inf')
        self.x_max = float ('-inf')
        self.y_min = float ('inf')
        self.y_max = float ('-inf')

        self.colors = ('black', 'red', 'green', 'blue', 'maroon', 'olive', 'teal')
        self.cur_color_index = 0

    def rescale (self, allocation):
        width = (self.x_max - self.x_min + 1) * 1.1
        height = (self.y_max - self.y_min + 1) * 1.1
        if width <= 0 or height <= 0:
            return

        scale_x = allocation.width / width
        scale_y = allocation.height / height
        scale = min (scale_x, scale_y)
        self.set_scale (scale)

        center_x = (self.x_max + self.x_min) / 2
        center_y = (self.y_max + self.y_min) / 2
        actual_width = allocation.width / scale
        actual_height = allocation.height / scale
        actual_min_x = center_x - actual_width / 2
        actual_max_x = center_x + actual_width / 2
        actual_min_y = center_y - actual_height / 2
        actual_max_y = center_y + actual_height / 2

        self.set_bounds (actual_min_x, actual_min_y, actual_max_x, actual_max_y)
        self.scroll_to (actual_min_x, actual_min_y)

    def add_skyline (self, lines):
        """Adds a skyline to the current canvas, in a new color.

        The canvas will be rescaled, if necessary, to make room for the
        new skyline."""
        # Flip vertically, because goocanvas thinks higher numbers are
        # further down, while lilypond thinks they're further up.
        lines = [(x1, -y1, x2, -y2) for (x1, y1, x2, y2) in lines]

        color = self.colors[self.cur_color_index]
        self.cur_color_index = (self.cur_color_index + 1) % len (self.colors)

        # Update the bounding box of the skylines.
        x_vals = [s[0] for s in lines] + [s[2] for s in lines]
        y_vals = [s[1] for s in lines] + [s[3] for s in lines]
        self.x_min = min ([self.x_min] + x_vals)
        self.x_max = max ([self.x_max] + x_vals)
        self.y_min = min ([self.y_min] + y_vals)
        self.y_max = max ([self.y_max] + y_vals)

        # Add the lines to the canvas.
        root = self.get_root_item ()
        for (x1, y1, x2, y2) in lines:
            goocanvas.polyline_new_line (root, x1, y1, x2, y2,
                    stroke_color=color,
                    line_width=0.05)
        self.rescale (self.get_allocation ())

# We want to run the gtk main loop in a separate thread so that
# the main thread can be responsible for reading stdin.
class SkylineWindowThread (Thread):
    """A thread that runs a Gtk.Window displaying a skyline."""

    def run (self):
        gtk.gdk.threads_init ()
        self.window = None
        self.canvas = None
        gtk.main ()

    # This should only be called from the Gtk main loop.
    def _destroy_window (self, window):
        sys.exit (0)

    # This should only be called from the Gtk main loop.
    def _setup_window (self):
        if self.window is None:
            self.window = gtk.Window ()
            self.canvas = GtkSkylineCanvas ()
            self.window.add (self.canvas)
            self.window.connect ("destroy", self._destroy_window)
            self.window.show_all ()

    # This should only be called from the Gtk main loop.
    def _add_skyline (self, lines):
        self._setup_window ()
        self.canvas.add_skyline (lines)

    def add_skyline (self, lines):
        # Copy the lines, just in case someone modifies them.
        gobject.idle_add (self._add_skyline, list (lines))

thread = SkylineWindowThread ()
thread.setDaemon (True)
thread.start ()

def lines(infile):
    line = infile.readline()
    while len(line) > 0:
        yield line[:-1]
        line = infile.readline()

point_re_str = r'\(([a-z.0-9-]*) *,([a-z0-9.-]*)\)'
line_re_str = point_re_str + r' +' + point_re_str
line_re = re.compile (line_re_str)

# The main loop just reads lines from stdin and feeds them to the
# display.
current_skyline = []
for line in lines(sys.stdin):
    if not line:
        thread.add_skyline(current_skyline)
        current_skyline = []
        continue

    m = re.search (line_re, line)
    if m is None:
        print('line did not match')
    else:
        pts = map(float, m.groups())
        if not any(map(isinf, pts)):
            current_skyline.append(pts)