• R/O
  • SSH

tkpane: Commit

Default repository for tkpane.py.


Commit MetaInfo

Revisión9d80a794577eac1794f919592b75aff4b0e419fa (tree)
Tiempo2018-01-30 14:24:16
AutorDreas Nielsen <dreas.nielsen@gmai...>
CommiterDreas Nielsen

Log Message

First commit. Working TkPane and several general pane types in lib.py.

Cambiar Resumen

Diferencia incremental

diff -r 000000000000 -r 9d80a794577e test/test_1.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_1.py Mon Jan 29 21:24:16 2018 -0800
@@ -0,0 +1,124 @@
1+#!/usr/bin/python
2+# test_1.py
3+#
4+# PURPOSE
5+#
6+#
7+# NOTES
8+# 1.
9+#
10+# AUTHOR
11+# Dreas Nielsen
12+#
13+# HISTORY
14+# Date Remarks
15+# ---------- -----------------------------------------------------------
16+# 2018-01-26 Created. RDN.
17+# 2018-01-27 Added button and message panes. RDN.
18+# 2018-01-28 Added application frame for additional padding. RDN.
19+# =========================================================================
20+
21+try:
22+ import Tkinter as tk
23+except:
24+ import tkinter as tk
25+
26+import sys
27+sys.path.append("..")
28+
29+from tkpane import *
30+import tkpane.lib
31+import tklayout
32+
33+# Add a method to the AppLayout class to get a pane: the first child of
34+# a frame's widgets.
35+def layout_pane(self, pane_name):
36+ return self.frame_widgets(pane_name)[0]
37+
38+tklayout.AppLayout.pane = layout_pane
39+
40+
41+# Create simple sets of data to display in the TableDisplayPane.
42+ds1_headers = ["column_1", "column_2", "column_3"]
43+ds1_data = [["abcde", 25.1, 5],
44+ ["fghij", 87.3, 8],
45+ ["klmno", 12.8, 3]]
46+
47+ds2_headers = ["row_id","row_number","long_text","some_date","some_number"]
48+ds2_data = [
49+ ["Row 4",4,"Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.","1951-03-19",61.9917173461],
50+ ["Row 8",8,"Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus.","1977-07-21",34.5729855806],
51+ ["Row 12",12,"DJs flock by when MTV ax quiz prog.","1983-10-12",2.3773967111]
52+ ]
53+
54+def build_message_pane(parent):
55+ tkpane.lib.MessagePane(parent, "Enter user credentials and an output directory to enable data operations.")
56+
57+def build_table_pane(parent):
58+ tkpane.lib.TableDisplayPane(parent, "This is a demo with a message that is long enough that it should wrap when the window is resized to a relatively small size.", ds1_headers, ds1_data)
59+
60+def build_button_pane(parent):
61+ def no_action():
62+ pass
63+ tkpane.lib.OkCancelPane(parent, no_action, no_action)
64+
65+# Lay out the panes
66+lo = tklayout.AppLayout()
67+inp_panes = lo.row_elements(["user_pane", "output_pane"], row_weight=0)
68+app = lo.column_elements(["message_pane", inp_panes, "table_pane", "button_pane", "status_pane"], row_weights=[0,0,1,0,0])
69+
70+root = tk.Tk()
71+
72+# Use an extra frame within the root element with padding to add extra space
73+# around the outermost app widgets.
74+appframe = tk.Frame(root, padx=11, pady=11)
75+appframe.pack(expand=True, fill=tk.BOTH)
76+
77+lo.create_layout(appframe, app)
78+
79+lo.build_elements({"message_pane": build_message_pane,
80+ "user_pane": tkpane.lib.UserPane,
81+ "output_pane": tkpane.lib.OutputDirPane,
82+ "table_pane": build_table_pane,
83+ "button_pane": build_button_pane,
84+ "status_pane": tkpane.lib.StatusProgressPane
85+ })
86+
87+
88+# Start the demo application with the 'OK' button disabled.
89+button_pane = lo.pane("button_pane")
90+button_pane.disable([])
91+
92+# Require a user name and output directory to be entered for the 'OK' button
93+# to be enabled.
94+button_pane.keys_to_enable = ["name", "output_dir"]
95+
96+user_pane = lo.pane("user_pane")
97+user_pane.enable_callbacks = [button_pane.enable]
98+output_pane = lo.pane("output_pane")
99+output_pane.enable_callbacks = [button_pane.enable]
100+output_pane.clear_callbacks = [button_pane.disable]
101+output_pane.disable_callbacks = [button_pane.disable]
102+
103+# Make the buttons change the data and the status bar.
104+status_pane = lo.pane("status_pane")
105+table_pane = lo.pane("table_pane")
106+def ok_click(*args):
107+ # When this is bound, it will receive event arguments, which should be ignored.
108+ status_pane.set_status("OK button clicked.")
109+ status_pane.set_value(50)
110+ table_pane.display_data(ds2_headers, ds2_data)
111+button_pane.set_ok_action(ok_click)
112+
113+def cancel_click(*args):
114+ # When this is bound, it will receive event arguments, which should be ignored.
115+ status_pane.clear([])
116+button_pane.set_cancel_action(cancel_click)
117+
118+# Bind <Enter> and <Esc> to the buttons.
119+root.bind("<Return>", ok_click)
120+root.bind("<Escape>", cancel_click)
121+
122+
123+# Run the application
124+root.mainloop()
diff -r 000000000000 -r 9d80a794577e tkpane/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tkpane/__init__.py Mon Jan 29 21:24:16 2018 -0800
@@ -0,0 +1,1 @@
1+from tkpane import *
diff -r 000000000000 -r 9d80a794577e tkpane/dialog.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tkpane/dialog.py Mon Jan 29 21:24:16 2018 -0800
@@ -0,0 +1,57 @@
1+
2+try:
3+ import Tkinter as tk
4+except:
5+ import tkinter as tk
6+
7+class Dialog(tk.Toplevel):
8+ # From effbot: http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
9+ def __init__(self, parent, title = None):
10+ tk.Toplevel.__init__(self, parent)
11+ self.transient(parent)
12+ if title:
13+ self.title(title)
14+ self.parent = parent
15+ self.result = None
16+ body = tk.Frame(self)
17+ self.initial_focus = self.makebody(body)
18+ body.pack(padx=5, pady=5)
19+ self.buttonbox()
20+ self.grab_set()
21+ if not self.initial_focus:
22+ self.initial_focus = self
23+ self.protocol("WM_DELETE_WINDOW", self.cancel)
24+ self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
25+ parent.winfo_rooty()+50))
26+ self.initial_focus.focus_set()
27+ self.wait_window(self)
28+ def makebody(self, master):
29+ # create dialog body. return widget that should have
30+ # initial focus. this method should be overridden
31+ pass
32+ def buttonbox(self):
33+ # add standard button box. override if you don't want the
34+ # standard buttons
35+ box = tk.Frame(self)
36+ w = tk.Button(box, text="Ok", width=10, command=self.ok, default=tk.ACTIVE)
37+ w.pack(side=tk.LEFT, padx=5, pady=5)
38+ w = tk.Button(box, text="Cancel", width=10, command=self.cancel)
39+ w.pack(side=tk.LEFT, padx=5, pady=5)
40+ self.bind("<Return>", self.ok)
41+ self.bind("<Escape>", self.cancel)
42+ box.pack()
43+ def ok(self, event=None):
44+ if not self.validate():
45+ self.initial_focus.focus_set() # put focus back
46+ return
47+ self.withdraw()
48+ self.update_idletasks()
49+ self.apply()
50+ self.cancel()
51+ def cancel(self, event=None):
52+ self.parent.focus_set()
53+ self.destroy()
54+ def validate(self):
55+ return 1 # override
56+ def apply(self):
57+ pass # override
diff -r 000000000000 -r 9d80a794577e tkpane/lib.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tkpane/lib.py Mon Jan 29 21:24:16 2018 -0800
@@ -0,0 +1,722 @@
1+# panelib.py
2+#
3+# PURPOSE
4+# A collection of TkPane subclasses.
5+#
6+# NOTES
7+# 1.
8+#
9+# AUTHORS
10+# Dreas Nielsen (RDN)
11+#
12+# HISTORY
13+# Date Remarks
14+# ---------- ----------------------------------------------------------------
15+# 2018-01-15 Created. RDN.
16+# 2018-01-26 Added OkCancelPane, StatusProgressPane, and TableDisplayPane. RDN.
17+# 2018-01-27 Added MessagePane. RDN.
18+# 2018-01-28 Added pane styles. RDN.
19+# 2018-01-29 Added OutputFilePane, InputFilePane, and TextPane. RDN.
20+#===============================================================================
21+
22+try:
23+ import Tkinter as tk
24+except:
25+ import tkinter as tk
26+try:
27+ import ttk
28+except:
29+ from tkinter import ttk
30+try:
31+ import tkFileDialog as tk_file
32+except:
33+ from tkinter import filedialog as tk_file
34+try:
35+ import tkFont as tkfont
36+except:
37+ from tkinter import tkfont
38+
39+
40+from tkpane import TkPane
41+
42+
43+
44+#===============================================================================
45+# Pane styles
46+# A style is a set of options for pane (frame) configuration and gridding.
47+#-------------------------------------------------------------------------------
48+panestyles = {}
49+
50+class PaneStyle(object):
51+ # Default spacing inside pane borders should be 18 pixels per the GNOME HIG
52+ # (https://developer.gnome.org/hig/stable/visual-layout.html.en).
53+ # Assuming a 3-pixel padding around interior widgets and that frames (panes)
54+ # will be adjacent, default internal padding for frames should be set to
55+ # (18 - 3)/2 ~= 7 pixels,
56+ # This leaves the border around the outermost widgets in the app window
57+ # smaller than desired. A frame around the entire application can be created
58+ # to add this padding.
59+ """A set of configuration options for frames and for widgets."""
60+ #def __init__(self, stylename, frame_config_dict, frame_grid_dict={}):
61+ def __init__(self, stylename, frame_config_dict={"padx": 7, "pady":7}, frame_grid_dict={}):
62+ self.config = frame_config_dict
63+ self.grid = frame_grid_dict
64+ panestyles[stylename] = self
65+
66+# Default grid styles are used for all of these.
67+PaneStyle("plain")
68+PaneStyle("ridged", {"padx": 7, "pady": 7, "borderwidth": 2, "relief": tk.RIDGE})
69+PaneStyle("grooved", {"padx": 7, "pady": 7, "borderwidth": 2, "relief": tk.GROOVE})
70+PaneStyle("sunken", {"padx": 7, "pady": 7, "borderwidth": 2, "relief": tk.SUNKEN})
71+PaneStyle("statusbar", {"padx": 7, "pady": 2, "borderwidth": 2, "relief": tk.SUNKEN})
72+
73+current_panestyle = "plain"
74+dialog_style = "plain"
75+
76+def frame_config_opts(style=None):
77+ return panestyles[style or current_panestyle].config
78+
79+def frame_grid_opts(style=None):
80+ return panestyles[style or current_panestyle].grid
81+
82+
83+
84+
85+#===============================================================================
86+# Dialog class
87+# Used by some panes. Also usable for other purposes.
88+# Adapted from effbot: http://effbot.org/tkinterbook/tkinter-dialog-windows.htm.
89+#-------------------------------------------------------------------------------
90+
91+class Dialog(tk.Toplevel):
92+ def __init__(self, parent, title = None):
93+ tk.Toplevel.__init__(self, parent, **frame_config_opts(dialog_style))
94+ self.transient(parent)
95+ if title:
96+ self.title(title)
97+ self.parent = parent
98+ self.result = None
99+ body = tk.Frame(self)
100+ self.initial_focus = self.makebody(body)
101+ body.pack(padx=3, pady=3)
102+ self.buttonbox()
103+ self.grab_set()
104+ if not self.initial_focus:
105+ self.initial_focus = self
106+ self.protocol("WM_DELETE_WINDOW", self.cancel)
107+ self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
108+ parent.winfo_rooty()+50))
109+ self.initial_focus.focus_set()
110+ self.wait_window(self)
111+ def makebody(self, master):
112+ # Create the dialog body. Return the widget that should have
113+ # the initial focus. This method should be overridden.
114+ pass
115+ def buttonbox(self):
116+ # Add a standard button box. This method should be overriden
117+ # if no buttons, or some other buttons, are to be shown.
118+ box = tk.Frame(self)
119+ w = tk.Button(box, text="Cancel", width=8, command=self.cancel)
120+ w.pack(side=tk.RIGHT, padx=3, pady=3)
121+ w = tk.Button(box, text="OK", width=8, command=self.ok)
122+ w.pack(side=tk.RIGHT, padx=3, pady=3)
123+ self.bind("<Return>", self.ok)
124+ self.bind("<Escape>", self.cancel)
125+ box.pack()
126+ def ok(self, event=None):
127+ if not self.validate():
128+ self.initial_focus.focus_set()
129+ return
130+ self.withdraw()
131+ self.update_idletasks()
132+ self.apply()
133+ self.cancel()
134+ def cancel(self, event=None):
135+ self.parent.focus_set()
136+ self.destroy()
137+ def validate(self):
138+ # Override this method as necessary.
139+ return 1
140+ def apply(self):
141+ # Override this method as necessary.
142+ pass
143+
144+
145+
146+#===============================================================================
147+# Pane classes
148+#-------------------------------------------------------------------------------
149+
150+class MessagePane(TkPane):
151+ """Pane to display a text message.
152+
153+ This pane does not manage any data.
154+
155+ Overridden methods: none.
156+
157+ Custom methods:
158+ * set_message
159+ """
160+ def __init__(self, parent, message):
161+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
162+ self.msg_label = None
163+ def wrap_mp_msg(event):
164+ self.msg_label.configure(wraplength=event.width - 5)
165+ msgframe = ttk.Frame(master=parent)
166+ self.msg_label = ttk.Label(msgframe, text=message)
167+ self.msg_label.bind("<Configure>", wrap_mp_msg)
168+ self.msg_label.grid(column=0, row=0, sticky=tk.EW, padx=6, pady=6)
169+ msgframe.rowconfigure(0, weight=0)
170+ msgframe.columnconfigure(0, weight=1)
171+ msgframe.grid(row=0, column=0, sticky=tk.EW)
172+ parent.rowconfigure(0, weight=0)
173+ parent.columnconfigure(0, weight=1)
174+ def set_message(self, message):
175+ """Change the message displayed in the pane."""
176+ self.msg_label.configure(text=message)
177+
178+
179+class UserPane(TkPane):
180+ """Pane to display a user's name, and to prompt for a user's name and password.
181+
182+ Data keys managed by this pane are "name" and "password,"
183+
184+ Overridden methods:
185+ * clear_pane
186+ * values
187+ """
188+
189+ class GetUserDialog(Dialog):
190+ def makebody(self, master):
191+ tk.Label(master, text="User name:", width=12, anchor=tk.E).grid(row=0, column=0, sticky=tk.E, padx=3, pady=3)
192+ tk.Label(master, text="Password:", width=12, anchor=tk.E).grid(row=1, column=0, sticky=tk.E, padx=3, pady=3)
193+ self.e1 = tk.Entry(master, width=36)
194+ self.e2 = tk.Entry(master, width=36, show="*")
195+ self.e1.grid(row=0, column=1, sticky=tk.W, padx=3, pady=3)
196+ self.e2.grid(row=1, column=1, sticky=tk.W, padx=3, pady=3)
197+ return self.e1
198+ def validate(self):
199+ return self.e1.get() != u'' and self.e2.get() != u''
200+ def apply(self):
201+ self.result = {u"name": self.e1.get(), u"password": self.e2.get()}
202+
203+ def __init__(self, parent):
204+ TkPane.__init__(self, parent, config_opts=frame_config_opts(), grid_opts=frame_grid_opts())
205+ user_label = tk.Label(self, text='User name:', width=10, anchor=tk.E)
206+ self.user_var = tk.StringVar()
207+ self.user_pw = None
208+ user_display = tk.Entry(self, textvariable=self.user_var)
209+ user_display.config(state='readonly')
210+ user_button = tk.Button(self, text='Change', width=8, command=self.set_user)
211+ user_label.grid(row=0, column=0, padx=6, pady=3, sticky=tk.EW)
212+ user_display.grid(row=0, column=1, padx=6, pady=3, sticky=tk.EW)
213+ user_button.grid(row=1, column=1, padx=6, pady=1, sticky=tk.W)
214+ self.columnconfigure(0, weight=0)
215+ self.columnconfigure(1, weight=1)
216+ self.rowconfigure(1, weight=1)
217+ parent.rowconfigure(0, weight=0)
218+ parent.columnconfigure(0, weight=1)
219+
220+ def values(self):
221+ return { u"name": self.user_var.get(), u"password": self.user_pw }
222+
223+ def clear_pane(self):
224+ self.user_var.set(u'')
225+ self.user_pw = None
226+
227+ def set_user(self):
228+ # Open a dialog box to prompt for the user's name and password.
229+ dlg = self.GetUserDialog(self)
230+ if dlg.result is not None:
231+ self.user_var.set(dlg.result[u"name"])
232+ self.user_pw = dlg.result[u"password"]
233+ self.call_enables()
234+ else:
235+ self.call_clears()
236+ self.call_disables()
237+
238+
239+class OutputDirPane(TkPane):
240+ """ Pane to get and display an output directory.
241+
242+ The data key managed by this pane is "output_dir,"
243+
244+ Overridden methods:
245+ * clear_pane
246+ * disable_pane
247+ * enable_pane
248+ * values
249+ """
250+
251+ def __init__(self, parent):
252+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
253+ dir_label = tk.Label(self, text='Output directory:', anchor=tk.E)
254+ self.dir_var = tk.StringVar()
255+ self.dir_var.trace('w', self.dir_changed)
256+ self.dir_display = tk.Entry(self, textvariable=self.dir_var)
257+ self.dir_button = tk.Button(self, text='Browse', width=8, command=self.set_outputdir)
258+ dir_label.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
259+ self.dir_display.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
260+ self.dir_button.grid(row=1, column=1, padx=3, pady=1, sticky=tk.W)
261+ self.columnconfigure(0, weight=0)
262+ self.columnconfigure(1, weight=1)
263+ self.rowconfigure(1, weight=1)
264+ parent.rowconfigure(0, weight=0)
265+ parent.columnconfigure(0, weight=1)
266+
267+ def values(self):
268+ return { u"output_dir": self.dir_var.get() }
269+
270+ def clear_pane(self):
271+ self.dir_var.set(u'')
272+
273+ def enable_pane(self):
274+ self.dir_display.configure(state='!readonly')
275+ self.dir_button.configure(state=['!disabled'])
276+
277+ def disable_pane(self):
278+ self.dir_display.configure(state='readonly')
279+ self.dir_button.configure(state=['disabled'])
280+
281+ def dir_changed(self, var, index, mode):
282+ if mode == "w":
283+ self.validate()
284+
285+ def validate(self):
286+ import os.path
287+ if os.path.isdir(self.dir_var.get() or ""):
288+ self.call_enables()
289+ else:
290+ self.call_disables()
291+
292+ def set_outputdir(self):
293+ self.dir_var.set(u'')
294+ dir = tk_file.askdirectory()
295+ if dir:
296+ self.dir_var.set(dir)
297+ else:
298+ self.call_clears()
299+ self.validate()
300+
301+
302+class OutputFilePane(TkPane):
303+ """ Pane to get and display an output filename.
304+
305+ The data key managed by this pane is "output_filename,"
306+
307+ Overridden methods:
308+ * clear_pane
309+ * disable_pane
310+ * enable_pane
311+ * values
312+ """
313+
314+ def __init__(self, parent, optiondict):
315+ """:param optiondict: a dictionary of option names and values for the
316+ Tkinter 'asksaveasfilename' method.
317+ """
318+ self.optiondict = optiondict
319+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
320+ dir_label = tk.Label(self, text='Output directory:', anchor=tk.E)
321+ self.file_var = tk.StringVar()
322+ self.file_var.trace('w', self.file_changed)
323+ self.file_display = tk.Entry(self, textvariable=self.file_var)
324+ self.dir_button = tk.Button(self, text='Browse', width=8, command=self.set_outputfile)
325+ dir_label.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
326+ self.file_display.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
327+ self.dir_button.grid(row=1, column=1, padx=3, pady=1, sticky=tk.W)
328+ self.columnconfigure(0, weight=0)
329+ self.columnconfigure(1, weight=1)
330+ self.rowconfigure(1, weight=1)
331+ parent.rowconfigure(0, weight=0)
332+ parent.columnconfigure(0, weight=1)
333+
334+ def values(self):
335+ return { u"output_filename": self.file_var.get() }
336+
337+ def clear_pane(self):
338+ self.file_var.set(u'')
339+
340+ def enable_pane(self):
341+ self.file_display.configure(state='!readonly')
342+ self.dir_button.configure(state=['!disabled'])
343+
344+ def disable_pane(self):
345+ self.file_display.configure(state='readonly')
346+ self.dir_button.configure(state=['disabled'])
347+
348+ def file_changed(self, var, index, mode):
349+ if mode == "w":
350+ self.validate()
351+
352+ def validate(self):
353+ import os.path
354+ fn = self.file_var.get() or ""
355+ if os.path.isdir(os.path.dirname(fn)):
356+ self.call_enables()
357+ else:
358+ self.call_disables()
359+
360+ def set_outputfile(self):
361+ self.file_var.set(u'')
362+ fn = tk_file.asksaveasfilename(**self.optiondict)
363+ if fn:
364+ self.file_var.set(dir)
365+ else:
366+ self.call_clears()
367+ self.validate()
368+
369+
370+class InputFilePane(TkPane):
371+ """ Pane to get and display an input filename.
372+
373+ The data key managed by this pane is "input_filename,"
374+
375+ Overridden methods:
376+ * clear_pane
377+ * disable_pane
378+ * enable_pane
379+ * values
380+ """
381+
382+ def __init__(self, parent, optiondict):
383+ """:param optiondict: a dictionary of option names and values for the
384+ Tkinter 'askopenfilename' method.
385+ """
386+ self.optiondict = optiondict
387+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
388+ dir_label = tk.Label(self, text='Output directory:', anchor=tk.E)
389+ self.file_var = tk.StringVar()
390+ self.file_var.trace('w', self.file_changed)
391+ self.file_display = tk.Entry(self, textvariable=self.file_var)
392+ self.dir_button = tk.Button(self, text='Browse', width=8, command=self.set_inputfile)
393+ dir_label.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
394+ self.file_display.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
395+ self.dir_button.grid(row=1, column=1, padx=3, pady=1, sticky=tk.W)
396+ self.columnconfigure(0, weight=0)
397+ self.columnconfigure(1, weight=1)
398+ self.rowconfigure(1, weight=1)
399+ parent.rowconfigure(0, weight=0)
400+ parent.columnconfigure(0, weight=1)
401+
402+ def values(self):
403+ return { u"input_filename": self.file_var.get() }
404+
405+ def clear_pane(self):
406+ self.file_var.set(u'')
407+
408+ def enable_pane(self):
409+ self.file_display.configure(state='!readonly')
410+ self.dir_button.configure(state=['!disabled'])
411+
412+ def disable_pane(self):
413+ self.file_display.configure(state='readonly')
414+ self.dir_button.configure(state=['disabled'])
415+
416+ def file_changed(self, var, index, mode):
417+ if mode == "w":
418+ self.validate()
419+
420+ def validate(self):
421+ import os.path
422+ if os.path.isfile(self.file_var.get() or ""):
423+ self.call_enables()
424+ else:
425+ self.call_disables()
426+
427+ def set_inputfile(self):
428+ self.file_var.set(u'')
429+ dir = tk_file.askopenfilename(**self.optiondict)
430+ if dir:
431+ self.file_var.set(dir)
432+ else:
433+ self.call_clears()
434+ self.validate()
435+
436+
437+class TextPane(TkPane):
438+ """Pane to display a Tkinter Text widget.
439+
440+ Because of the large number of uses of the Text widget, this pane
441+ provides direct access to the Text widget via the 'textwidget' method.
442+ To simplify use, this pane also provides direct methods for appending to,
443+ replacing, and clearing the contents of the Text widget.
444+ The custom methods 'set_status' and 'clear_status' allow a TextPane to
445+ be used as a status_reporter callback for any other type of pane.
446+
447+ Data keys managed by this pane: "text".
448+
449+ Overridden methods:
450+ * values
451+ * clear_pane
452+ * enable_pane
453+ * disable_pane
454+
455+ Custom methods:
456+ * textwidget
457+ * replace_all
458+ * append
459+ * set_status
460+ * clear_status
461+ """
462+ def __init__(self, parent, optiondict, initial_text=None):
463+ """:param optiondict: Initial options for the Text widget, as a dictionary."""
464+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
465+ self.textwidget = tk.Text(self, **optiondict)
466+ if initial_text is not None:
467+ self.replace_all(initial_text)
468+ self.textwidget.grid(row=0, column=0, padx=3, pady=3, sticky=tk.NSEW)
469+ self.rowconfigure(0, weight=1)
470+ self.columnconfigure(0, weight=1)
471+ parent.rowconfigure(0, weight=1)
472+ parent.columnconfigure(0, weight=1)
473+
474+ def textwidget(self):
475+ """Return the text widget object, to allow direct manipulation."""
476+ return self.textwidget
477+
478+ def values(self):
479+ return {"text": self.textwidget.get("1.0", tk.END)}
480+
481+ def clear_pane(self):
482+ self.textwidget.delete("1.0", tk.END)
483+
484+ def enable_pane(self):
485+ self.textwidget.configure(state=tk.NORMAL)
486+
487+ def disable_pane(self):
488+ self.textwidget.configure(state=tk.DISABLED)
489+
490+ def replace_all(self, new_contents):
491+ self.clear_pane()
492+ self.textwidget.insert(tk.END, new_contents)
493+
494+ def append(self, more_text):
495+ """Inserts the given text at the end of the Text widget's contents."""
496+ self.textwidget.insert(tk.END, more_text)
497+
498+ def set_status(self, status_msg):
499+ """Inserts the status message at the end of the Text widget's contents."""
500+ self.append(status_msg)
501+
502+ def clear_status(self):
503+ """Clear the entire widget."""
504+ self.clear()
505+
506+
507+
508+class StatusProgressPane(TkPane):
509+ """Pane to display a status bar and progress bar.
510+
511+ There are no data keys managed by this pane.
512+
513+ Overridden methods:
514+ * clear_pane
515+ * values
516+
517+ Custom methods:
518+ * set_status(message): Sets the status bar message.
519+ * set_determinate(): Sets the progress bar to determinate mode.
520+ * set_indeterminate(): Sets the progress bar to indeterminate mode.
521+ * set_value(value): Sets a determinate progress bar to the specified value (0-100).
522+ * start(): Starts an indefinite progress bar.
523+ * stop(): Stops an indefinite progress bar.
524+ """
525+ def __init__(self, parent):
526+ TkPane.__init__(self, parent, frame_config_opts("statusbar"), frame_grid_opts("statusbar"))
527+ self.status_msg = tk.StringVar()
528+ self.status_msg.set('')
529+ self.ctrvalue = tk.DoubleVar()
530+ self.ctrvalue.set(0)
531+ statusbar = ttk.Label(parent, text='', textvariable=self.status_msg, relief=tk.RIDGE, anchor=tk.W)
532+ self.progressmode = 'determinate'
533+ ctrprogress = ttk.Progressbar(parent, mode=self.progressmode, maximum=100,
534+ orient='horizontal', length=150, variable=self.ctrvalue)
535+ statusbar.grid(row=0, column=0, sticky=tk.EW)
536+ ctrprogress.grid(row=0, column=1, sticky=tk.EW)
537+ self.columnconfigure(0, weight=1)
538+ self.columnconfigure(1, weight=0)
539+ self.rowconfigure(1, weight=1)
540+ parent.rowconfigure(0, weight=0)
541+ parent.columnconfigure(0, weight=1)
542+
543+ def values(self):
544+ return {'status': self.satus_msg.get(), 'progress': self.ctrvalue.get()}
545+
546+ def clear_pane(self):
547+ """Clears the status bar and progress bar."""
548+ self.status_msg.set('')
549+ self.ctrvalue.set(0)
550+ self.stop()
551+
552+ def set_status(self, message):
553+ """Sets the status bar message."""
554+ self.status_msg.set(message)
555+
556+ def set_determinate(self):
557+ """Sets the progress bar to definite mode."""
558+ self.progressmode = "indeterminate"
559+ self.ctrprogress.configure(mode=self.progressmode)
560+
561+ def set_indeterminate(self):
562+ """Sets the progress bar to indefinite mode."""
563+ self.progressmode = "indeterminate"
564+ self.ctrprogress.configure(mode=self.progressmode)
565+
566+ def set_value(self, value):
567+ """Sets the progress bar indicator.
568+
569+ The 'value' argument should be between 0 and 100, and will be trimmed to
570+ this range if it is not.
571+ """
572+ if self.progressmode == "determinate":
573+ self.ctrvalue.set(max(min(float(value), 100.0), 0.0))
574+
575+ def start(self):
576+ """Start an indefinite progress bar running."""
577+ if self.progressmode == "indeterminate":
578+ self.ctrprogress.start()
579+
580+ def stop(self):
581+ """Stop an indefinite progress bar."""
582+ if self.progressmode == "determinate":
583+ pass
584+
585+
586+class TableDisplayPane(TkPane):
587+ """Pane to display a specified data table."""
588+ def __init__(self, parent, message, column_headers, rowset):
589+ """Create the table display.
590+
591+ :param message: A message to display above the data table.
592+ :param column_headers: A list of the column names for the data table.
593+ :param rowset: An iterable that yields rows for the data table.
594+
595+ There are no data keys managed by this pane.
596+
597+ Overridden methods:
598+ * clear_pane
599+
600+ Custom methods:
601+ * display_data
602+ """
603+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
604+ # Message frame and control.
605+ self.msg_label = None
606+ def wrap_msg(event):
607+ self.msg_label.configure(wraplength=event.width - 5)
608+ if message is not None:
609+ msgframe = ttk.Frame(master=self, padding="3 3 3 3")
610+ self.msg_label = ttk.Label(msgframe, text=message)
611+ self.msg_label.bind("<Configure>", wrap_msg)
612+ self.msg_label.grid(column=0, row=0, sticky=tk.EW)
613+ msgframe.rowconfigure(0, weight=0)
614+ msgframe.columnconfigure(0, weight=1)
615+ msgframe.grid(row=0, column=0, pady=3, sticky=tk.EW)
616+ tableframe = ttk.Frame(master=self, padding="3 3 3 3")
617+ # Create and configure the Treeview table widget and scrollbars.
618+ self.tbl = ttk.Treeview(tableframe, columns=column_headers, selectmode="none", show="headings")
619+ ysb = ttk.Scrollbar(tableframe, orient='vertical', command=self.tbl.yview)
620+ xsb = ttk.Scrollbar(tableframe, orient='horizontal', command=self.tbl.xview)
621+ self.tbl.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
622+ tableframe.grid(column=0, row=1 if message is not None else 0, sticky=tk.NSEW)
623+ self.tbl.grid(column=0, row=0, sticky=tk.NSEW)
624+ ysb.grid(column=1, row=0, sticky=tk.NS)
625+ xsb.grid(column=0, row=1, sticky=tk.EW)
626+ tableframe.columnconfigure(0, weight=1)
627+ tableframe.rowconfigure(0, weight=1)
628+ # Display the data
629+ self.display_data(column_headers, rowset)
630+ # Make the table resizeable
631+ if message is not None:
632+ self.rowconfigure(0, weight=0)
633+ self.rowconfigure(1, weight=1)
634+ else:
635+ self.rowconfigure(0, weight=1)
636+ parent.rowconfigure(0, weight=1)
637+ parent.columnconfigure(0, weight=1)
638+ def clear_pane(self):
639+ for item in self.tbl.get_children():
640+ self.tbl.delete(item)
641+ self.tbl.configure(columns=[""])
642+ def display_data(self, column_headers, rowset):
643+ """Display a new data set on the pane.
644+
645+ :param column_headers: A list of strings for the headers of the data columns.
646+ :param rowset: A list of lists of data values to display. The outer list is rows, the inner lists are columns.
647+ """
648+ self.clear_pane()
649+ # Reconfigure TreeView columns
650+ self.tbl.configure(columns=column_headers)
651+ # Get the data to display.
652+ nrows = range(len(rowset))
653+ ncols = range(len(column_headers))
654+ hdrwidths = [len(column_headers[j]) for j in ncols]
655+ datawidthtbl = [[len(rowset[i][j] if isinstance(rowset[i][j], basestring) else unicode(rowset[i][j])) for i in nrows] for j in ncols]
656+ datawidths = [max(cwidths) for cwidths in datawidthtbl]
657+ colwidths = [max(hdrwidths[i], datawidths[i]) for i in ncols]
658+ # Set the font.
659+ ff = tkfont.nametofont("TkFixedFont")
660+ tblstyle = ttk.Style()
661+ tblstyle.configure('tblstyle', font=ff)
662+ self.tbl.configure()["style"] = tblstyle
663+ charpixels = int(1.3 * ff.measure(u"0"))
664+ pixwidths = [charpixels * col for col in colwidths]
665+ # Fill the Treeview table widget with data
666+ for i in range(len(column_headers)):
667+ self.tbl.column(column_headers[i], width=pixwidths[i])
668+ self.tbl.heading(column_headers[i], text=column_headers[i])
669+ for i, row in enumerate(rowset):
670+ enc_row = [c if c is not None else '' for c in row]
671+ self.tbl.insert(parent='', index='end', iid=str(i), values=enc_row)
672+
673+
674+class OkCancelPane(TkPane):
675+ """Pane to display Ok and Cancel buttons.
676+
677+ There are no data keys managed by this pane.
678+
679+ Overridden methods:
680+ * disable_pane
681+ * enable_pane
682+
683+ Custom methods:
684+ * set_cancel_action
685+ * set_ok_action
686+ * ok
687+ * cancal
688+ """
689+ def __init__(self, parent, ok_action, cancel_action):
690+ def do_nothing():
691+ pass
692+ self.ok_action = ok_action if ok_action is not None else do_nothing
693+ self.cancel_action = cancel_action if cancel_action is not None else do_nothing
694+ TkPane.__init__(self, parent, frame_config_opts(), frame_grid_opts())
695+ self.cancel_btn = ttk.Button(self, text="Cancel", command=self.cancel_action)
696+ self.cancel_btn.grid(row=0, column=1, padx=3, sticky=tk.E)
697+ self.ok_btn = ttk.Button(self, text="OK", command=self.ok_action)
698+ self.ok_btn.grid(row=0, column=0, padx=3, sticky=tk.E)
699+ self.columnconfigure(0, weight=1)
700+ self.columnconfigure(1, weight=0)
701+ self.rowconfigure(0, weight=1)
702+ parent.rowconfigure(0, weight=0)
703+ parent.columnconfigure(0, weight=1)
704+ def enable_pane(self):
705+ self.ok_btn.state(['!disabled'])
706+ def disable_pane(self):
707+ self.ok_btn.state(['disabled'])
708+ def set_ok_action(self, ok_action):
709+ """Specify the callback function to be called when the 'OK' button is clicked."""
710+ self.ok_action = ok_action if ok_action is not None else do_nothing
711+ self.ok_btn.configure(command=self.ok_action)
712+ def set_cancel_action(self, cancel_action):
713+ """Specify the callback function to be called when the 'Cancel' button is clicked."""
714+ self.cancel_action = cancel_action if cancel_action is not None else do_nothing
715+ self.cancel_btn.configure(command=self.cancel_action)
716+ def ok(self):
717+ """Trigger this pane's 'OK' action."""
718+ self.ok_action()
719+ def cancel(self):
720+ """Trigger this pane's 'Cancel' action."""
721+ self.cancel_action()
722+
Show on old repository browser