From 9462908d47e8e04520cf550c9714de7d274d7533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Fri, 26 Aug 2016 01:50:01 -0300 Subject: [PATCH 1/9] fixes #72 --- pygubu/widgets/pathchooserinput.py | 2 ++ tests/test_pathchooserinput.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/pygubu/widgets/pathchooserinput.py b/pygubu/widgets/pathchooserinput.py index 23c85441..2f9dfe2e 100644 --- a/pygubu/widgets/pathchooserinput.py +++ b/pygubu/widgets/pathchooserinput.py @@ -75,6 +75,8 @@ def cget(self, key): if key == option: return self.entry.cget(option) return ttk.Frame.cget(self, key) + + __getitem__ = cget def _is_changed(self): # print(repr(self._oldvalue), ':', repr(self.entry.get())) diff --git a/tests/test_pathchooserinput.py b/tests/test_pathchooserinput.py index 1c2d0378..9b1fc1e0 100644 --- a/tests/test_pathchooserinput.py +++ b/tests/test_pathchooserinput.py @@ -60,4 +60,8 @@ def test_path(self): self.assertEqual('/home/user', path) self.widget.destroy() + def test_path_dictionary_like(self): + path = str(self.widget['path']) + self.assertEqual('/home/user', path) + self.widget.destroy() From eb34b5c2776bb084053e7678b8c51c6ca45dc243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Mon, 7 Nov 2016 23:02:14 -0300 Subject: [PATCH 2/9] Added nuitka compile example. --- examples/nuitka/compile.bat | 1 + examples/nuitka/myapp.py | 38 +++++++ examples/nuitka/myapp.ui | 172 ++++++++++++++++++++++++++++++++ examples/nuitka/nuitkahelper.py | 12 +++ 4 files changed, 223 insertions(+) create mode 100644 examples/nuitka/compile.bat create mode 100644 examples/nuitka/myapp.py create mode 100644 examples/nuitka/myapp.ui create mode 100644 examples/nuitka/nuitkahelper.py diff --git a/examples/nuitka/compile.bat b/examples/nuitka/compile.bat new file mode 100644 index 00000000..c31af9c8 --- /dev/null +++ b/examples/nuitka/compile.bat @@ -0,0 +1 @@ +c:\Python344\Scripts\nuitka --recurse-directory=c:\Python344\Lib\dist-packages\pygubu --show-progress --show-modules --standalone myapp.py diff --git a/examples/nuitka/myapp.py b/examples/nuitka/myapp.py new file mode 100644 index 00000000..ef8f3b20 --- /dev/null +++ b/examples/nuitka/myapp.py @@ -0,0 +1,38 @@ +#file: myapp.py +import tkinter as tk +import tkinter.messagebox +import tkinter.ttk as ttk +import pygubu + +#Help nuitka compiler to include specific modules +import nuitkahelper + + +class MyApplication: + + def __init__(self): + self.builder = b = pygubu.Builder() + b.add_from_file('myapp.ui') + self.mainwindow = b.get_object('mainwindow') + b.connect_callbacks(self) + + def on_action1_clicked(self): + tk.messagebox.showinfo('Myapp', 'Action 1 clicked') + + def on_action2_clicked(self): + tk.messagebox.showinfo('Myapp', 'Action 2 clicked') + + def on_action3_clicked(self): + tk.messagebox.showinfo('Myapp', 'Action 3 clicked') + + def on_appmenu_action(self, command_id): + tk.messagebox.showinfo('Myapp', 'Byby!') + self.mainwindow.quit() + + def run(self): + self.mainwindow.mainloop(); + + +if __name__ == '__main__': + app = MyApplication() + app.run(); diff --git a/examples/nuitka/myapp.ui b/examples/nuitka/myapp.ui new file mode 100644 index 00000000..45ecf1d9 --- /dev/null +++ b/examples/nuitka/myapp.ui @@ -0,0 +1,172 @@ + + + + 200 + both + Myapp + 200 + + + 200 + 2 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 200 + 200 + + 0 + True + 0 + ew + + + + on_action1_clicked + Toolbutton + Action1 + + 0 + True + 0 + + + + + + on_action2_clicked + Toolbutton + Action2 + + 1 + True + 0 + + + + + + on_action3_clicked + Toolbutton + Action3 + + 2 + True + 0 + + + + + + AppMenu + + 3 + True + 0 + + + + + + on_appmenu_action + true + Exit + + + + + + + + + + + 200 + 200 + + 0 + True + 1 + nsew + + + 1 + + + + + 1 + + + + + + both + + 0 + True + 0 + nsew + + + + #ffffff + 0 + + 0 + True + 0 + nsew + + + + + + + + + + 0 + 200 + 2 + ridge + 200 + + 0 + True + 2 + ew + + + + status bar here + + 0 + True + 0 + ew + + + + + + + + + diff --git a/examples/nuitka/nuitkahelper.py b/examples/nuitka/nuitkahelper.py new file mode 100644 index 00000000..5225f57e --- /dev/null +++ b/examples/nuitka/nuitkahelper.py @@ -0,0 +1,12 @@ +#file: nuitkahelper + +#Help nuitka compiler to include specific modules +import pygubu.builder.tkstdwidgets +import pygubu.builder.ttkstdwidgets +import pygubu.builder.widgets.dialog +import pygubu.builder.widgets.editabletreeview +import pygubu.builder.widgets.scrollbarhelper +import pygubu.builder.widgets.scrolledframe +import pygubu.builder.widgets.tkscrollbarhelper +import pygubu.builder.widgets.tkscrolledframe +import pygubu.builder.widgets.pathchooserinput From 7502fcd057a315f01c7984ab8f1c573e237128ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Sat, 19 Nov 2016 23:13:20 -0300 Subject: [PATCH 3/9] Add example using pygubu and pyinstaller. closes #78 Updated other examples. --- examples/cx_freeze/button_cb.py | 52 ---- examples/cx_freeze/button_cb.ui | 52 ---- examples/cx_freeze/imgs/MenuIcon4.gif | Bin 0 -> 72 bytes examples/cx_freeze/imgs/ps_circle.gif | Bin 0 -> 90 bytes examples/cx_freeze/imgs/ps_cross.gif | Bin 0 -> 104 bytes examples/cx_freeze/imgs/ps_square.gif | Bin 0 -> 86 bytes examples/cx_freeze/imgs/ps_triangle.gif | Bin 0 -> 131 bytes examples/cx_freeze/myapp.py | 194 ++++++++++++ examples/cx_freeze/myapp.ui | 344 ++++++++++++++++++++++ examples/cx_freeze/setup.py | 11 +- examples/nuitka/copydatafiles.bat | 3 + examples/nuitka/imgs/MenuIcon4.gif | Bin 0 -> 72 bytes examples/nuitka/imgs/ps_circle.gif | Bin 0 -> 90 bytes examples/nuitka/imgs/ps_cross.gif | Bin 0 -> 104 bytes examples/nuitka/imgs/ps_square.gif | Bin 0 -> 86 bytes examples/nuitka/imgs/ps_triangle.gif | Bin 0 -> 131 bytes examples/nuitka/myapp.py | 173 ++++++++++- examples/nuitka/myapp.ui | 236 +++++++++++++-- examples/py2exe/button_cb.py | 45 --- examples/py2exe/button_cb.ui | 52 ---- examples/py2exe/imgs/MenuIcon4.gif | Bin 0 -> 72 bytes examples/py2exe/imgs/ps_circle.gif | Bin 0 -> 90 bytes examples/py2exe/imgs/ps_cross.gif | Bin 0 -> 104 bytes examples/py2exe/imgs/ps_square.gif | Bin 0 -> 86 bytes examples/py2exe/imgs/ps_triangle.gif | Bin 0 -> 131 bytes examples/py2exe/myapp.py | 195 ++++++++++++ examples/py2exe/myapp.ui | 344 ++++++++++++++++++++++ examples/py2exe/setup.py | 13 +- examples/pyinstaller/imgs/MenuIcon4.gif | Bin 0 -> 72 bytes examples/pyinstaller/imgs/ps_circle.gif | Bin 0 -> 90 bytes examples/pyinstaller/imgs/ps_cross.gif | Bin 0 -> 104 bytes examples/pyinstaller/imgs/ps_square.gif | Bin 0 -> 86 bytes examples/pyinstaller/imgs/ps_triangle.gif | Bin 0 -> 131 bytes examples/pyinstaller/myapp.py | 195 ++++++++++++ examples/pyinstaller/myapp.spec | 50 ++++ examples/pyinstaller/myapp.ui | 344 ++++++++++++++++++++++ 36 files changed, 2051 insertions(+), 252 deletions(-) delete mode 100644 examples/cx_freeze/button_cb.py delete mode 100644 examples/cx_freeze/button_cb.ui create mode 100644 examples/cx_freeze/imgs/MenuIcon4.gif create mode 100644 examples/cx_freeze/imgs/ps_circle.gif create mode 100644 examples/cx_freeze/imgs/ps_cross.gif create mode 100644 examples/cx_freeze/imgs/ps_square.gif create mode 100644 examples/cx_freeze/imgs/ps_triangle.gif create mode 100644 examples/cx_freeze/myapp.py create mode 100644 examples/cx_freeze/myapp.ui create mode 100644 examples/nuitka/copydatafiles.bat create mode 100644 examples/nuitka/imgs/MenuIcon4.gif create mode 100644 examples/nuitka/imgs/ps_circle.gif create mode 100644 examples/nuitka/imgs/ps_cross.gif create mode 100644 examples/nuitka/imgs/ps_square.gif create mode 100644 examples/nuitka/imgs/ps_triangle.gif delete mode 100644 examples/py2exe/button_cb.py delete mode 100644 examples/py2exe/button_cb.ui create mode 100644 examples/py2exe/imgs/MenuIcon4.gif create mode 100644 examples/py2exe/imgs/ps_circle.gif create mode 100644 examples/py2exe/imgs/ps_cross.gif create mode 100644 examples/py2exe/imgs/ps_square.gif create mode 100644 examples/py2exe/imgs/ps_triangle.gif create mode 100644 examples/py2exe/myapp.py create mode 100644 examples/py2exe/myapp.ui create mode 100644 examples/pyinstaller/imgs/MenuIcon4.gif create mode 100644 examples/pyinstaller/imgs/ps_circle.gif create mode 100644 examples/pyinstaller/imgs/ps_cross.gif create mode 100644 examples/pyinstaller/imgs/ps_square.gif create mode 100644 examples/pyinstaller/imgs/ps_triangle.gif create mode 100644 examples/pyinstaller/myapp.py create mode 100644 examples/pyinstaller/myapp.spec create mode 100644 examples/pyinstaller/myapp.ui diff --git a/examples/cx_freeze/button_cb.py b/examples/cx_freeze/button_cb.py deleted file mode 100644 index a4d7a28a..00000000 --- a/examples/cx_freeze/button_cb.py +++ /dev/null @@ -1,52 +0,0 @@ -# button_cb.py -import sys -import os - -try: - import tkinter as tk - from tkinter import messagebox -except: - import Tkinter as tk - import tkMessageBox as messagebox - -import pygubu - - -def find_data_file(filename): - if getattr(sys, 'frozen', False): - # The application is frozen - datadir = os.path.dirname(sys.executable) - else: - # The application is not frozen - # Change this bit to match where you store your data files: - datadir = os.path.dirname(__file__) - - return os.path.join(datadir, filename) - - -class Myapp: - def __init__(self, master): - self.builder = builder = pygubu.Builder() - fpath = find_data_file("button_cb.ui") - builder.add_from_file(fpath) - - mainwindow = builder.get_object('mainwindow', master) - - builder.connect_callbacks(self) - - callbacks = { - 'on_button2_clicked': self.on_button2_clicked - } - - builder.connect_callbacks(callbacks) - - def on_my_button_clicked(self): - messagebox.showinfo('From callback', 'My button was clicked !!') - - def on_button2_clicked(self): - messagebox.showinfo('From callback', 'Button 2 was clicked !!') - -if __name__ == '__main__': - root = tk.Tk() - app = Myapp(root) - root.mainloop() diff --git a/examples/cx_freeze/button_cb.ui b/examples/cx_freeze/button_cb.ui deleted file mode 100644 index 07be6081..00000000 --- a/examples/cx_freeze/button_cb.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - -250 -10 -250 - -0 -0 - - -10 - - -10 - - - - - -e -Button command test ? ouch!! - -0 -ew -1 -0 - - - - - -on_my_button_clicked -Click me - -0 -1 - - - - - -on_button2_clicked -Button 2 - -1 -1 - - - - - diff --git a/examples/cx_freeze/imgs/MenuIcon4.gif b/examples/cx_freeze/imgs/MenuIcon4.gif new file mode 100644 index 0000000000000000000000000000000000000000..9824a61653a16159e7f7d35bedad7e6e9dbe0864 GIT binary patch literal 72 zcmZ?wbhEHb6k*_J_`m=Kia%Kx85kHDbU=KN3y6_g9i5BZD;nRtOd2 literal 0 HcmV?d00001 diff --git a/examples/cx_freeze/imgs/ps_circle.gif b/examples/cx_freeze/imgs/ps_circle.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac199fe8fd52240365c225caef51ea5f8d6a4f10 GIT binary patch literal 90 zcmZ?wbhEHb6k_0K_{hxgKQ;A#TH62gbOr_n#h)yU3=GT+IzTo9NS=X7dyf6W6D$7T t>SQ~0m1UmewRa713N822=S3^Uy}dPIrS$gpwE9J+PFGHzlH_8r1^^GJAh7@d literal 0 HcmV?d00001 diff --git a/examples/cx_freeze/imgs/ps_cross.gif b/examples/cx_freeze/imgs/ps_cross.gif new file mode 100644 index 0000000000000000000000000000000000000000..586ab4428170f5364ddb34d466cbfe8cd9b2556a GIT binary patch literal 104 zcmZ?wbhEHb6k_0K_{7Fgz43YVrWZ9EU({@RQM(yPzGMIc#h)yU3=C`xIv^fMEd#S| z#;!ZIhjkewd-{^1XHGk2TDssz<7wALUK=*u(ympiYTHz#y>UUaJX6`*d pC*z8XwYIBu_MO@w4Y_%di;_>SP2%39{^#NZrW@ZUEn{V{1_0AXA1?p^ literal 0 HcmV?d00001 diff --git a/examples/cx_freeze/imgs/ps_triangle.gif b/examples/cx_freeze/imgs/ps_triangle.gif new file mode 100644 index 0000000000000000000000000000000000000000..b965d43a29bb48421a203be1a8e815b5b06bf2eb GIT binary patch literal 131 zcmZ?wbhEHb6k_0K_{_~<|7d~z;{^_n<~uxG;P`N%!=nX`j}|yQ29gUM9xrr!ypRDE zDE?$&WMJTC&;bd9%wS+KC^+f4dadH!*kAkRe)nByp_e2$CoN!u{>~!9$c(TV*Qa=@ V+J#&Qlf0F+bcxZnoPA0R)&TPLF8Tlf literal 0 HcmV?d00001 diff --git a/examples/cx_freeze/myapp.py b/examples/cx_freeze/myapp.py new file mode 100644 index 00000000..ba495de8 --- /dev/null +++ b/examples/cx_freeze/myapp.py @@ -0,0 +1,194 @@ +#file: myapp.py +import sys +import os +import random +import math + +try: + import Tkinter as tk + import ttk + import tkMessageBox as tkmb + tk.messagebox = tkmb +except: + import tkinter as tk + import tkinter.messagebox + import tkinter.ttk as ttk + +import pygubu + + +DATA_DIR = os.path.abspath(os.path.dirname(__file__)) +if getattr(sys, 'frozen', False): + DATA_DIR = os.path.abspath(os.path.dirname(sys.executable)) + +DEG2RAD = 4 * math.atan(1) * 2 / 360 + + +class MyApplication: + + def __init__(self): + self.about_dialog = None + + self.builder = b = pygubu.Builder() + b.add_from_file(os.path.join(DATA_DIR, 'myapp.ui')) + b.add_resource_path(os.path.join(DATA_DIR, 'imgs')) + + self.mainwindow = b.get_object('mainwindow') + self.mainmenu = b.get_object('mainmenu', self.mainwindow) + self.btn_menu = b.get_object('btn_menu') + #self.mainwindow['menu'] = menu + self.canvas = b.get_object('main_canvas') + + # Connect to Delete event + self.mainwindow.protocol("WM_DELETE_WINDOW", self.quit) + + b.connect_callbacks(self) + + def on_mainmenu_action(self, option_id=None): + if option_id == 'mm_clear': + self.canvas.delete('all') + if option_id == 'mm_about': + self.show_about_dialog() + if option_id == 'mm_quit': + self.mainwindow.quit() + + def btn_menu_clicked(self): + # this is ugly but I don't want to use menubutton :( + x, y = (self.btn_menu.winfo_rootx(), self.btn_menu.winfo_rooty()) + y = y + self.btn_menu.winfo_height() + try: + self.mainmenu.tk_popup(x, y, '') + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.mainmenu.grab_release() + + def show_about_dialog(self): + if self.about_dialog is None: + dialog = self.builder.get_object('dlg_about', self.mainwindow) + self.about_dialog = dialog + + def dialog_btnclose_clicked(): + dialog.close() + + btnclose = self.builder.get_object('about_btnclose') + btnclose['command'] = dialog_btnclose_clicked + + dialog.run() + else: + self.about_dialog.show() + + def btn_square_clicked(self): + self._draw_figure('square') + + def btn_cross_clicked(self): + self._draw_figure('cross') + + def btn_circle_clicked(self): + self._draw_figure('circle') + + def btn_triangle_clicked(self): + self._draw_figure('triangle') + + def quit(self, event=None): + self.mainwindow.quit() + + def run(self): + self.mainwindow.mainloop() + + def _draw_figure(self, figure): + canvas = self.canvas + max_iterations = random.randint(3, 10) + + for i in range(0, max_iterations): + canvasw = canvas.winfo_width() + canvash = canvas.winfo_height() + borderw = random.randint(1, 5) + max_width = int(canvas.winfo_width()*0.25) + w = random.randint(20, max_width) + x = random.randint(0, canvasw) - int(max_width * 0.5) + y = random.randint(0, canvash) - int(max_width * 0.5) + start = random.randint(0, 180) + + if figure == 'circle': + canvas.create_oval(x, y, x+w, y+w, outline='#FF6666', width=borderw) + if figure == 'square': + self.create_regpoly(x, y, x+w, y+w, + sides=4, start=start, + outline='#ED9DE9', width=borderw, fill='') + if figure == 'triangle': + self.create_regpoly(x, y, x+w, y+w, + sides=3, start=start, + outline='#40E2A0', width=borderw, fill='') + if figure == 'cross': + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start, + outline='#80B3E7', width=borderw, fill='') + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start+90, + outline='#80B3E7', width=borderw, fill='') + + def create_regpoly(self, x0, y0, x1, y1, sides=0, start=90, extent=360, **kw): + """Create a regular polygon""" + coords = self.__regpoly_coords(x0, y0, x1, y1, sides, start, extent) + return self.canvas.create_polygon(*coords, **kw) + + def __regpoly_coords(self, x0, y0, x1, y1, sides, start, extent): + """Create the coordinates of the regular polygon specified""" + + coords = [] + if extent == 0: + return coords + + xm = (x0 + x1) / 2. + ym = (y0 + y1) / 2. + rx = xm - x0 + ry = ym - y0 + + n = sides + if n == 0: # 0 sides => circle + n = round((rx + ry) * .5) + if n < 2: + n = 4 + + # Extent can be negative + dirv = 1 if extent > 0 else -1 + if abs(extent) > 360: + extent = dirv * abs(extent) % 360 + + step = dirv * 360 / n + numsteps = 1 + extent / float(step) + numsteps_int = int(numsteps) + + i = 0 + while i < numsteps_int: + rad = (start - i * step) * DEG2RAD + x = rx * math.cos(rad) + y = ry * math.sin(rad) + coords.append((xm+x, ym-y)) + i += 1 + + # Figure out where last segment should end + if numsteps != numsteps_int: + # Vecter V1 is last drawn vertext (x,y) from above + # Vector V2 is the edge of the polygon + rad2 = (start - numsteps_int * step) * DEG2RAD + x2 = rx * math.cos(rad2) - x + y2 = ry * math.sin(rad2) - y + + # Vector V3 is unit vector in direction we end at + rad3 = (start - extent) * DEG2RAD + x3 = math.cos(rad3) + y3 = math.sin(rad3) + + # Find where V3 crosses V1+V2 => find j s.t. V1 + kV2 = jV3 + j = (x*y2 - x2*y) / (x3*y2 - x2*y3) + + coords.append((xm + j * x3, ym - j * y3)) + + return coords + + +if __name__ == '__main__': + app = MyApplication() + app.run(); + diff --git a/examples/cx_freeze/myapp.ui b/examples/cx_freeze/myapp.ui new file mode 100644 index 00000000..aebba68a --- /dev/null +++ b/examples/cx_freeze/myapp.ui @@ -0,0 +1,344 @@ + + + + false + + + on_mainmenu_action + true + Clear + + + + + on_mainmenu_action + true + About + + + + + + + + on_mainmenu_action + true + Quit + + + + + 200 + both + Myapp + 200 + + + 200 + 2 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 2 + 200 + 200 + + 0 + True + 0 + ew + + + + btn_menu_clicked + MenuIcon4.gif + Toolbutton + Myappmenu + + 0 + True + 0 + + + + + + vertical + + 1 + 5 + True + 0 + ns + + + + + + btn_triangle_clicked + ps_triangle.gif + Toolbutton + triangle + + 2 + True + 0 + + + + + + btn_circle_clicked + ps_circle.gif + Toolbutton + circle + + 3 + True + 0 + + + + + + btn_cross_clicked + ps_cross.gif + Toolbutton + cross + + 4 + True + 0 + + + + + + btn_square_clicked + ps_square.gif + Toolbutton + square + + 5 + True + 0 + + + + + + + + 200 + 200 + + 0 + True + 1 + nsew + + + 1 + + + + + 1 + + + + + + both + + 0 + True + 0 + nsew + + + + #ffffff + 0 + + 0 + True + 0 + nsew + + + + + + + + + + 1 + 200 + ridge + 200 + + 0 + True + 2 + ew + + + + Welcome :) + + 0 + True + 0 + ew + + + + + + + + + + 240x200 + 100 + 100|100 + false + both + Myapp: About + 200 + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + + {Helvetica} 16 {} + Myapp + + 0 + True + 0 + + + + + + right + This is a demo app for testing +application freezing systems. + + 0 + True + 1 + + + + + + TkSmallCaptionFont + right + (All icons were downloaded from +creativecommons.org +under public domain) + + 0 + True + 2 + + + + + + + + 200 + 2 20 2 2 + 200 + + 0 + True + 1 + ew + + + 1 + + + + + + Close + + 0 + True + 0 + e + + + + + + + + + diff --git a/examples/cx_freeze/setup.py b/examples/cx_freeze/setup.py index 4ac66c85..538dd67e 100644 --- a/examples/cx_freeze/setup.py +++ b/examples/cx_freeze/setup.py @@ -1,10 +1,13 @@ import sys from cx_Freeze import setup, Executable +# Note: I'm using cx_freeze on python 2.7, +# change tkinter module names when using python3 + # Dependencies are automatically detected, but it might need fine tuning. build_exe_options = { "packages": [ - "os", "tkinter", + "os", "Tkinter", 'tkMessageBox', 'ttk', # Pygubu packages: "pygubu.builder.tkstdwidgets", "pygubu.builder.ttkstdwidgets", @@ -15,7 +18,7 @@ "pygubu.builder.widgets.tkscrollbarhelper", "pygubu.builder.widgets.tkscrolledframe", "pygubu.builder.widgets.pathchooserinput"], - 'include_files': ['button_cb.ui'] + 'include_files': ['myapp.ui', 'imgs'] } # GUI applications require a different base on Windows (the default is for a @@ -24,8 +27,8 @@ if sys.platform == "win32": base = "Win32GUI" -setup( name = "button_cb", +setup( name = "myapp", version = "0.1", description = "My GUI application!", options = {"build_exe": build_exe_options}, - executables = [Executable("button_cb.py", base=base)]) + executables = [Executable("myapp.py", base=base)]) diff --git a/examples/nuitka/copydatafiles.bat b/examples/nuitka/copydatafiles.bat new file mode 100644 index 00000000..e76a1d7b --- /dev/null +++ b/examples/nuitka/copydatafiles.bat @@ -0,0 +1,3 @@ +copy myapp.ui myapp.dist +mkdir myapp.dist\imgs +copy imgs myapp.dist\imgs diff --git a/examples/nuitka/imgs/MenuIcon4.gif b/examples/nuitka/imgs/MenuIcon4.gif new file mode 100644 index 0000000000000000000000000000000000000000..9824a61653a16159e7f7d35bedad7e6e9dbe0864 GIT binary patch literal 72 zcmZ?wbhEHb6k*_J_`m=Kia%Kx85kHDbU=KN3y6_g9i5BZD;nRtOd2 literal 0 HcmV?d00001 diff --git a/examples/nuitka/imgs/ps_circle.gif b/examples/nuitka/imgs/ps_circle.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac199fe8fd52240365c225caef51ea5f8d6a4f10 GIT binary patch literal 90 zcmZ?wbhEHb6k_0K_{hxgKQ;A#TH62gbOr_n#h)yU3=GT+IzTo9NS=X7dyf6W6D$7T t>SQ~0m1UmewRa713N822=S3^Uy}dPIrS$gpwE9J+PFGHzlH_8r1^^GJAh7@d literal 0 HcmV?d00001 diff --git a/examples/nuitka/imgs/ps_cross.gif b/examples/nuitka/imgs/ps_cross.gif new file mode 100644 index 0000000000000000000000000000000000000000..586ab4428170f5364ddb34d466cbfe8cd9b2556a GIT binary patch literal 104 zcmZ?wbhEHb6k_0K_{7Fgz43YVrWZ9EU({@RQM(yPzGMIc#h)yU3=C`xIv^fMEd#S| z#;!ZIhjkewd-{^1XHGk2TDssz<7wALUK=*u(ympiYTHz#y>UUaJX6`*d pC*z8XwYIBu_MO@w4Y_%di;_>SP2%39{^#NZrW@ZUEn{V{1_0AXA1?p^ literal 0 HcmV?d00001 diff --git a/examples/nuitka/imgs/ps_triangle.gif b/examples/nuitka/imgs/ps_triangle.gif new file mode 100644 index 0000000000000000000000000000000000000000..b965d43a29bb48421a203be1a8e815b5b06bf2eb GIT binary patch literal 131 zcmZ?wbhEHb6k_0K_{_~<|7d~z;{^_n<~uxG;P`N%!=nX`j}|yQ29gUM9xrr!ypRDE zDE?$&WMJTC&;bd9%wS+KC^+f4dadH!*kAkRe)nByp_e2$CoN!u{>~!9$c(TV*Qa=@ V+J#&Qlf0F+bcxZnoPA0R)&TPLF8Tlf literal 0 HcmV?d00001 diff --git a/examples/nuitka/myapp.py b/examples/nuitka/myapp.py index ef8f3b20..68961a90 100644 --- a/examples/nuitka/myapp.py +++ b/examples/nuitka/myapp.py @@ -1,4 +1,7 @@ #file: myapp.py +import os +import random +import math import tkinter as tk import tkinter.messagebox import tkinter.ttk as ttk @@ -8,31 +11,175 @@ import nuitkahelper +CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) +DEG2RAD = 4 * math.atan(1) * 2 / 360 + + class MyApplication: def __init__(self): + self.about_dialog = None + self.builder = b = pygubu.Builder() - b.add_from_file('myapp.ui') + b.add_from_file(os.path.join(CURRENT_DIR, 'myapp.ui')) + b.add_resource_path(os.path.join(CURRENT_DIR, 'imgs')) + self.mainwindow = b.get_object('mainwindow') + self.mainmenu = b.get_object('mainmenu', self.mainwindow) + self.btn_menu = b.get_object('btn_menu') + #self.mainwindow['menu'] = menu + self.canvas = b.get_object('main_canvas') + + # Connect to Delete event + self.mainwindow.protocol("WM_DELETE_WINDOW", self.quit) + b.connect_callbacks(self) - def on_action1_clicked(self): - tk.messagebox.showinfo('Myapp', 'Action 1 clicked') + def on_mainmenu_action(self, option_id=None): + if option_id == 'mm_clear': + self.canvas.delete('all') + if option_id == 'mm_about': + self.show_about_dialog() + if option_id == 'mm_quit': + self.mainwindow.quit() + + def btn_menu_clicked(self): + # this is ugly but I don't want to use menubutton :( + x, y = (self.btn_menu.winfo_rootx(), self.btn_menu.winfo_rooty()) + y = y + self.btn_menu.winfo_height() + try: + self.mainmenu.tk_popup(x, y, '') + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.mainmenu.grab_release() + + def show_about_dialog(self): + if self.about_dialog is None: + dialog = self.builder.get_object('dlg_about', self.mainwindow) + self.about_dialog = dialog + + def dialog_btnclose_clicked(): + dialog.close() + + btnclose = self.builder.get_object('about_btnclose') + btnclose['command'] = dialog_btnclose_clicked + + dialog.run() + else: + self.about_dialog.show() + + def btn_square_clicked(self): + self._draw_figure('square') + + def btn_cross_clicked(self): + self._draw_figure('cross') + + def btn_circle_clicked(self): + self._draw_figure('circle') + + def btn_triangle_clicked(self): + self._draw_figure('triangle') + + def quit(self, event=None): + self.mainwindow.quit() - def on_action2_clicked(self): - tk.messagebox.showinfo('Myapp', 'Action 2 clicked') + def run(self): + self.mainwindow.mainloop() + + def _draw_figure(self, figure): + canvas = self.canvas + max_iterations = random.randint(3, 10) + + for i in range(0, max_iterations): + canvasw = canvas.winfo_width() + canvash = canvas.winfo_height() + borderw = random.randint(1, 5) + max_width = int(canvas.winfo_width()*0.25) + w = random.randint(20, max_width) + x = random.randint(0, canvasw) - int(max_width * 0.5) + y = random.randint(0, canvash) - int(max_width * 0.5) + start = random.randint(0, 180) + + if figure == 'circle': + canvas.create_oval(x, y, x+w, y+w, outline='#FF6666', width=borderw) + if figure == 'square': + self.create_regpoly(x, y, x+w, y+w, + sides=4, start=start, + outline='#ED9DE9', width=borderw, fill='') + if figure == 'triangle': + self.create_regpoly(x, y, x+w, y+w, + sides=3, start=start, + outline='#40E2A0', width=borderw, fill='') + if figure == 'cross': + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start, + outline='#80B3E7', width=borderw, fill='') + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start+90, + outline='#80B3E7', width=borderw, fill='') + + def create_regpoly(self, x0, y0, x1, y1, sides=0, start=90, extent=360, **kw): + """Create a regular polygon""" + coords = self.__regpoly_coords(x0, y0, x1, y1, sides, start, extent) + return self.canvas.create_polygon(*coords, **kw) - def on_action3_clicked(self): - tk.messagebox.showinfo('Myapp', 'Action 3 clicked') + def __regpoly_coords(self, x0, y0, x1, y1, sides, start, extent): + """Create the coordinates of the regular polygon specified""" - def on_appmenu_action(self, command_id): - tk.messagebox.showinfo('Myapp', 'Byby!') - self.mainwindow.quit() - - def run(self): - self.mainwindow.mainloop(); + coords = [] + if extent == 0: + return coords + + xm = (x0 + x1) / 2. + ym = (y0 + y1) / 2. + rx = xm - x0 + ry = ym - y0 + + n = sides + if n == 0: # 0 sides => circle + n = round((rx + ry) * .5) + if n < 2: + n = 4 + + # Extent can be negative + dirv = 1 if extent > 0 else -1 + if abs(extent) > 360: + extent = dirv * abs(extent) % 360 + + step = dirv * 360 / n + numsteps = 1 + extent / float(step) + numsteps_int = int(numsteps) + + i = 0 + while i < numsteps_int: + rad = (start - i * step) * DEG2RAD + x = rx * math.cos(rad) + y = ry * math.sin(rad) + coords.append((xm+x, ym-y)) + i += 1 + + # Figure out where last segment should end + if numsteps != numsteps_int: + # Vecter V1 is last drawn vertext (x,y) from above + # Vector V2 is the edge of the polygon + rad2 = (start - numsteps_int * step) * DEG2RAD + x2 = rx * math.cos(rad2) - x + y2 = ry * math.sin(rad2) - y + + # Vector V3 is unit vector in direction we end at + rad3 = (start - extent) * DEG2RAD + x3 = math.cos(rad3) + y3 = math.sin(rad3) + + # Find where V3 crosses V1+V2 => find j s.t. V1 + kV2 = jV3 + j = (x*y2 - x2*y) / (x3*y2 - x2*y3) + + coords.append((xm + j * x3, ym - j * y3)) + + return coords if __name__ == '__main__': app = MyApplication() app.run(); + diff --git a/examples/nuitka/myapp.ui b/examples/nuitka/myapp.ui index 45ecf1d9..aebba68a 100644 --- a/examples/nuitka/myapp.ui +++ b/examples/nuitka/myapp.ui @@ -1,12 +1,39 @@ + + false + + + on_mainmenu_action + true + Clear + + + + + on_mainmenu_action + true + About + + + + + + + + on_mainmenu_action + true + Quit + + + 200 both Myapp 200 - + 200 2 200 @@ -27,7 +54,8 @@ - + + 2 200 200 @@ -37,10 +65,11 @@ ew - - on_action1_clicked + + btn_menu_clicked + MenuIcon4.gif Toolbutton - Action1 + Myappmenu 0 True @@ -49,22 +78,23 @@ - - on_action2_clicked - Toolbutton - Action2 + + vertical 1 + 5 True 0 + ns - - on_action3_clicked + + btn_triangle_clicked + ps_triangle.gif Toolbutton - Action3 + triangle 2 True @@ -73,30 +103,48 @@ - - AppMenu + + btn_circle_clicked + ps_circle.gif + Toolbutton + circle 3 True 0 - - - - - on_appmenu_action - true - Exit - - - - + + + + + btn_cross_clicked + ps_cross.gif + Toolbutton + cross + + 4 + True + 0 + + + + + + btn_square_clicked + ps_square.gif + Toolbutton + square + + 5 + True + 0 + - + 200 200 @@ -125,7 +173,7 @@ nsew - + #ffffff 0 @@ -141,10 +189,9 @@ - - 0 + + 1 200 - 2 ridge 200 @@ -154,8 +201,8 @@ ew - - status bar here + + Welcome :) 0 True @@ -169,4 +216,129 @@ + + 240x200 + 100 + 100|100 + false + both + Myapp: About + 200 + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + + {Helvetica} 16 {} + Myapp + + 0 + True + 0 + + + + + + right + This is a demo app for testing +application freezing systems. + + 0 + True + 1 + + + + + + TkSmallCaptionFont + right + (All icons were downloaded from +creativecommons.org +under public domain) + + 0 + True + 2 + + + + + + + + 200 + 2 20 2 2 + 200 + + 0 + True + 1 + ew + + + 1 + + + + + + Close + + 0 + True + 0 + e + + + + + + + + diff --git a/examples/py2exe/button_cb.py b/examples/py2exe/button_cb.py deleted file mode 100644 index e7d7a825..00000000 --- a/examples/py2exe/button_cb.py +++ /dev/null @@ -1,45 +0,0 @@ -# button_cb.py -import sys -import os - -try: - import tkinter as tk - from tkinter import messagebox -except: - import Tkinter as tk - import tkMessageBox as messagebox - -try: - CDIR = os.path.dirname(__file__) -except NameError: - CDIR = os.path.dirname(os.path.abspath(sys.argv[0])) - -import pygubu - -class Myapp: - def __init__(self, master): - self.builder = builder = pygubu.Builder() - fpath = os.path.join(CDIR,"button_cb.ui") - builder.add_from_file(fpath) - - mainwindow = builder.get_object('mainwindow', master) - - builder.connect_callbacks(self) - - callbacks = { - 'on_button2_clicked': self.on_button2_clicked - } - - builder.connect_callbacks(callbacks) - - def on_my_button_clicked(self): - messagebox.showinfo('From callback', 'My button was clicked !!') - - def on_button2_clicked(self): - messagebox.showinfo('From callback', 'Button 2 was clicked !!') - -if __name__ == '__main__': - root = tk.Tk() - app = Myapp(root) - root.mainloop() - diff --git a/examples/py2exe/button_cb.ui b/examples/py2exe/button_cb.ui deleted file mode 100644 index 07be6081..00000000 --- a/examples/py2exe/button_cb.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - -250 -10 -250 - -0 -0 - - -10 - - -10 - - - - - -e -Button command test ? ouch!! - -0 -ew -1 -0 - - - - - -on_my_button_clicked -Click me - -0 -1 - - - - - -on_button2_clicked -Button 2 - -1 -1 - - - - - diff --git a/examples/py2exe/imgs/MenuIcon4.gif b/examples/py2exe/imgs/MenuIcon4.gif new file mode 100644 index 0000000000000000000000000000000000000000..9824a61653a16159e7f7d35bedad7e6e9dbe0864 GIT binary patch literal 72 zcmZ?wbhEHb6k*_J_`m=Kia%Kx85kHDbU=KN3y6_g9i5BZD;nRtOd2 literal 0 HcmV?d00001 diff --git a/examples/py2exe/imgs/ps_circle.gif b/examples/py2exe/imgs/ps_circle.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac199fe8fd52240365c225caef51ea5f8d6a4f10 GIT binary patch literal 90 zcmZ?wbhEHb6k_0K_{hxgKQ;A#TH62gbOr_n#h)yU3=GT+IzTo9NS=X7dyf6W6D$7T t>SQ~0m1UmewRa713N822=S3^Uy}dPIrS$gpwE9J+PFGHzlH_8r1^^GJAh7@d literal 0 HcmV?d00001 diff --git a/examples/py2exe/imgs/ps_cross.gif b/examples/py2exe/imgs/ps_cross.gif new file mode 100644 index 0000000000000000000000000000000000000000..586ab4428170f5364ddb34d466cbfe8cd9b2556a GIT binary patch literal 104 zcmZ?wbhEHb6k_0K_{7Fgz43YVrWZ9EU({@RQM(yPzGMIc#h)yU3=C`xIv^fMEd#S| z#;!ZIhjkewd-{^1XHGk2TDssz<7wALUK=*u(ympiYTHz#y>UUaJX6`*d pC*z8XwYIBu_MO@w4Y_%di;_>SP2%39{^#NZrW@ZUEn{V{1_0AXA1?p^ literal 0 HcmV?d00001 diff --git a/examples/py2exe/imgs/ps_triangle.gif b/examples/py2exe/imgs/ps_triangle.gif new file mode 100644 index 0000000000000000000000000000000000000000..b965d43a29bb48421a203be1a8e815b5b06bf2eb GIT binary patch literal 131 zcmZ?wbhEHb6k_0K_{_~<|7d~z;{^_n<~uxG;P`N%!=nX`j}|yQ29gUM9xrr!ypRDE zDE?$&WMJTC&;bd9%wS+KC^+f4dadH!*kAkRe)nByp_e2$CoN!u{>~!9$c(TV*Qa=@ V+J#&Qlf0F+bcxZnoPA0R)&TPLF8Tlf literal 0 HcmV?d00001 diff --git a/examples/py2exe/myapp.py b/examples/py2exe/myapp.py new file mode 100644 index 00000000..9324bfa1 --- /dev/null +++ b/examples/py2exe/myapp.py @@ -0,0 +1,195 @@ +#file: myapp.py +import sys +import os +import random +import math + +try: + import Tkinter as tk + import ttk + import tkMessageBox as tkmb + tk.messagebox = tkmb +except: + import tkinter as tk + import tkinter.messagebox + import tkinter.ttk as ttk + +import pygubu + + +try: + DATA_DIR = os.path.abspath(os.path.dirname(__file__)) +except NameError: + DATA_DIR = os.path.dirname(os.path.abspath(sys.argv[0])) + +DEG2RAD = 4 * math.atan(1) * 2 / 360 + + +class MyApplication: + + def __init__(self): + self.about_dialog = None + + self.builder = b = pygubu.Builder() + b.add_from_file(os.path.join(DATA_DIR, 'myapp.ui')) + b.add_resource_path(os.path.join(DATA_DIR, 'imgs')) + + self.mainwindow = b.get_object('mainwindow') + self.mainmenu = b.get_object('mainmenu', self.mainwindow) + self.btn_menu = b.get_object('btn_menu') + #self.mainwindow['menu'] = menu + self.canvas = b.get_object('main_canvas') + + # Connect to Delete event + self.mainwindow.protocol("WM_DELETE_WINDOW", self.quit) + + b.connect_callbacks(self) + + def on_mainmenu_action(self, option_id=None): + if option_id == 'mm_clear': + self.canvas.delete('all') + if option_id == 'mm_about': + self.show_about_dialog() + if option_id == 'mm_quit': + self.mainwindow.quit() + + def btn_menu_clicked(self): + # this is ugly but I don't want to use menubutton :( + x, y = (self.btn_menu.winfo_rootx(), self.btn_menu.winfo_rooty()) + y = y + self.btn_menu.winfo_height() + try: + self.mainmenu.tk_popup(x, y, '') + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.mainmenu.grab_release() + + def show_about_dialog(self): + if self.about_dialog is None: + dialog = self.builder.get_object('dlg_about', self.mainwindow) + self.about_dialog = dialog + + def dialog_btnclose_clicked(): + dialog.close() + + btnclose = self.builder.get_object('about_btnclose') + btnclose['command'] = dialog_btnclose_clicked + + dialog.run() + else: + self.about_dialog.show() + + def btn_square_clicked(self): + self._draw_figure('square') + + def btn_cross_clicked(self): + self._draw_figure('cross') + + def btn_circle_clicked(self): + self._draw_figure('circle') + + def btn_triangle_clicked(self): + self._draw_figure('triangle') + + def quit(self, event=None): + self.mainwindow.quit() + + def run(self): + self.mainwindow.mainloop() + + def _draw_figure(self, figure): + canvas = self.canvas + max_iterations = random.randint(3, 10) + + for i in range(0, max_iterations): + canvasw = canvas.winfo_width() + canvash = canvas.winfo_height() + borderw = random.randint(1, 5) + max_width = int(canvas.winfo_width()*0.25) + w = random.randint(20, max_width) + x = random.randint(0, canvasw) - int(max_width * 0.5) + y = random.randint(0, canvash) - int(max_width * 0.5) + start = random.randint(0, 180) + + if figure == 'circle': + canvas.create_oval(x, y, x+w, y+w, outline='#FF6666', width=borderw) + if figure == 'square': + self.create_regpoly(x, y, x+w, y+w, + sides=4, start=start, + outline='#ED9DE9', width=borderw, fill='') + if figure == 'triangle': + self.create_regpoly(x, y, x+w, y+w, + sides=3, start=start, + outline='#40E2A0', width=borderw, fill='') + if figure == 'cross': + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start, + outline='#80B3E7', width=borderw, fill='') + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start+90, + outline='#80B3E7', width=borderw, fill='') + + def create_regpoly(self, x0, y0, x1, y1, sides=0, start=90, extent=360, **kw): + """Create a regular polygon""" + coords = self.__regpoly_coords(x0, y0, x1, y1, sides, start, extent) + return self.canvas.create_polygon(*coords, **kw) + + def __regpoly_coords(self, x0, y0, x1, y1, sides, start, extent): + """Create the coordinates of the regular polygon specified""" + + coords = [] + if extent == 0: + return coords + + xm = (x0 + x1) / 2. + ym = (y0 + y1) / 2. + rx = xm - x0 + ry = ym - y0 + + n = sides + if n == 0: # 0 sides => circle + n = round((rx + ry) * .5) + if n < 2: + n = 4 + + # Extent can be negative + dirv = 1 if extent > 0 else -1 + if abs(extent) > 360: + extent = dirv * abs(extent) % 360 + + step = dirv * 360 / n + numsteps = 1 + extent / float(step) + numsteps_int = int(numsteps) + + i = 0 + while i < numsteps_int: + rad = (start - i * step) * DEG2RAD + x = rx * math.cos(rad) + y = ry * math.sin(rad) + coords.append((xm+x, ym-y)) + i += 1 + + # Figure out where last segment should end + if numsteps != numsteps_int: + # Vecter V1 is last drawn vertext (x,y) from above + # Vector V2 is the edge of the polygon + rad2 = (start - numsteps_int * step) * DEG2RAD + x2 = rx * math.cos(rad2) - x + y2 = ry * math.sin(rad2) - y + + # Vector V3 is unit vector in direction we end at + rad3 = (start - extent) * DEG2RAD + x3 = math.cos(rad3) + y3 = math.sin(rad3) + + # Find where V3 crosses V1+V2 => find j s.t. V1 + kV2 = jV3 + j = (x*y2 - x2*y) / (x3*y2 - x2*y3) + + coords.append((xm + j * x3, ym - j * y3)) + + return coords + + +if __name__ == '__main__': + app = MyApplication() + app.run(); + diff --git a/examples/py2exe/myapp.ui b/examples/py2exe/myapp.ui new file mode 100644 index 00000000..aebba68a --- /dev/null +++ b/examples/py2exe/myapp.ui @@ -0,0 +1,344 @@ + + + + false + + + on_mainmenu_action + true + Clear + + + + + on_mainmenu_action + true + About + + + + + + + + on_mainmenu_action + true + Quit + + + + + 200 + both + Myapp + 200 + + + 200 + 2 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 2 + 200 + 200 + + 0 + True + 0 + ew + + + + btn_menu_clicked + MenuIcon4.gif + Toolbutton + Myappmenu + + 0 + True + 0 + + + + + + vertical + + 1 + 5 + True + 0 + ns + + + + + + btn_triangle_clicked + ps_triangle.gif + Toolbutton + triangle + + 2 + True + 0 + + + + + + btn_circle_clicked + ps_circle.gif + Toolbutton + circle + + 3 + True + 0 + + + + + + btn_cross_clicked + ps_cross.gif + Toolbutton + cross + + 4 + True + 0 + + + + + + btn_square_clicked + ps_square.gif + Toolbutton + square + + 5 + True + 0 + + + + + + + + 200 + 200 + + 0 + True + 1 + nsew + + + 1 + + + + + 1 + + + + + + both + + 0 + True + 0 + nsew + + + + #ffffff + 0 + + 0 + True + 0 + nsew + + + + + + + + + + 1 + 200 + ridge + 200 + + 0 + True + 2 + ew + + + + Welcome :) + + 0 + True + 0 + ew + + + + + + + + + + 240x200 + 100 + 100|100 + false + both + Myapp: About + 200 + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + + {Helvetica} 16 {} + Myapp + + 0 + True + 0 + + + + + + right + This is a demo app for testing +application freezing systems. + + 0 + True + 1 + + + + + + TkSmallCaptionFont + right + (All icons were downloaded from +creativecommons.org +under public domain) + + 0 + True + 2 + + + + + + + + 200 + 2 20 2 2 + 200 + + 0 + True + 1 + ew + + + 1 + + + + + + Close + + 0 + True + 0 + e + + + + + + + + + diff --git a/examples/py2exe/setup.py b/examples/py2exe/setup.py index 464f02cd..a5cb0ecd 100644 --- a/examples/py2exe/setup.py +++ b/examples/py2exe/setup.py @@ -6,8 +6,17 @@ sys.argv.append("py2exe") setup( - console=["button_cb.py"], - data_files=[("", ["button_cb.ui"])], + console=["myapp.py"], + data_files=[ + ("", ['myapp.ui']), + ('imgs', [ + 'imgs/MenuIcon4.gif', + 'imgs/ps_circle.gif', + 'imgs/ps_cross.gif', + 'imgs/ps_square.gif', + 'imgs/ps_triangle.gif', + ]) + ], options= { "py2exe": { "includes" : ["pygubu.builder.tkstdwidgets", diff --git a/examples/pyinstaller/imgs/MenuIcon4.gif b/examples/pyinstaller/imgs/MenuIcon4.gif new file mode 100644 index 0000000000000000000000000000000000000000..9824a61653a16159e7f7d35bedad7e6e9dbe0864 GIT binary patch literal 72 zcmZ?wbhEHb6k*_J_`m=Kia%Kx85kHDbU=KN3y6_g9i5BZD;nRtOd2 literal 0 HcmV?d00001 diff --git a/examples/pyinstaller/imgs/ps_circle.gif b/examples/pyinstaller/imgs/ps_circle.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac199fe8fd52240365c225caef51ea5f8d6a4f10 GIT binary patch literal 90 zcmZ?wbhEHb6k_0K_{hxgKQ;A#TH62gbOr_n#h)yU3=GT+IzTo9NS=X7dyf6W6D$7T t>SQ~0m1UmewRa713N822=S3^Uy}dPIrS$gpwE9J+PFGHzlH_8r1^^GJAh7@d literal 0 HcmV?d00001 diff --git a/examples/pyinstaller/imgs/ps_cross.gif b/examples/pyinstaller/imgs/ps_cross.gif new file mode 100644 index 0000000000000000000000000000000000000000..586ab4428170f5364ddb34d466cbfe8cd9b2556a GIT binary patch literal 104 zcmZ?wbhEHb6k_0K_{7Fgz43YVrWZ9EU({@RQM(yPzGMIc#h)yU3=C`xIv^fMEd#S| z#;!ZIhjkewd-{^1XHGk2TDssz<7wALUK=*u(ympiYTHz#y>UUaJX6`*d pC*z8XwYIBu_MO@w4Y_%di;_>SP2%39{^#NZrW@ZUEn{V{1_0AXA1?p^ literal 0 HcmV?d00001 diff --git a/examples/pyinstaller/imgs/ps_triangle.gif b/examples/pyinstaller/imgs/ps_triangle.gif new file mode 100644 index 0000000000000000000000000000000000000000..b965d43a29bb48421a203be1a8e815b5b06bf2eb GIT binary patch literal 131 zcmZ?wbhEHb6k_0K_{_~<|7d~z;{^_n<~uxG;P`N%!=nX`j}|yQ29gUM9xrr!ypRDE zDE?$&WMJTC&;bd9%wS+KC^+f4dadH!*kAkRe)nByp_e2$CoN!u{>~!9$c(TV*Qa=@ V+J#&Qlf0F+bcxZnoPA0R)&TPLF8Tlf literal 0 HcmV?d00001 diff --git a/examples/pyinstaller/myapp.py b/examples/pyinstaller/myapp.py new file mode 100644 index 00000000..9324bfa1 --- /dev/null +++ b/examples/pyinstaller/myapp.py @@ -0,0 +1,195 @@ +#file: myapp.py +import sys +import os +import random +import math + +try: + import Tkinter as tk + import ttk + import tkMessageBox as tkmb + tk.messagebox = tkmb +except: + import tkinter as tk + import tkinter.messagebox + import tkinter.ttk as ttk + +import pygubu + + +try: + DATA_DIR = os.path.abspath(os.path.dirname(__file__)) +except NameError: + DATA_DIR = os.path.dirname(os.path.abspath(sys.argv[0])) + +DEG2RAD = 4 * math.atan(1) * 2 / 360 + + +class MyApplication: + + def __init__(self): + self.about_dialog = None + + self.builder = b = pygubu.Builder() + b.add_from_file(os.path.join(DATA_DIR, 'myapp.ui')) + b.add_resource_path(os.path.join(DATA_DIR, 'imgs')) + + self.mainwindow = b.get_object('mainwindow') + self.mainmenu = b.get_object('mainmenu', self.mainwindow) + self.btn_menu = b.get_object('btn_menu') + #self.mainwindow['menu'] = menu + self.canvas = b.get_object('main_canvas') + + # Connect to Delete event + self.mainwindow.protocol("WM_DELETE_WINDOW", self.quit) + + b.connect_callbacks(self) + + def on_mainmenu_action(self, option_id=None): + if option_id == 'mm_clear': + self.canvas.delete('all') + if option_id == 'mm_about': + self.show_about_dialog() + if option_id == 'mm_quit': + self.mainwindow.quit() + + def btn_menu_clicked(self): + # this is ugly but I don't want to use menubutton :( + x, y = (self.btn_menu.winfo_rootx(), self.btn_menu.winfo_rooty()) + y = y + self.btn_menu.winfo_height() + try: + self.mainmenu.tk_popup(x, y, '') + finally: + # make sure to release the grab (Tk 8.0a1 only) + self.mainmenu.grab_release() + + def show_about_dialog(self): + if self.about_dialog is None: + dialog = self.builder.get_object('dlg_about', self.mainwindow) + self.about_dialog = dialog + + def dialog_btnclose_clicked(): + dialog.close() + + btnclose = self.builder.get_object('about_btnclose') + btnclose['command'] = dialog_btnclose_clicked + + dialog.run() + else: + self.about_dialog.show() + + def btn_square_clicked(self): + self._draw_figure('square') + + def btn_cross_clicked(self): + self._draw_figure('cross') + + def btn_circle_clicked(self): + self._draw_figure('circle') + + def btn_triangle_clicked(self): + self._draw_figure('triangle') + + def quit(self, event=None): + self.mainwindow.quit() + + def run(self): + self.mainwindow.mainloop() + + def _draw_figure(self, figure): + canvas = self.canvas + max_iterations = random.randint(3, 10) + + for i in range(0, max_iterations): + canvasw = canvas.winfo_width() + canvash = canvas.winfo_height() + borderw = random.randint(1, 5) + max_width = int(canvas.winfo_width()*0.25) + w = random.randint(20, max_width) + x = random.randint(0, canvasw) - int(max_width * 0.5) + y = random.randint(0, canvash) - int(max_width * 0.5) + start = random.randint(0, 180) + + if figure == 'circle': + canvas.create_oval(x, y, x+w, y+w, outline='#FF6666', width=borderw) + if figure == 'square': + self.create_regpoly(x, y, x+w, y+w, + sides=4, start=start, + outline='#ED9DE9', width=borderw, fill='') + if figure == 'triangle': + self.create_regpoly(x, y, x+w, y+w, + sides=3, start=start, + outline='#40E2A0', width=borderw, fill='') + if figure == 'cross': + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start, + outline='#80B3E7', width=borderw, fill='') + self.create_regpoly(x, y, x+w, y+w, + sides=2, start=start+90, + outline='#80B3E7', width=borderw, fill='') + + def create_regpoly(self, x0, y0, x1, y1, sides=0, start=90, extent=360, **kw): + """Create a regular polygon""" + coords = self.__regpoly_coords(x0, y0, x1, y1, sides, start, extent) + return self.canvas.create_polygon(*coords, **kw) + + def __regpoly_coords(self, x0, y0, x1, y1, sides, start, extent): + """Create the coordinates of the regular polygon specified""" + + coords = [] + if extent == 0: + return coords + + xm = (x0 + x1) / 2. + ym = (y0 + y1) / 2. + rx = xm - x0 + ry = ym - y0 + + n = sides + if n == 0: # 0 sides => circle + n = round((rx + ry) * .5) + if n < 2: + n = 4 + + # Extent can be negative + dirv = 1 if extent > 0 else -1 + if abs(extent) > 360: + extent = dirv * abs(extent) % 360 + + step = dirv * 360 / n + numsteps = 1 + extent / float(step) + numsteps_int = int(numsteps) + + i = 0 + while i < numsteps_int: + rad = (start - i * step) * DEG2RAD + x = rx * math.cos(rad) + y = ry * math.sin(rad) + coords.append((xm+x, ym-y)) + i += 1 + + # Figure out where last segment should end + if numsteps != numsteps_int: + # Vecter V1 is last drawn vertext (x,y) from above + # Vector V2 is the edge of the polygon + rad2 = (start - numsteps_int * step) * DEG2RAD + x2 = rx * math.cos(rad2) - x + y2 = ry * math.sin(rad2) - y + + # Vector V3 is unit vector in direction we end at + rad3 = (start - extent) * DEG2RAD + x3 = math.cos(rad3) + y3 = math.sin(rad3) + + # Find where V3 crosses V1+V2 => find j s.t. V1 + kV2 = jV3 + j = (x*y2 - x2*y) / (x3*y2 - x2*y3) + + coords.append((xm + j * x3, ym - j * y3)) + + return coords + + +if __name__ == '__main__': + app = MyApplication() + app.run(); + diff --git a/examples/pyinstaller/myapp.spec b/examples/pyinstaller/myapp.spec new file mode 100644 index 00000000..59b74093 --- /dev/null +++ b/examples/pyinstaller/myapp.spec @@ -0,0 +1,50 @@ +# -*- mode: python -*- + +block_cipher = None + +# specify pygubu modules +hidden_imports = [ + 'pygubu.builder.tkstdwidgets', + 'pygubu.builder.ttkstdwidgets', + 'pygubu.builder.widgets.dialog', + 'pygubu.builder.widgets.editabletreeview', + 'pygubu.builder.widgets.scrollbarhelper', + 'pygubu.builder.widgets.scrolledframe', + 'pygubu.builder.widgets.tkscrollbarhelper', + 'pygubu.builder.widgets.tkscrolledframe', + 'pygubu.builder.widgets.pathchooserinput', +] + +data_files = [ + ('myapp.ui', '.'), + ('imgs/*.gif', 'imgs'), +] + +a = Analysis(['myapp.py'], + pathex=['C:\\src\\pyinstaller'], + binaries=None, + datas=data_files, + hiddenimports=hidden_imports, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='myapp', + debug=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='myapp') diff --git a/examples/pyinstaller/myapp.ui b/examples/pyinstaller/myapp.ui new file mode 100644 index 00000000..aebba68a --- /dev/null +++ b/examples/pyinstaller/myapp.ui @@ -0,0 +1,344 @@ + + + + false + + + on_mainmenu_action + true + Clear + + + + + on_mainmenu_action + true + About + + + + + + + + on_mainmenu_action + true + Quit + + + + + 200 + both + Myapp + 200 + + + 200 + 2 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 2 + 200 + 200 + + 0 + True + 0 + ew + + + + btn_menu_clicked + MenuIcon4.gif + Toolbutton + Myappmenu + + 0 + True + 0 + + + + + + vertical + + 1 + 5 + True + 0 + ns + + + + + + btn_triangle_clicked + ps_triangle.gif + Toolbutton + triangle + + 2 + True + 0 + + + + + + btn_circle_clicked + ps_circle.gif + Toolbutton + circle + + 3 + True + 0 + + + + + + btn_cross_clicked + ps_cross.gif + Toolbutton + cross + + 4 + True + 0 + + + + + + btn_square_clicked + ps_square.gif + Toolbutton + square + + 5 + True + 0 + + + + + + + + 200 + 200 + + 0 + True + 1 + nsew + + + 1 + + + + + 1 + + + + + + both + + 0 + True + 0 + nsew + + + + #ffffff + 0 + + 0 + True + 0 + nsew + + + + + + + + + + 1 + 200 + ridge + 200 + + 0 + True + 2 + ew + + + + Welcome :) + + 0 + True + 0 + ew + + + + + + + + + + 240x200 + 100 + 100|100 + false + both + Myapp: About + 200 + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + + + 1 + + + + + + 200 + 200 + + 0 + True + 0 + nsew + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + + {Helvetica} 16 {} + Myapp + + 0 + True + 0 + + + + + + right + This is a demo app for testing +application freezing systems. + + 0 + True + 1 + + + + + + TkSmallCaptionFont + right + (All icons were downloaded from +creativecommons.org +under public domain) + + 0 + True + 2 + + + + + + + + 200 + 2 20 2 2 + 200 + + 0 + True + 1 + ew + + + 1 + + + + + + Close + + 0 + True + 0 + e + + + + + + + + + From 6a3c66f896647de1097a2d5b1d81a0921cf009d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Sun, 20 Nov 2016 01:09:12 -0300 Subject: [PATCH 4/9] Add check when getting menu index. fixes #81 --- pygubudesigner/previewer.py | 5 ++++- pygubudesigner/uitreeeditor.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pygubudesigner/previewer.py b/pygubudesigner/previewer.py index cce54627..4f25326d 100644 --- a/pygubudesigner/previewer.py +++ b/pygubudesigner/previewer.py @@ -284,7 +284,10 @@ def _calculate_menu_wh(self): """ Calculate menu widht and height.""" w = iw = 50 h = ih = 0 - count = self._menu.index(tk.END) + 1 + # menu.index returns None if there are no choices + index = self._menu.index(tk.END) + index = index if index is not None else 0 + count = index + 1 # First calculate using the font paramters of root menu: font = self._menu.cget('font') diff --git a/pygubudesigner/uitreeeditor.py b/pygubudesigner/uitreeeditor.py index 3fffebb2..4277792a 100644 --- a/pygubudesigner/uitreeeditor.py +++ b/pygubudesigner/uitreeeditor.py @@ -141,6 +141,7 @@ def on_treeview_delete_selection(self, event=None): self.filter_remove(remember=True) toplevel_items = tv.get_children() + parents_to_redraw = set() for item in selection: try: parent = '' @@ -153,12 +154,15 @@ def on_treeview_delete_selection(self, event=None): self.app.set_changed() if parent: self._update_max_grid_rc(parent) - self.draw_widget(parent) + parents_to_redraw.add(parent) self.widget_editor.hide_all() except tk.TclError: # Selection of parent and child items ?? # TODO: notify something here pass + # redraw widgets + for item in parents_to_redraw: + self.draw_widget(item) # restore filter self.filter_restore() From d01a0ea3c042a293d607a036e586070b770f02e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Sun, 20 Nov 2016 02:27:30 -0300 Subject: [PATCH 5/9] Update editors when header is resized. fixes #74 --- pygubu/widgets/editabletreeview.py | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pygubu/widgets/editabletreeview.py b/pygubu/widgets/editabletreeview.py index 1cf266cc..ba37cd3b 100644 --- a/pygubu/widgets/editabletreeview.py +++ b/pygubu/widgets/editabletreeview.py @@ -28,6 +28,29 @@ class EditableTreeview(ttk.Treeview): + """A simple editable treeview + + It uses the following events from Treeview: + <> + <4> + <5> + + + + + + + + If you need them use add=True when calling bind method. + + It Generates two virtual events: + <> + <> + The first is used to configure cell editors. + The second is called after a cell was changed. + You can know wich cell is being configured or edited, using: + get_event_info() + """ def __init__(self, master=None, **kw): ttk.Treeview.__init__(self, master, **kw) @@ -35,6 +58,8 @@ def __init__(self, master=None, **kw): self._inplace_widgets = {} self._inplace_widgets_show = {} self._inplace_vars = {} + self._header_clicked = False + self._header_dragged = False self.bind('<>', self.__check_focus) #Wheel events? @@ -44,10 +69,28 @@ def __init__(self, master=None, **kw): self.bind('', self.__check_focus) self.bind('', functools.partial(self.__on_key_press, 'Home')) self.bind('', functools.partial(self.__on_key_press, 'End')) + self.bind('', self.__on_button1) + self.bind('', self.__on_button1_release) + self.bind('', self.__on_mouse_motion) self.bind('', lambda e: self.after_idle(self.__updateWnds)) + def __on_button1(self, event): + r = event.widget.identify_region(event.x, event.y) + if r in ('separator', 'header'): + self._header_clicked = True + + def __on_mouse_motion(self, event): + if self._header_clicked: + self._header_dragged = True + + def __on_button1_release(self, event): + if self._header_dragged: + self.after_idle(self.__updateWnds) + self._header_clicked = False + self._header_dragged = False + def __on_key_press(self, key, event): if key == 'Home': self.selection_set("") From 64dcf976d6ee262646741ff12bd70ba8adb60297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Autal=C3=A1n?= Date: Sun, 20 Nov 2016 02:40:40 -0300 Subject: [PATCH 6/9] update version to sync with pypi. --- pygubu/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygubu/__init__.py b/pygubu/__init__.py index f2d3ca8f..209fbbaf 100644 --- a/pygubu/__init__.py +++ b/pygubu/__init__.py @@ -8,7 +8,7 @@ from pygubu.builder.builderobject import BuilderObject, register_widget -__version__ = '0.9.7.8' +__version__ = '0.9.7.9' def register_property(name, description): From d7563d592422d297d8fe838f360b0416da143cfb Mon Sep 17 00:00:00 2001 From: nu_no Date: Mon, 26 Dec 2016 16:57:38 +0100 Subject: [PATCH 7/9] Fix Windows batch file It crashes in paths with spaces like `c:\program files\python 3.5\python.exe -m pygubudesigner` returning `c:\program is not recognized as an internal or external command, operable program or batch file`. Putting the path between quotes solves this. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0dce77be..118afd8e 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def run(self): batfilename = 'pygubu-designer.bat' batpath = os.path.join(self.install_scripts, batfilename) with open(batpath, 'w') as batfile: - content = "{0} -m pygubudesigner".format(sys.executable) + content = '"{0}" -m pygubudesigner'.format(sys.executable) batfile.write(content) From d15a81d2690c2e1bb2789c0f6212782d43a6f2b0 Mon Sep 17 00:00:00 2001 From: nu_no Date: Mon, 26 Dec 2016 20:49:50 +0100 Subject: [PATCH 8/9] "OrderedDict mutated during iteration" in file_new (Python 3.5) In Python 3.5 OrderedDicts iterate over generators, not over lists, so when one tries to open a new file and the main window has previously loaded a project with more than one top level element (as in `pygubu-ui.ui`, which has two: `mainmenu` and `mainwindow`) it throws an exception and closes only the first element. C:\Users\Woden\Desktop>"c:\program files\python 3.5\python.exe" -m pygubudesigner python version: 3.5.0 on win32 pygubu version: 0.9.7.9 Exception in Tkinter callback Traceback (most recent call last): File "c:\program files\python 3.5\lib\tkinter\__init__.py", line 1549, in __call__ return self.func(*args) File "c:\program files\python 3.5\lib\site-packages\pygubu\builder\tkstdwidgets.py", line 489, in item_callback callback(item_id) File "c:\program files\python 3.5\lib\site-packages\pygubudesigner\main.py", line 426, in on_file_menuitem_clicked self.previewer.remove_all() File "c:\program files\python 3.5\lib\site-packages\pygubudesigner\previewer.py", line 609, in remove_all for identifier in self.previews: RuntimeError: OrderedDict mutated during iteration --- pygubudesigner/previewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygubudesigner/previewer.py b/pygubudesigner/previewer.py index 4f25326d..29a8b1f7 100644 --- a/pygubudesigner/previewer.py +++ b/pygubudesigner/previewer.py @@ -606,7 +606,7 @@ def reset_selected(self, identifier): self._sel_widget = None def remove_all(self): - for identifier in self.previews: + for identifier in list(self.previews): self.delete(identifier) self.resource_paths = [] From 7d244d298b38efeb8444f912e77883d14da06307 Mon Sep 17 00:00:00 2001 From: nu_no Date: Tue, 27 Dec 2016 14:16:42 +0100 Subject: [PATCH 9/9] Added %1 to windows batch to allow link pygubu files --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 118afd8e..3c90b8a9 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def run(self): batfilename = 'pygubu-designer.bat' batpath = os.path.join(self.install_scripts, batfilename) with open(batpath, 'w') as batfile: - content = '"{0}" -m pygubudesigner'.format(sys.executable) + content = '"{0}" -m pygubudesigner %1'.format(sys.executable) batfile.write(content)