Mercurial > hg > pub > prymula > scripts
comparison photocrop/src/photocrop.py @ 0:dcd610585610
INIT
author | prymula <prymula76@outlook.com> |
---|---|
date | Thu, 21 Sep 2023 22:32:14 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dcd610585610 |
---|---|
1 #!/usr/bin/env python3 | |
2 # Photo Crop - photo crop to size app to print in the most popular photo paper sizes | |
3 # author: Przemysław R. Pietraszczyk | |
4 # license: GPL v.2 | |
5 # date 21-01-2022 | |
6 # editor: Geany | |
7 | |
8 import sys | |
9 import cairo | |
10 import gi, os | |
11 import time | |
12 gi.require_version("Gtk", "3.0") | |
13 from gi.repository import Gtk, GdkPixbuf, Gdk | |
14 from gi.repository.GdkPixbuf import Pixbuf | |
15 | |
16 UI_INFO = """ | |
17 <ui> | |
18 <menubar name='MenuBar'> | |
19 <menu action='FileMenu'> | |
20 <menuitem action='FileLoad' /> | |
21 <menuitem action='FileSave' /> | |
22 <menuitem action='FilePrint' /> | |
23 <separator /> | |
24 <menuitem action='FileQuit' /> | |
25 </menu> | |
26 <menu action='EditMenu'> | |
27 <menuitem action='Rotate'/> | |
28 </menu> | |
29 <menu action='InfoMenu'> | |
30 <menuitem action='Help'/> | |
31 <menuitem action='About'/> | |
32 <separator /> | |
33 <menuitem action='License'/> | |
34 </menu> | |
35 </menubar> | |
36 | |
37 </ui> | |
38 """ | |
39 | |
40 """ | |
41 # szkielet z podmenu | |
42 <ui> | |
43 <menubar name='MenuBar'> | |
44 <menu action='FileMenu'> | |
45 <menu action='FileLoad'> | |
46 <menuitem action='LoadImg' /> | |
47 </menu> | |
48 <menuitem action='FilePrint' /> | |
49 <separator /> | |
50 <menuitem action='FileQuit' /> | |
51 </menu> | |
52 <menu action='InfoMenu'> | |
53 <menuitem action='Help'/> | |
54 <menuitem action='About'/> | |
55 </menu> | |
56 </menubar> | |
57 </ui> | |
58 """ | |
59 | |
60 grid = Gtk.Grid() | |
61 rotate = False | |
62 file_img_selected = "" | |
63 | |
64 def get_resource_path(rel_path): | |
65 dir_of_py_file = os.path.dirname(__file__) | |
66 rel_path_to_resource = os.path.join(dir_of_py_file, rel_path) | |
67 abs_path_to_resource = os.path.abspath(rel_path_to_resource) | |
68 return abs_path_to_resource | |
69 | |
70 class FileChooserIMGLoad(Gtk.Window): | |
71 def __init__(self): | |
72 Gtk.Window.__init__(self, title="Selection of graphic files") | |
73 global file_img_selected | |
74 | |
75 dialog = Gtk.FileChooserDialog(title="Selection of graphic files", parent=self, action=Gtk.FileChooserAction.OPEN) | |
76 dialog.add_buttons( | |
77 Gtk.STOCK_CANCEL, | |
78 Gtk.ResponseType.CANCEL, | |
79 Gtk.STOCK_OPEN, | |
80 Gtk.ResponseType.OK, | |
81 ) | |
82 | |
83 self.add_filters(dialog) | |
84 | |
85 response = dialog.run() | |
86 if response == Gtk.ResponseType.OK: | |
87 print("Open clicked") | |
88 print("File selected: " + dialog.get_filename()) | |
89 file_img_selected=dialog.get_filename() | |
90 elif response == Gtk.ResponseType.CANCEL: | |
91 print("Cancel clicked") | |
92 | |
93 dialog.destroy() | |
94 | |
95 def add_filters(self, dialog): | |
96 | |
97 filter_jpeg = Gtk.FileFilter() | |
98 filter_jpeg.set_name("Files JPEG") | |
99 filter_jpeg.add_mime_type("image/jpeg") | |
100 dialog.add_filter(filter_jpeg) | |
101 | |
102 filter_png = Gtk.FileFilter() | |
103 filter_png.set_name("Files PNG") | |
104 filter_png.add_mime_type("image/png") | |
105 dialog.add_filter(filter_png) | |
106 """ | |
107 filter_png = Gtk.FileFilter() | |
108 filter_png.set_name("Files TIFF") | |
109 filter_png.add_mime_type("image/tiff") | |
110 dialog.add_filter(filter_png) | |
111 """ | |
112 | |
113 class FileChooserIMGSave(Gtk.Window): | |
114 def __init__(self): | |
115 Gtk.Window.__init__(self, title="Save the cropped photo") | |
116 global file_img_selected | |
117 | |
118 dialog = Gtk.FileChooserDialog(title="Save the cropped photo", parent=self, action=Gtk.FileChooserAction.SAVE) | |
119 dialog.add_buttons( | |
120 Gtk.STOCK_CANCEL, | |
121 Gtk.ResponseType.CANCEL, | |
122 Gtk.STOCK_SAVE, | |
123 Gtk.ResponseType.OK, | |
124 ) | |
125 | |
126 self.add_filters(dialog) | |
127 | |
128 response = dialog.run() | |
129 if response == Gtk.ResponseType.OK: | |
130 print("Open clicked") | |
131 print("File selected: " + dialog.get_filename()) | |
132 file_img_selected=dialog.get_filename() | |
133 elif response == Gtk.ResponseType.CANCEL: | |
134 print("Cancel clicked") | |
135 | |
136 dialog.destroy() | |
137 | |
138 def add_filters(self, dialog): | |
139 | |
140 filter_jpeg = Gtk.FileFilter() | |
141 filter_jpeg.set_name("Files JPEG") | |
142 filter_jpeg.add_mime_type("image/jpeg") | |
143 dialog.add_filter(filter_jpeg) | |
144 | |
145 filter_png = Gtk.FileFilter() | |
146 filter_png.set_name("Files PNG") | |
147 filter_png.add_mime_type("image/png") | |
148 dialog.add_filter(filter_png) | |
149 """ | |
150 filter_png = Gtk.FileFilter() | |
151 filter_png.set_name("Files TIFF") | |
152 filter_png.add_mime_type("image/tiff") | |
153 dialog.add_filter(filter_png) | |
154 """ | |
155 | |
156 | |
157 class DialogWarning(Gtk.Dialog): | |
158 def __init__(self, parent): | |
159 Gtk.Dialog.__init__(self, title="Attention!", transient_for=parent, flags=0) | |
160 self.props.border_width = 20 | |
161 self.add_buttons( | |
162 Gtk.STOCK_OK, Gtk.ResponseType.OK | |
163 ) | |
164 self.set_default_size(150, 100) | |
165 label1 = Gtk.Label(label="The frame is outside the canvas!\n") | |
166 box = self.get_content_area() | |
167 box.add(label1) | |
168 self.show_all() | |
169 | |
170 class DialogCropWarning(Gtk.Dialog): | |
171 def __init__(self, parent): | |
172 Gtk.Dialog.__init__(self, title="Attention!", transient_for=parent, flags=0) | |
173 self.props.border_width = 20 | |
174 self.add_buttons( | |
175 Gtk.STOCK_OK, Gtk.ResponseType.OK | |
176 ) | |
177 self.set_default_size(150, 100) | |
178 label1 = Gtk.Label(label="Crop the photo first!\n") | |
179 box = self.get_content_area() | |
180 box.add(label1) | |
181 self.show_all() | |
182 | |
183 class DialogHelp(Gtk.Dialog): | |
184 def __init__(self, parent): | |
185 Gtk.Dialog.__init__(self, title="Help", transient_for=parent, flags=0) | |
186 self.props.border_width = 20 | |
187 self.add_buttons( | |
188 Gtk.STOCK_OK, Gtk.ResponseType.OK | |
189 ) | |
190 | |
191 self.set_default_size(150, 100) | |
192 label1 = Gtk.Label(label="An application for cropping photos to the most popular\nformats [13x18, 10x15, A4]\n\nScaling the size of the frame - mouse wheel with the CTRL key pressed\nMoving the frame - pressed LMB and moving the mouse\nin the desired direction.") | |
193 #label2 = Gtk.Label(lanel="") | |
194 box = self.get_content_area() | |
195 box.add(label1) | |
196 #box.add(label2) | |
197 self.show_all() | |
198 | |
199 class DialogAbout(Gtk.Dialog): | |
200 def __init__(self, parent): | |
201 Gtk.Dialog.__init__(self, title="About", transient_for=parent, flags=0) | |
202 self.props.border_width = 20 | |
203 self.add_buttons( | |
204 Gtk.STOCK_OK, Gtk.ResponseType.OK | |
205 ) | |
206 | |
207 self.set_default_size(150, 100) | |
208 | |
209 label = Gtk.Label(label="\tThe application is based on:") | |
210 box = self.get_content_area() | |
211 box.add(label) | |
212 | |
213 button = Gtk.LinkButton("https://python-gtk-3-tutorial.readthedocs.io/", label="https://python-gtk-3-tutorial.readthedocs.io/") | |
214 box.add(button) | |
215 | |
216 label2 = Gtk.Label(label="\n\tVersion: 0.230112-1~alpha\n\n\tPrzemysław R. Pietraszczyk\n\n\t\t January 2022\n\n\n") | |
217 box.add(label2) | |
218 | |
219 | |
220 button = Gtk.LinkButton("https://prymula.ct8.pl", label="Site") | |
221 box.add(button) | |
222 | |
223 self.show_all() | |
224 | |
225 class DialogLicense(Gtk.Dialog): | |
226 def __init__(self, parent): | |
227 Gtk.Dialog.__init__(self, title="License", transient_for=parent, flags=0) | |
228 self.props.border_width = 20 | |
229 self.add_buttons( | |
230 Gtk.STOCK_OK, Gtk.ResponseType.OK | |
231 ) | |
232 | |
233 self.set_default_size(150, 100) | |
234 | |
235 label = Gtk.Label(label="This program is distributed without any warranty. More information:\n") | |
236 | |
237 box = self.get_content_area() | |
238 box.add(label) | |
239 | |
240 button = Gtk.LinkButton("https://www.gnu.org/licenses/old-licenses/gpl-2.0.html", label="GNU General License version => 2") | |
241 box.add(button) | |
242 | |
243 self.show_all() | |
244 | |
245 class Brush(object): | |
246 | |
247 default_rgba_color = (0, 0, 0, 1) | |
248 | |
249 def __init__(self, width=None, rgba_color=None): | |
250 | |
251 if rgba_color is None: | |
252 rgba_color = self.default_rgba_color | |
253 | |
254 if width is None: | |
255 width = 3 | |
256 | |
257 self.__width = width | |
258 self.__rgba_color = rgba_color | |
259 self.__stroke = [] | |
260 self.__current_line = [] | |
261 | |
262 def _line_ended(self): | |
263 self.__stroke.append(self.__current_line.copy()) | |
264 self.__current_line = [] | |
265 | |
266 def _add_point(self, point): | |
267 self.__current_line.append(point) | |
268 | |
269 def _draw(self, cairo_context): | |
270 | |
271 cairo_context.set_source_rgba(*self.__rgba_color) | |
272 cairo_context.set_line_width(self.__width) | |
273 cairo_context.set_line_cap(cairo.LINE_CAP_ROUND) | |
274 | |
275 cairo_context.new_path() | |
276 for line in self.__stroke: | |
277 for x, y in line: | |
278 cairo_context.line_to(x, y) | |
279 cairo_context.new_sub_path() | |
280 | |
281 for x, y in self.__current_line: | |
282 cairo_context.line_to(x, y) | |
283 | |
284 cairo_context.stroke() | |
285 | |
286 | |
287 # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ # | |
288 # ~ Getters & Setters ~ # | |
289 # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ # | |
290 | |
291 def _get_width(self): | |
292 return self.__width | |
293 | |
294 def _set_width(self, width): | |
295 self.__width = width | |
296 | |
297 def _get_rgba_color(self): | |
298 return self.__rgba_color | |
299 | |
300 def _set_rgba_color(self, rgba_color): | |
301 self.__rgba_color = rgba_color | |
302 | |
303 def _get_stroke(self): | |
304 return self.__stroke | |
305 | |
306 def _get_current_line(self): | |
307 return self.__current_line | |
308 | |
309 class MyWindow(Gtk.Window): | |
310 | |
311 def __init__(self): | |
312 super().__init__() | |
313 | |
314 self.init_ui() | |
315 | |
316 def on_drawing_area_draw(self, drawable, cairo_context): | |
317 global rotate | |
318 | |
319 start = time.time() | |
320 self.brush = Brush() | |
321 | |
322 # DrawingArea size depends on Pixbuf size | |
323 #self.drawing_area.get_window().resize(self.displayed_pixbuf.get_width(), | |
324 # self.displayed_pixbuf .get_height()) | |
325 #self.drawing_area.set_size_request(self.displayed_pixbuf.get_width(), | |
326 # self.displayed_pixbuf.get_height()) | |
327 | |
328 | |
329 # (x, y) offsets | |
330 pixbuf_x = 0 #int(self.viewport.get_hadjustment().get_value()) | |
331 pixbuf_y = 0 # int(self.viewport.get_vadjustment().get_value()) | |
332 | |
333 # Width and height of the image's clip | |
334 width = cairo_context.get_target().get_width() | |
335 height = cairo_context.get_target().get_height() | |
336 | |
337 if width > 0 and height > 0: | |
338 | |
339 # Draw created area of the Sample's Pixbuf | |
340 Gdk.cairo_set_source_pixbuf(cairo_context, self.pixbuf_view, pixbuf_x, pixbuf_y) | |
341 | |
342 cairo_context.paint() | |
343 | |
344 if self.border_visible == True: | |
345 # Draw brush strokes | |
346 self.brush._add_point((self.border_x, self.border_y)) | |
347 self.brush._add_point((self.border_width, self.border_y)) | |
348 self.brush._add_point((self.border_width, self.border_y+self.border_height)) | |
349 self.brush._add_point((self.border_x, self.border_y+self.border_height)) | |
350 self.brush._add_point((self.border_x, self.border_y)) # nie działa ? | |
351 | |
352 self.brush._draw(cairo_context) | |
353 | |
354 end = time.time() | |
355 #print(f"Runtime of the program is {end - start}") | |
356 | |
357 | |
358 def set_ratio_border_size(self): | |
359 | |
360 self.border_x = 0 | |
361 self.border_y = 0 | |
362 | |
363 if self.format_size == "10x15": | |
364 if self.horizontal == True and self.vertical == False: | |
365 single = self.picture_view_height*0.5 | |
366 self.border_height = single | |
367 self.border_width = single*1.4 | |
368 #self.look = "horizontal" | |
369 #self.border_type = "horizontal" | |
370 elif self.horizontal == False and self.vertical == True: | |
371 single = self.picture_view_width*0.5 | |
372 self.border_width = single | |
373 self.border_height = single*1.4 | |
374 #self.look = "vertical" | |
375 #self.border_type = "vertical" | |
376 elif self.horizontal == False and self.vertical == False: | |
377 self.border_height = self.picture_view_height | |
378 self.border_width = self.picture_view_width | |
379 | |
380 elif self.format_size == "13x18": | |
381 if self.horizontal == True and self.vertical == False: | |
382 single = self.picture_view_height*0.5 | |
383 self.border_height = single | |
384 self.border_width = single*1.42 | |
385 elif self.horizontal == False and self.vertical == True: | |
386 single = self.picture_view_width*0.5 | |
387 self.border_width = single | |
388 self.border_height = single*1.42 | |
389 elif self.horizontal == False and self.vertical == False: | |
390 self.border_height = self.picture_view_height | |
391 self.border_width = self.picture_view_width | |
392 | |
393 elif self.format_size == "A4": | |
394 if self.horizontal == True and self.vertical == False: | |
395 single = self.picture_view_height*0.5 | |
396 self.border_height = single | |
397 self.border_width = single*1.4 | |
398 elif self.horizontal == False and self.vertical == True: | |
399 single = self.picture_view_width*0.5 | |
400 self.border_width = single | |
401 self.border_height = single*1.4 | |
402 elif self.horizontal == False and self.vertical == False: | |
403 self.border_height = self.picture_view_height | |
404 self.border_width = self.picture_view_width | |
405 | |
406 | |
407 | |
408 # ustaw proporcje dla obrazu w kontrolce | |
409 def set_ratio_picture_view(self): | |
410 self.picture_orig_width = float(self.pixbuf_orig.get_width()) | |
411 self.picture_orig_height = float(self.pixbuf_orig.get_height()) | |
412 #if self.format_size == "10x15": | |
413 #self.picture_orig_x = 0 | |
414 #self.picture_orig_y = 0 | |
415 | |
416 if self.picture_orig_width > self.picture_orig_height: | |
417 self.picture_view_width = 586.66 #600 | |
418 m = self.picture_orig_width / 586.66 #600 | |
419 self.picture_view_height = self.picture_orig_height/m | |
420 if self.picture_orig_height > self.picture_orig_width: | |
421 self.picture_view_height = 586.66 #600 | |
422 m = self.picture_orig_height / 586.66 #600 | |
423 self.picture_view_width = self.picture_orig_width/m | |
424 | |
425 # Zapamiętanie rozmiaru obrazka dla kontrolki | |
426 self.orig_ratio_width = self.picture_view_width | |
427 self.orig_ratio_height = self.picture_view_height | |
428 | |
429 | |
430 | |
431 # ustaw proporcje dla obrazu w kontrolce ppo rotacji | |
432 def set_ratio_picture_view_after_rotation(self): | |
433 | |
434 self.picture_tmp_width = self.border_width - self.border_x | |
435 self.picture_tmp_height = self.border_height - self.border_y | |
436 | |
437 if self.border_x != 0 : | |
438 bx = self.picture_orig_width / self.border_x | |
439 crop_x = self.picture_tmp_width / bx | |
440 else: | |
441 crop_x = 0 | |
442 | |
443 if self.border_y != 0: | |
444 by = self.picture_orig_height / self.border_y | |
445 crop_y = self.picture_tmp_height / by | |
446 else: | |
447 crop_y = 0 | |
448 | |
449 | |
450 self.picture_orig_x = crop_x | |
451 self.picture_orig_y = crop_y | |
452 """ | |
453 W zależności jaka była pierwotnie orientacja zdjęcia, po obrocie | |
454 jest ono odpowiedio przycinane | |
455 """ | |
456 print ("ORIG: "+self.look) | |
457 if self.look == "vertical": | |
458 if self.border_type == "horizontal": | |
459 bw = self.picture_orig_width / (self.border_width - self.border_x) | |
460 self.picture_tmp_width = self.picture_orig_width / bw - crop_x | |
461 # w tym wypadku 'y' bez odejmowania gdyż skróciło by to fotografie w pione | |
462 bh = self.picture_orig_height / self.border_height | |
463 self.picture_tmp_height = self.picture_orig_height / bh | |
464 | |
465 elif self.border_type == "vertical": | |
466 bw = self.picture_orig_width / (self.border_width - self.border_x) | |
467 self.picture_tmp_width = self.picture_orig_width / bw - crop_x | |
468 | |
469 bh = self.picture_orig_height / (self.border_height - self.border_y) | |
470 self.picture_tmp_height = self.picture_orig_height / bh | |
471 | |
472 elif self.look == "horizontal": | |
473 if self.border_type == "horizontal": | |
474 bw = self.picture_orig_width / (self.border_width - self.border_x) | |
475 self.picture_tmp_width = self.picture_orig_width / bw - crop_x | |
476 | |
477 bh = self.picture_orig_height / (self.border_height - self.border_y) | |
478 self.picture_tmp_height = self.picture_orig_height / bh | |
479 | |
480 elif self.border_type == "vertical": | |
481 bw = self.picture_orig_width / (self.border_width - self.border_x) | |
482 self.picture_tmp_width = self.picture_orig_width / bw - crop_x | |
483 | |
484 bh = self.picture_orig_height / (self.border_height - self.border_y) | |
485 self.picture_tmp_height = self.picture_orig_height / bh | |
486 | |
487 | |
488 if self.picture_tmp_width > self.picture_tmp_height: | |
489 self.picture_view_width = 586.66 | |
490 m = self.picture_tmp_width / 586.66 | |
491 self.picture_view_height = self.picture_tmp_height/m | |
492 if self.picture_tmp_height > self.picture_tmp_width: | |
493 self.picture_view_height = 586.66 | |
494 m = self.picture_tmp_height / 586.66 | |
495 self.picture_view_width = self.picture_tmp_width/m | |
496 | |
497 # Zapamiętanie rozmiaru obrazka dla kontrolki | |
498 self.orig_ratio_width = self.picture_view_width | |
499 self.orig_ratio_height = self.picture_view_height | |
500 | |
501 def on_menu_file_load_img_generic(self, widget): | |
502 global grid, file_img_selected | |
503 | |
504 filename=FileChooserIMGLoad() | |
505 | |
506 if len(file_img_selected) != 0: | |
507 | |
508 self.pixbuf_orig = GdkPixbuf.Pixbuf.new_from_file(filename=file_img_selected) | |
509 | |
510 self.set_ratio_picture_view() | |
511 self.pixbuf_view = self.pixbuf_orig.scale_simple(self.picture_view_width, self.picture_view_height, GdkPixbuf.InterpType.HYPER) | |
512 | |
513 if self.picture_view_width > self.picture_view_height: | |
514 self.horizontal = True | |
515 self.vertical = False | |
516 self.resize(582.66,413.34) | |
517 self.look = "horizontal" | |
518 self.border_type = "horizontal" | |
519 elif self.picture_view_height > self.picture_view_width: | |
520 self.vertical = True | |
521 self.horizontal = False | |
522 self.resize(413.34,582.66) | |
523 self.look = "vertical" | |
524 self.border_type = "vertical" | |
525 else: | |
526 self.horizontal = False | |
527 self.vertical = False | |
528 | |
529 | |
530 self.set_ratio_border_size() | |
531 | |
532 | |
533 | |
534 self.border_visible = True | |
535 | |
536 # nie rysujemy na orginale ale na kopii z okna | |
537 self.drawing_area.set_size_request(self.pixbuf_view.get_width(), self.pixbuf_view.get_height()) | |
538 self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK) | |
539 | |
540 self.show_all() | |
541 | |
542 def add_edit_menu_actions(self, action_group): | |
543 action_info_menu = Gtk.Action(name="EditMenu", label="Edit") | |
544 action_group.add_action(action_info_menu) | |
545 | |
546 action_new = Gtk.Action( | |
547 name="Rotate", | |
548 label="Frame rotation", | |
549 tooltip="Rotate border", | |
550 ) | |
551 action_new.connect("activate", self.on_menu_rotate) | |
552 action_group.add_action_with_accel(action_new, None) | |
553 | |
554 | |
555 def add_info_menu_actions(self, action_group): | |
556 action_info_menu = Gtk.Action(name="InfoMenu", label="Info") | |
557 action_group.add_action(action_info_menu) | |
558 | |
559 action_new = Gtk.Action( | |
560 name="Help", | |
561 label="Help", | |
562 tooltip="Help", | |
563 ) | |
564 action_new.connect("activate", self.on_menu_help) | |
565 action_group.add_action_with_accel(action_new, None) | |
566 | |
567 action_new = Gtk.Action( | |
568 name="About", | |
569 label="About", | |
570 tooltip="About", | |
571 ) | |
572 action_new.connect("activate", self.on_menu_about) | |
573 action_group.add_action_with_accel(action_new, None) | |
574 | |
575 action_new = Gtk.Action( | |
576 name="License", | |
577 label="License", | |
578 tooltip="License", | |
579 ) | |
580 action_new.connect("activate", self.on_menu_license) | |
581 action_group.add_action_with_accel(action_new, None) | |
582 | |
583 | |
584 | |
585 def add_file_menu_actions(self, action_group): | |
586 action_filemenu = Gtk.Action(name="FileMenu", label="File") | |
587 action_group.add_action(action_filemenu) | |
588 | |
589 """ | |
590 # sposób dodawania podmenu | |
591 action_fileloadmenu = Gtk.Action(name="FileLoad", stock_id=Gtk.STOCK_OPEN) | |
592 action_group.add_action(action_fileloadmenu) | |
593 | |
594 action_new = Gtk.Action( | |
595 name="LoadImg", | |
596 label="Wczytaj Obrazek", | |
597 tooltip="Wczytuje obrazek", | |
598 ) | |
599 action_new.connect("activate", self.on_menu_file_load_img_generic) | |
600 action_group.add_action_with_accel(action_new, None) | |
601 """ | |
602 | |
603 action_fileload = Gtk.Action(name="FileLoad", stock_id=Gtk.STOCK_OPEN) | |
604 action_fileload.connect("activate", self.on_menu_file_load_img_generic) | |
605 action_group.add_action(action_fileload) | |
606 | |
607 action_filesave = Gtk.Action(name="FileSave", stock_id=Gtk.STOCK_SAVE) | |
608 action_filesave.connect("activate", self.on_menu_file_save_img) | |
609 action_group.add_action(action_filesave) | |
610 | |
611 | |
612 action_print = Gtk.Action(name="FilePrint", stock_id=Gtk.STOCK_PRINT) | |
613 action_print.connect("activate", self.print_image) | |
614 action_group.add_action(action_print) | |
615 | |
616 | |
617 action_filequit = Gtk.Action(name="FileQuit", stock_id=Gtk.STOCK_QUIT) | |
618 action_filequit.connect("activate", self.on_menu_file_quit) | |
619 action_group.add_action(action_filequit) | |
620 | |
621 | |
622 | |
623 # tworzy menu bar | |
624 def create_ui_manager(self): | |
625 uimanager = Gtk.UIManager() | |
626 | |
627 # Throws exception if something went wrong | |
628 uimanager.add_ui_from_string(UI_INFO) | |
629 | |
630 # Add the accelerator group to the toplevel window | |
631 accelgroup = uimanager.get_accel_group() | |
632 self.add_accel_group(accelgroup) | |
633 return uimanager | |
634 | |
635 def on_menu_about(self, widget): | |
636 dialog = DialogAbout(self) | |
637 response = dialog.run() | |
638 | |
639 dialog.destroy() | |
640 | |
641 def on_menu_license(self, widget): | |
642 dialog = DialogLicense(self) | |
643 response = dialog.run() | |
644 | |
645 dialog.destroy() | |
646 | |
647 | |
648 def on_menu_help(self, widget): | |
649 dialog = DialogHelp(self) | |
650 response = dialog.run() | |
651 | |
652 dialog.destroy() | |
653 | |
654 def on_menu_rotate(self, widget): | |
655 global rotate | |
656 | |
657 | |
658 if self.border_type == "horizontal": | |
659 self.border_type = "vertical" | |
660 self.horizontal = False | |
661 self.vertical = True | |
662 | |
663 single = self.picture_view_width*0.3 | |
664 self.border_width = single | |
665 self.border_height = single*1.4 | |
666 self.border_x = 0 | |
667 self.border_y = 0 | |
668 | |
669 rotate = True | |
670 | |
671 elif self.border_type == "vertical": | |
672 self.border_type = "horizontal" | |
673 self.horizontal = True | |
674 self.vertical = False | |
675 | |
676 single = self.picture_view_height*0.3 | |
677 self.border_height = single | |
678 self.border_width = single*1.4 | |
679 self.border_x = 0 | |
680 self.border_y = 0 | |
681 | |
682 rotate = True | |
683 else: | |
684 pass | |
685 | |
686 print ("ASPECT: "+self.border_type) | |
687 print ("BORDER-WIDTH :"+str(self.border_width)) | |
688 print ("BORDER-HEIGHT:"+str(self.border_height)) | |
689 | |
690 self.drawing_area.queue_draw() | |
691 | |
692 def on_menu_file_save_img(self, widget): | |
693 global file_img_selected | |
694 | |
695 if self.pixbuf_tmp != None: | |
696 filename=FileChooserIMGSave() | |
697 if len(file_img_selected) != 0: | |
698 self.pixbuf_view.savev(file_img_selected, "jpeg", ["quality"], ["100"]) | |
699 else: | |
700 dialog = DialogCropWarning(self) | |
701 response = dialog.run() | |
702 | |
703 dialog.destroy() | |
704 | |
705 def on_menu_file_quit(self, widget): | |
706 Gtk.main_quit() | |
707 | |
708 def print_page(self, operation=None, context=None, page_nr=None): | |
709 | |
710 ctx = context.get_cairo_context() | |
711 | |
712 # make cairo ImageSurface from the png file | |
713 surface = cairo.ImageSurface.create_from_png('/tmp/photocrop.png') | |
714 #ctx.rectangle(50,50,100,100) | |
715 ctx.set_source_surface(surface) | |
716 ctx.paint () | |
717 os.remove("/tmp/photocrop.png"); | |
718 | |
719 | |
720 def print_image(self, widget): | |
721 | |
722 if self.pixbuf_tmp == None: | |
723 self.pixbuf_tmp = self.pixbuf_orig | |
724 | |
725 | |
726 # źle obraca | |
727 #if self.border_type == "horizontal": | |
728 # self.pixbuf_tmp.rotate_simple(GdkPixbuf.PixbufRotation.COUNTERCLOCKWISE) | |
729 # #pixbuf2.rotate_simple(GdkPixbuf.PixbufRotation.CLOCKWISE) | |
730 # self.border_type = "vertical" | |
731 | |
732 FACTOR_MM_TO_PIXEL = 2.834645669 | |
733 | |
734 | |
735 if self.format_size == "10x15": | |
736 #if self.horizontal == True and self.vertical == False: | |
737 if self.border_type == "horizontal": | |
738 page_width = 148 | |
739 page_height = 104.99 | |
740 | |
741 img_height =104.99 * FACTOR_MM_TO_PIXEL | |
742 img_width = 148 * FACTOR_MM_TO_PIXEL | |
743 #elif self.horizontal == False and self.vertical == True: | |
744 elif self.border_type == "vertical": | |
745 | |
746 page_width = 104.99 | |
747 page_height = 148 | |
748 | |
749 img_width =104.99 * FACTOR_MM_TO_PIXEL | |
750 img_height = 148 * FACTOR_MM_TO_PIXEL | |
751 | |
752 size = "10x15" | |
753 elif self.format_size == "13x18": | |
754 #if self.horizontal == True and self.vertical == False: | |
755 if self.border_type == "horizontal": | |
756 | |
757 page_width = 178 | |
758 page_height = 127 | |
759 | |
760 img_height = 127 * FACTOR_MM_TO_PIXEL | |
761 img_width = 178 * FACTOR_MM_TO_PIXEL | |
762 #elif self.horizontal == False and self.vertical == True: | |
763 elif self.border_type == "vertical": | |
764 | |
765 page_width = 127 | |
766 page_height = 178 | |
767 | |
768 img_width = 127 * FACTOR_MM_TO_PIXEL | |
769 img_height = 178 * FACTOR_MM_TO_PIXEL | |
770 size = "5x7" | |
771 elif self.format_size == "A4": | |
772 #if self.horizontal == True and self.vertical == False: | |
773 if self.border_type == "horizontal": | |
774 page_width = 297 | |
775 page_height = 207 | |
776 | |
777 img_height = 207 * FACTOR_MM_TO_PIXEL | |
778 img_width = 297 * FACTOR_MM_TO_PIXEL | |
779 #elif self.horizontal == False and self.vertical == True: | |
780 elif self.border_type == "vertical": | |
781 | |
782 page_width = 207 | |
783 page_height = 297 | |
784 | |
785 img_width = 207 * FACTOR_MM_TO_PIXEL | |
786 img_height = 297 * FACTOR_MM_TO_PIXEL | |
787 | |
788 size = "A4" | |
789 | |
790 dpi = 600 | |
791 | |
792 # z orginalnymi wielkościami nie chce drukowac | |
793 pixbuf2 = self.pixbuf_tmp.scale_simple(img_width, img_height, GdkPixbuf.InterpType.HYPER) | |
794 | |
795 pixbuf2.savev("/tmp/photocrop.png","png", ["quailty"], ["100"]) | |
796 | |
797 #ps = Gtk.PaperSize.new_custom(size, size, self.pixbuf_tmp.get_width(), self.pixbuf_tmp.get_height(), Gtk.Unit.POINTS) | |
798 ps = Gtk.PaperSize.new_custom(size, size, page_width, page_height, Gtk.Unit.MM) | |
799 | |
800 print_settings = Gtk.PrintSettings() | |
801 print_settings.set_resolution(dpi) | |
802 | |
803 page_setup = Gtk.PageSetup() | |
804 page_setup.set_paper_size(ps) | |
805 page_setup.set_bottom_margin(0.0, Gtk.Unit.MM) | |
806 page_setup.set_left_margin(0.0, Gtk.Unit.MM) | |
807 page_setup.set_right_margin(0.0, Gtk.Unit.MM) | |
808 page_setup.set_top_margin(0.0, Gtk.Unit.MM) | |
809 | |
810 #if self.border_type == "horizontal": | |
811 # page_setup.set_orientation(Gtk.PageOrientation.LANDSCAPE) | |
812 #elif self.border_type == "vertical": | |
813 # page_setup.set_orientation(Gtk.PageOrientation.PORTRAIT) | |
814 | |
815 | |
816 print_operation = Gtk.PrintOperation() | |
817 print_operation.set_unit(Gtk.Unit.POINTS) | |
818 print_operation.set_n_pages(1) | |
819 print_operation.set_default_page_setup(page_setup) | |
820 print_operation.set_print_settings(print_settings) | |
821 print_operation.connect("draw_page", self.print_page) | |
822 #print_operation.set_export_filename("example.pdf") | |
823 | |
824 result = print_operation.run(Gtk.PrintOperationAction.PRINT_DIALOG, None) # window zamisat None | |
825 | |
826 #result = print_operation.run(Gtk.PrintOperationAction.PREVIEW, None) | |
827 print(result) | |
828 | |
829 # przycinamy ! | |
830 def photo_crop(self, button): | |
831 | |
832 if self.border_x < 0: | |
833 dialog = DialogWarning(self) | |
834 response = dialog.run() | |
835 dialog.destroy() | |
836 return | |
837 if self.border_y < 0: | |
838 dialog = DialogWarning(self) | |
839 response = dialog.run() | |
840 dialog.destroy() | |
841 return | |
842 if self.border_width > self.picture_view_width: | |
843 dialog = DialogWarning(self) | |
844 response = dialog.run() | |
845 dialog.destroy() | |
846 return | |
847 # FIXME - w sumie to jest zastanawiające !? | |
848 if self.border_height + self.border_y > self.picture_view_height: | |
849 dialog = DialogWarning(self) | |
850 response = dialog.run() | |
851 dialog.destroy() | |
852 return | |
853 | |
854 | |
855 if self.border_x != 0 : | |
856 bx = self.picture_view_width / self.border_x | |
857 crop_x = self.picture_orig_width / bx | |
858 else: | |
859 crop_x = 0 | |
860 | |
861 if self.border_y != 0: | |
862 by = self.picture_view_height / self.border_y | |
863 crop_y = self.picture_orig_height / by | |
864 else: | |
865 crop_y = 0 | |
866 | |
867 bw = self.picture_view_width / self.border_width | |
868 crop_width = self.picture_orig_width / bw - crop_x | |
869 | |
870 bh = self.picture_view_height / self.border_height | |
871 crop_height = self.picture_orig_height / bh | |
872 | |
873 | |
874 # False - kanał Alpha | |
875 self.pixbuf_tmp = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, crop_width, crop_height) | |
876 | |
877 # zera na koncu to dest_x i dest_y | |
878 self.pixbuf_orig.copy_area(crop_x, crop_y, crop_width, crop_height, self.pixbuf_tmp, 0, 0) | |
879 | |
880 if rotate == False: | |
881 | |
882 self.picture_tmp_width = float(self.pixbuf_tmp.get_width()) | |
883 self.picture_tmp_height = float(self.pixbuf_tmp.get_height()) | |
884 | |
885 # tym razem przeliczamy z uwzględnieniem bufora tymczasowego | |
886 if self.picture_tmp_width > self.picture_tmp_height: | |
887 self.picture_view_width = 586.66 | |
888 m = self.picture_tmp_width / 586.66 | |
889 self.picture_view_height = self.picture_tmp_height/m | |
890 if self.picture_tmp_height > self.picture_tmp_width: | |
891 self.picture_view_height = 586.66 | |
892 m = self.picture_tmp_height / 586.66 | |
893 self.picture_view_width = self.picture_tmp_width/m | |
894 | |
895 | |
896 | |
897 else: | |
898 self.set_ratio_picture_view_after_rotation() | |
899 | |
900 self.drawing_area.set_size_request(self.picture_view_width, self.picture_view_height) | |
901 self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK) | |
902 | |
903 self.pixbuf_view = self.pixbuf_tmp.scale_simple(self.picture_view_width, self.picture_view_height, GdkPixbuf.InterpType.HYPER) | |
904 | |
905 self.picture_view_x = 0 | |
906 self.picture_view_y = 0 | |
907 | |
908 self.border_visible = False | |
909 | |
910 self.drawing_area.queue_draw() | |
911 | |
912 # przywracamy | |
913 def photo_restore(self, button): | |
914 | |
915 self.border_visible = True | |
916 | |
917 self.set_ratio_picture_view() | |
918 self.pixbuf_view = self.pixbuf_orig.scale_simple(self.orig_ratio_width, self.orig_ratio_height, GdkPixbuf.InterpType.BILINEAR) | |
919 | |
920 self.drawing_area.set_size_request(self.orig_ratio_width, self.orig_ratio_height) | |
921 self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK) | |
922 self.pixbuf_tmp = None # uniemożliwiamy zapis | |
923 self.show_all() | |
924 | |
925 self.drawing_area.queue_draw() | |
926 | |
927 | |
928 def on_format_combo_changed(self, combo): | |
929 tree_iter = combo.get_active_iter() | |
930 if tree_iter is not None: | |
931 model = combo.get_model() | |
932 self.format_size = model[tree_iter][0] | |
933 print("Selected: format=%s" % self.format_size) | |
934 | |
935 self.set_ratio_border_size() | |
936 | |
937 self.drawing_area.queue_draw() | |
938 | |
939 def on_scroll(self, widget, event): | |
940 """ handles on scroll event""" | |
941 # Handles zoom in / zoom out on Ctrl+mouse wheel | |
942 accel_mask = Gtk.accelerator_get_default_mod_mask() | |
943 if event.state & accel_mask == Gdk.ModifierType.CONTROL_MASK: | |
944 direction = event.get_scroll_deltas()[2] | |
945 | |
946 if direction > 0: | |
947 scrolling = "zoom_out" | |
948 else: | |
949 scrolling = "zoom_in" | |
950 | |
951 self.border_height += self.border_properties[self.format_size][self.border_type][scrolling]["height"] | |
952 self.border_width += self.border_properties[self.format_size][self.border_type][scrolling]["width"] | |
953 """ | |
954 # tu jest jakiś błąd | |
955 if self.border_width > self.picture_view_width: | |
956 self.border_width -= 1 | |
957 if self.border_height+self.border_y > self.picture_view_height: | |
958 self.border_height -= 1 | |
959 """ | |
960 self.drawing_area.queue_draw() | |
961 | |
962 def unclick_in_drawing_area (self, box, event): | |
963 self.button_press = False | |
964 print ("Przycisk myszki puszczony") | |
965 | |
966 | |
967 def onclick_in_drawing_area (self, box, event): | |
968 if event.button == 1: | |
969 self.button_press = True | |
970 print ("Lewy przyciski myszki naciśnięty") | |
971 | |
972 def on_mouse_move_in_drawing_area(self, box, event): | |
973 | |
974 | |
975 if self.button_press == True: | |
976 | |
977 if self.border_type == "vertical": | |
978 | |
979 #print ("VERTICAL %%") | |
980 | |
981 if self.last_x < event.x: | |
982 self.border_x += 1 | |
983 self.border_width += 1 | |
984 if event.x < self.last_x: | |
985 self.border_x -= 1 | |
986 self.border_width -= 1 | |
987 if self.last_y < event.y: | |
988 self.border_y += 1 | |
989 self.border_height += 0.0 | |
990 if event.y < self.last_y: | |
991 self.border_y -= 1 | |
992 self.border_height -= 0.0 | |
993 | |
994 elif self.border_type == "horizontal": | |
995 #print ("Horizontal %%") | |
996 if self.last_x < event.x: | |
997 self.border_x += 1 | |
998 self.border_width += 1 | |
999 if event.x < self.last_x: | |
1000 self.border_x -= 1. | |
1001 self.border_width -= 1 | |
1002 if self.last_y < event.y: | |
1003 self.border_y += 1 | |
1004 self.border_height += 0.0 | |
1005 if event.y < self.last_y: | |
1006 self.border_y -= 1 | |
1007 self.border_height -= 0.0 | |
1008 | |
1009 self.last_y = event.y | |
1010 self.last_x = event.x | |
1011 """ | |
1012 # jeśli będzie się napierać na skraj krawędzi wówczas powiększa ramkę | |
1013 if self.border_x < 0: | |
1014 self.border_x += 1 | |
1015 self.border_width += 1 | |
1016 if self.border_y < 0: | |
1017 self.border_y += 1 | |
1018 self.border_height +=1 | |
1019 # tu jest jakiś błąd | |
1020 | |
1021 # powoduje błędne zachowanie ramki | |
1022 if self.border_type == "vertical": | |
1023 if self.border_width > self.picture_view_width: | |
1024 self.border_width -= 1 | |
1025 self.border_x -= 1 | |
1026 if self.border_height+self.border_y > self.picture_view_height: | |
1027 self.border_height -= 1 | |
1028 self.border_y -= 1 | |
1029 elif self.border_type == "horizontal": | |
1030 if self.border_height+self.border_y> self.picture_view_width: | |
1031 self.border_width -= 1 | |
1032 self.border_x -= 1 | |
1033 if self.border_width > self.picture_view_height: | |
1034 self.border_height -= 1 | |
1035 self.border_y -= 1 | |
1036 """ | |
1037 | |
1038 self.drawing_area.queue_draw() | |
1039 | |
1040 def init_ui(self): | |
1041 # JPG akceptuje jedynie z GIMPa | |
1042 self.border_properties = { "10x15" : { "horizontal" : { "zoom_out" : {"width" : -1.48, "height" : -0.92}, "zoom_in" : {"width" : 1.48, "height" : 0.92}}, | |
1043 "vertical" : { "zoom_out": { "width" : -0.92, "height" : -1.48}, "zoom_in": { "width" : 0.92, "height" : 1.48}}, | |
1044 "square" : { "zoom_out" : { "width" : -1, "height" : -1}, "zoom_out" : { "width" : 1, "height" : 1}}}, | |
1045 | |
1046 "13x18" : { "horizontal" : { "zoom_out" : {"width" : -1.82, "height" : -1.30}, "zoom_in" : {"width" : 1.82, "height" : 1.30}}, | |
1047 "vertical" : { "zoom_out": { "width" : -1.30, "height" : -1.78}, "zoom_in": { "width" : 1.30, "height" : 1.78}}, | |
1048 "square" : { "zoom_out" : { "width" : -1, "height" : -1}, "zoom_out" : { "width" : 1, "height" : 1}}}, | |
1049 | |
1050 "A4" : { "horizontal" : { "zoom_out" : {"width" : -2.97, "height" : -1.84}, "zoom_in" : {"width" : 2.97, "height" : 1.84}}, | |
1051 "vertical" : { "zoom_out": { "width" : -1.84, "height" : -2.97}, "zoom_in": { "width" : 1.84, "height" : 2.97}}, | |
1052 "square" : { "zoom_out" : { "width" : -1, "height" : -1}, "zoom_out" : { "width" : 1, "height" : 1}}}} | |
1053 | |
1054 | |
1055 #self.props.border_width = 20 | |
1056 self.add(grid) | |
1057 self.pixbuf_tmp = None | |
1058 grid.set_row_spacing(10) | |
1059 grid.set_column_spacing(10) | |
1060 grid.set_column_homogeneous(True) # rozszerza kontrolki na resztę okna | |
1061 | |
1062 action_group = Gtk.ActionGroup(name="my_actions") | |
1063 | |
1064 self.add_file_menu_actions(action_group) | |
1065 self.add_edit_menu_actions(action_group) | |
1066 self.add_info_menu_actions(action_group) | |
1067 | |
1068 uimanager = self.create_ui_manager() | |
1069 uimanager.insert_action_group(action_group) | |
1070 | |
1071 menubar = uimanager.get_widget("/MenuBar") | |
1072 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
1073 box.pack_start(menubar, False, False, 0) | |
1074 | |
1075 grid.attach(box, 0, 0, 3, 1) | |
1076 | |
1077 | |
1078 file_img_selected=get_resource_path("/usr/share/photocrop/IMG_6854.JPG") #img/blank.png | |
1079 self.pixbuf_orig = GdkPixbuf.Pixbuf.new_from_file(filename=file_img_selected) | |
1080 | |
1081 self.format_size = "10x15" | |
1082 self.set_ratio_picture_view() | |
1083 self.pixbuf_view = self.pixbuf_orig.scale_simple(self.picture_view_width, self.picture_view_height, GdkPixbuf.InterpType.HYPER) | |
1084 | |
1085 self.border_visible = True | |
1086 | |
1087 if self.picture_view_width > self.picture_view_height: | |
1088 self.horizontal = True | |
1089 self.vertical = False | |
1090 self.look = "horizontal" | |
1091 self.border_type = "horizontal" | |
1092 elif self.picture_view_height > self.picture_view_width: | |
1093 self.vertical = True | |
1094 self.horizontal = False | |
1095 self.look = "vertical" | |
1096 self.border_type = "vertical" | |
1097 else: | |
1098 self.horizontal = False | |
1099 self.vertical = False | |
1100 self.look = "square" | |
1101 | |
1102 self.drawing_area = Gtk.DrawingArea() | |
1103 | |
1104 self.drawing_area.set_size_request(self.pixbuf_view.get_width(), self.pixbuf_view.get_height()) | |
1105 self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK) | |
1106 | |
1107 self.drawing_area.connect("draw", self.on_drawing_area_draw) | |
1108 | |
1109 frame = Gtk.Frame() | |
1110 event_box = Gtk.EventBox () | |
1111 self.last_x = 1 | |
1112 self.last_y = 1 | |
1113 self.border_x = 0 | |
1114 self.border_y = 0 | |
1115 self.button_press = False | |
1116 self.pixbuf_tmp = None | |
1117 event_box.connect ('button-press-event', self.onclick_in_drawing_area) | |
1118 event_box.connect ('button-release-event', self.unclick_in_drawing_area) | |
1119 event_box.connect("motion-notify-event", self.on_mouse_move_in_drawing_area) | |
1120 event_box.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK) | |
1121 | |
1122 event_box.add(self.drawing_area) | |
1123 frame.add(event_box) | |
1124 grid.attach(frame,0,1,3,1) | |
1125 | |
1126 button1 = Gtk.Button.new_with_label("Crop") | |
1127 button1.connect("clicked", self.photo_crop) | |
1128 grid.attach(button1,0,4,1,1) | |
1129 | |
1130 button2 = Gtk.Button.new_with_label("Restore") | |
1131 button2.connect("clicked", self.photo_restore) | |
1132 grid.attach(button2,1,4,1,1) | |
1133 | |
1134 format_store = Gtk.ListStore(str) | |
1135 format_photo = [ | |
1136 "10x15", | |
1137 "13x18", | |
1138 "A4", | |
1139 ] | |
1140 for fp in format_photo: | |
1141 format_store.append([fp]) | |
1142 | |
1143 format_combo = Gtk.ComboBox.new_with_model(format_store) | |
1144 format_combo.connect("changed", self.on_format_combo_changed) | |
1145 renderer_text = Gtk.CellRendererText() | |
1146 format_combo.pack_start(renderer_text, True) | |
1147 format_combo.add_attribute(renderer_text, "text", 0) | |
1148 format_combo.set_active(0) | |
1149 grid.attach(format_combo,2,4,1,1) | |
1150 | |
1151 self.drawing_area.connect('scroll-event', self.on_scroll) | |
1152 | |
1153 self.set_border_width(10) | |
1154 self.set_title("Photo Crop (alpha)") | |
1155 #self.set_default_size(700, 600) | |
1156 #self.resize(700, 600) | |
1157 self.set_resizable(False) | |
1158 self.connect("destroy", Gtk.main_quit) | |
1159 | |
1160 | |
1161 | |
1162 win = MyWindow() | |
1163 win.show_all() | |
1164 Gtk.main() |