Diagram.py: simplify setting fill and stroke when drawing rectangles
[experiments/RadialSymmetry.git] / RadialSymmetry.py
1 #!/usr/bin/env python
2 #
3 # An app to interactively change RadialSymmetryDiagram properies
4 #
5 # Copyright (C) 2015  Antonio Ospite <ao2@ao2.it>
6 #
7 # This program 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 # This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 from gi.repository import Gtk, Gio
21 import sys
22 import math
23
24 import RadialSymmetryDiagram
25
26 APPLICATION_ID = "it.ao2.RadialSymmetryApp"
27 WINDOW_TITLE = "Radial Symmetry"
28 CANVAS_WIDTH = 600
29 CANVAS_HEIGHT = 600
30
31
32 class RadialSymmetryWindow(Gtk.ApplicationWindow):
33
34     def __init__(self):
35         Gtk.Window.__init__(self, title=WINDOW_TITLE)
36         self.set_border_width(10)
37         self.set_property('resizable', False)
38
39         self.model = model = RadialSymmetryDiagram.RadialSymmetryModel()
40         self.diagram = RadialSymmetryDiagram.RadialSymmetryDiagram(CANVAS_WIDTH,
41                                                                    CANVAS_HEIGHT)
42         model.base_polygon_sides = 6
43         ad = Gtk.Adjustment(model.base_polygon_sides, 1, 32, 1, 0, 0)
44         self.spin_base_polygon_sides = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
45         self.spin_base_polygon_sides.connect("value-changed", self.get_base_polygon_sides)
46
47         model.element_sides = 4
48         ad = Gtk.Adjustment(model.element_sides, 2, 16, 1, 0, 0)
49         self.spin_elem_sides = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
50         self.spin_elem_sides.connect("value-changed", self.get_elem_sides)
51
52         max_element_radius = min(CANVAS_WIDTH, CANVAS_HEIGHT)
53         model.element_radius = max_element_radius / 4.0
54         ad = Gtk.Adjustment(model.element_radius, 0, max_element_radius, 1, 0, 0)
55         self.spin_element_radius = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
56         self.spin_element_radius.connect("value-changed", self.get_element_radius)
57
58         model.radial_orientate = True
59         self.checkbox_radial_orientate = Gtk.CheckButton(label="Radial orientation")
60         self.checkbox_radial_orientate.set_active(model.radial_orientate)
61         self.checkbox_radial_orientate.connect("toggled", self.get_radial_orientate)
62
63         model.show_base_polygon = False
64         self.checkbox_base_polygon = Gtk.CheckButton(label="Draw base polygon")
65         self.checkbox_base_polygon.set_active(model.show_base_polygon)
66         self.checkbox_base_polygon.connect("toggled", self.get_base_polygon)
67
68         max_base_polygon_radius = min(CANVAS_WIDTH, CANVAS_HEIGHT)
69         model.base_polygon_radius = max_base_polygon_radius / 4.0
70         ad = Gtk.Adjustment(model.base_polygon_radius, 0, max_base_polygon_radius, 1, 0, 0)
71         self.spin_base_polygon_radius = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
72         self.spin_base_polygon_radius.connect("value-changed", self.get_base_polygon_radius)
73
74         model.show_stars = False
75         self.checkbox_stars = Gtk.CheckButton(label="Draw stars")
76         self.checkbox_stars.set_active(model.show_stars)
77         self.checkbox_stars.connect("toggled", self.get_draw_stars)
78
79         model.show_elements = True
80         self.checkbox_elements = Gtk.CheckButton(label="Draw elements")
81         self.checkbox_elements.set_active(model.show_elements)
82         self.checkbox_elements.connect("toggled", self.get_draw_elements)
83
84         model.show_labels = False
85         self.checkbox_labels = Gtk.CheckButton(label="Draw labels")
86         self.checkbox_labels.set_active(model.show_labels)
87         self.checkbox_labels.connect("toggled", self.get_draw_labels)
88
89         model.element_angle_offset = 0
90         ad2 = Gtk.Adjustment(math.degrees(model.element_angle_offset), -1, 360, 1, 0, 0)
91         self.spin_element_angle = Gtk.SpinButton(adjustment=ad2, climb_rate=1, digits=0)
92         self.spin_element_angle.connect("value-changed", self.get_element_angle)
93
94         model.base_polygon_angle = 0
95         ad = Gtk.Adjustment(math.degrees(model.base_polygon_angle), -1, 360, 1, 0, 0)
96         self.spin_global_angle = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
97         self.spin_global_angle.connect("value-changed", self.get_global_angle)
98
99         self.darea = Gtk.DrawingArea()
100         self.darea.connect("draw", self.draw)
101         self.darea.set_size_request(CANVAS_WIDTH, CANVAS_HEIGHT)
102
103         self.export_button = Gtk.Button(label="Export diagram")
104         self.export_button.connect("clicked", self.on_export_diagram)
105
106         controls = Gtk.VBox(spacing=10)
107
108         vbox = Gtk.VBox()
109         vbox.pack_start(Gtk.Label("Elements"), False, False, 0)
110         vbox.pack_start(self.spin_base_polygon_sides, False, False, 0)
111         controls.pack_start(vbox, False, False, 0)
112
113         vbox = Gtk.VBox()
114         vbox.pack_start(Gtk.Label("Element sides"), False, False, 0)
115         vbox.pack_start(self.spin_elem_sides, False, False, 0)
116         controls.pack_start(vbox, False, False, 0)
117
118         vbox = Gtk.VBox()
119         vbox.pack_start(Gtk.Label("Element radius"), False, False, 0)
120         vbox.pack_start(self.spin_element_radius, False, False, 0)
121         controls.pack_start(vbox, False, False, 0)
122
123         controls.pack_start(self.checkbox_radial_orientate, False, False, 0)
124
125         vbox = Gtk.VBox()
126         vbox.pack_start(Gtk.Label("Element angle"), False, False, 0)
127         vbox.pack_start(self.spin_element_angle, False, False, 0)
128         controls.pack_start(vbox, False, False, 0)
129
130         vbox = Gtk.VBox()
131         vbox.pack_start(Gtk.Label("Global angle"), False, False, 0)
132         vbox.pack_start(self.spin_global_angle, False, False, 0)
133         controls.pack_start(vbox, False, False, 0)
134
135         controls.pack_start(self.checkbox_base_polygon, False, False, 0)
136
137         vbox = Gtk.VBox()
138         vbox.pack_start(Gtk.Label("Base polygon radius"), False, False, 0)
139         vbox.pack_start(self.spin_base_polygon_radius, False, False, 0)
140         controls.pack_start(vbox, False, False, 0)
141
142         controls.pack_start(self.checkbox_elements, False, False, 0)
143         controls.pack_start(self.checkbox_stars, False, False, 0)
144         controls.pack_start(self.checkbox_labels, False, False, 0)
145         controls.pack_end(self.export_button, False, False, 0)
146
147         main_container = Gtk.HBox(spacing=10)
148         main_container.add(controls)
149         main_container.add(self.darea)
150
151         self.add(main_container)
152
153     def normalize_angle(self, angle):
154         if angle == 360:
155             angle = 0
156         elif angle == -1:
157             angle = 359
158
159         return angle
160
161     def get_base_polygon_sides(self, event):
162         self.model.base_polygon_sides = self.spin_base_polygon_sides.get_value_as_int()
163         self.darea.queue_draw()
164
165     def get_elem_sides(self, event):
166         self.model.element_sides = self.spin_elem_sides.get_value_as_int()
167         self.darea.queue_draw()
168
169     def get_element_radius(self, event):
170         self.model.element_radius = self.spin_element_radius.get_value_as_int()
171         self.darea.queue_draw()
172
173     def get_radial_orientate(self, event):
174         self.model.radial_orientate = self.checkbox_radial_orientate.get_active()
175         self.darea.queue_draw()
176
177     def get_base_polygon(self, event):
178         self.model.show_base_polygon = self.checkbox_base_polygon.get_active()
179         self.darea.queue_draw()
180
181     def get_base_polygon_radius(self, event):
182         self.model.base_polygon_radius = self.spin_base_polygon_radius.get_value_as_int()
183         self.darea.queue_draw()
184
185     def get_draw_elements(self, event):
186         self.model.show_elements = self.checkbox_elements.get_active()
187         self.darea.queue_draw()
188
189     def get_draw_stars(self, event):
190         self.model.show_stars = self.checkbox_stars.get_active()
191         self.darea.queue_draw()
192
193     def get_draw_labels(self, event):
194         self.model.show_labels = self.checkbox_labels.get_active()
195         self.darea.queue_draw()
196
197     def get_global_angle(self, event):
198         angle = self.normalize_angle(self.spin_global_angle.get_value_as_int())
199         self.spin_global_angle.set_value(angle)
200         self.model.base_polygon_angle = math.radians(angle)
201         self.darea.queue_draw()
202
203     def get_element_angle(self, event):
204         angle = self.normalize_angle(self.spin_element_angle.get_value_as_int())
205         self.spin_element_angle.set_value(angle)
206         self.model.element_angle_offset = math.radians(angle)
207         self.darea.queue_draw()
208
209     def on_export_diagram(self, event):
210         dialog = Gtk.FileChooserDialog("Export Diagram", self,
211                                        Gtk.FileChooserAction.SAVE,
212                                        (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
213                                         Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
214         dialog.set_property('do-overwrite-confirmation', True)
215
216         filter_svg = Gtk.FileFilter()
217         filter_svg.set_name("SVG files")
218         filter_svg.add_mime_type("image/svg+xml")
219         dialog.add_filter(filter_svg)
220
221         filter_png = Gtk.FileFilter()
222         filter_png.set_name("PNG files")
223         filter_png.add_mime_type("image/png")
224         dialog.add_filter(filter_png)
225
226         response = dialog.run()
227         if response == Gtk.ResponseType.OK:
228             filename = dialog.get_filename()
229             if filename.endswith(".svg"):
230                 self.diagram.save_svg(filename[:-4])
231             elif filename.endswith(".png"):
232                 self.diagram.save_png(filename[:-4])
233
234         dialog.destroy()
235
236     def draw(self, darea, cr):
237         self.diagram.draw(self.model)
238
239         src_surface = self.diagram.surface
240         cr.set_source_surface(src_surface, 0, 0)
241         cr.paint()
242
243
244 class RadialSymmetryApp(Gtk.Application):
245
246     def __init__(self):
247         Gtk.Application.__init__(self,
248                                  application_id=APPLICATION_ID,
249                                  flags=Gio.ApplicationFlags.FLAGS_NONE)
250         self.connect("activate", self.activateCb)
251
252     def activateCb(self, app):
253         window = RadialSymmetryWindow()
254         app.add_window(window)
255         window.show_all()
256
257 if __name__ == "__main__":
258     app = RadialSymmetryApp()
259     exit_status = app.run(sys.argv)
260     sys.exit(exit_status)