• R/O
  • SSH

tkpane: Commit

Default repository for tkpane.py.


Commit MetaInfo

Revisãof082099bdfa750688805093c93370cf2d4510254 (tree)
Hora2018-03-15 13:16:06
AutorDreas Nielsen <dreas.nielsen@gmai...>
CommiterDreas Nielsen

Mensagem de Log

Added initialization of datadict for custom panes with default values. Added init parameters 'required' and 'blank_is_valid' to the TextPane class. Added function 'run_validity_callbacks()'.

Mudança Sumário

Diff

diff -r 4fa1db37dc30 -r f082099bdfa7 CHANGELOG.rst
--- a/CHANGELOG.rst Wed Mar 14 11:44:05 2018 -0700
+++ b/CHANGELOG.rst Wed Mar 14 21:16:06 2018 -0700
@@ -4,7 +4,7 @@
44 ========== ======= =================================================================================
55 Date Version Revision
66 ========== ======= =================================================================================
7-2018-03-14 0.29.0 Modified EntryPane; added parameters 'required' and 'blank_is_valid'.
7+2018-03-14 0.30.0 Modified EntryPane; added parameters 'required' and 'blank_is_valid'. Modified RadiobuttonPane to allow either orientation. Initialized datadict in panes with default values. Added function 'run_validity_callbacks()'.
88 2018-03-12 0.27.0 Modified title of UserPane in lib.py.
99 2018-03-10 0.26.0 Added 'set_entry_validator()' method to EntryPane in lib.py. Changed invalid colors to be configurable at module level. Changed use of ttk or tk widgets to be configurable at the module level.
1010 2018-03-01 0.22.0 Added 'requires_datavalue() and 'clear_on_disable()' methods. Improved support for 'on_save_change' callback list. Added 'set_filename_validator()' method to InputFilePane.
diff -r 4fa1db37dc30 -r f082099bdfa7 doc/source/conf.py
--- a/doc/source/conf.py Wed Mar 14 11:44:05 2018 -0700
+++ b/doc/source/conf.py Wed Mar 14 21:16:06 2018 -0700
@@ -55,9 +55,9 @@
5555 # built documents.
5656 #
5757 # The short X.Y version.
58-version = u'0.29.0'
58+version = u'0.30.0'
5959 # The full version, including alpha/beta/rc tags.
60-release = u'0.29.0'
60+release = u'0.30.0'
6161
6262 # The language for content autogenerated by Sphinx. Refer to documentation
6363 # for a list of supported languages.
diff -r 4fa1db37dc30 -r f082099bdfa7 doc/source/index.rst
--- a/doc/source/index.rst Wed Mar 14 11:44:05 2018 -0700
+++ b/doc/source/index.rst Wed Mar 14 21:16:06 2018 -0700
@@ -714,6 +714,8 @@
714714 Several functions in the ``tkpane`` package simplify the initialization
715715 of a UI or the integration of the ``tkpane`` and ``tklayout`` libraries.
716716
717+.. autofunction:: run_validity_callbacks
718+
717719 .. autofunction:: enable_or_disable_all
718720
719721 .. autofunction:: layout_panes
@@ -918,10 +920,7 @@
918920 """Update the pane's data dictionary with data from the Entry widget."""
919921 text = self.entry_var.get()
920922 if is_valid:
921- if text == "":
922- self.clear_own()
923- else:
924- self.datadict[self.datakeyname] = text
923+ self.datadict[self.datakeyname] = text
925924 else:
926925 self.clear_own()
927926
@@ -1231,7 +1230,7 @@
12311230 .. note::
12321231
12331232 The 'invalid' background color for themed (ttk) widgets is not
1234- getting set to light red on Windows when native widgets are being
1233+ set to light red on Windows when native widgets are being
12351234 used (as in the default theme).
12361235
12371236 2. Every pane that manages data must have a dictionary key (or keys)
@@ -1317,7 +1316,7 @@
13171316 ================================================================
13181317
13191318 Elizabeth Shea
1320- Conceptualization of pane interactions, radio button panes, original
1319+ Conceptualization of pane interactions, radio button pane, original
13211320 versions of ``UserPane`` and ``OutputDirPane``, and rigorous
13221321 testing.
13231322
diff -r 4fa1db37dc30 -r f082099bdfa7 setup.py
--- a/setup.py Wed Mar 14 11:44:05 2018 -0700
+++ b/setup.py Wed Mar 14 21:16:06 2018 -0700
@@ -2,7 +2,7 @@
22
33 setup(name='tkpane',
44 packages=['tkpane'],
5- version='0.29.0',
5+ version='0.30.0',
66 description="Encapsulates Tkinter UI elements in 'panes' that can be combined into an overall UI, integrating them by specifying callback functions and data keys.",
77 author='Dreas Nielsen',
88 author_email='dreas.nielsen@gmail.com',
diff -r 4fa1db37dc30 -r f082099bdfa7 test/test_checkbox.py
--- a/test/test_checkbox.py Wed Mar 14 11:44:05 2018 -0700
+++ b/test/test_checkbox.py Wed Mar 14 21:16:06 2018 -0700
@@ -87,8 +87,11 @@
8787 button1.can_use(entry2)
8888
8989
90+# Propagate initial values to dependent panes.
91+tkpane.run_validity_callbacks([entry1, cb1, cb2, cb3, entry2])
92+
9093 # Ensure panes are initially enabled or disabled as appropriate.
91-tkpane.en_or_dis_able_all([cb1, cb2, cb3, entry2, button1])
94+tkpane.enable_or_disable_all([cb1, cb2, cb3, entry2, button1])
9295
9396 # Make the checkboxes report their status changes to the text pane.
9497 cb1.status_reporter = text_pane
@@ -112,5 +115,7 @@
112115
113116
114117
118+
119+
115120 # Run the application
116121 root.mainloop()
diff -r 4fa1db37dc30 -r f082099bdfa7 test/test_radiobutton.py
--- a/test/test_radiobutton.py Wed Mar 14 11:44:05 2018 -0700
+++ b/test/test_radiobutton.py Wed Mar 14 21:16:06 2018 -0700
@@ -1,113 +1,116 @@
1-#!/usr/bin/python
2-# test_radiobutton.py
3-#
4-# PURPOSE
5-# Test the RadiobuttonPane.
6-#
7-# NOTES
8-# 1.
9-#
10-# AUTHOR
11-# Elizabeth Shea
12-#
13-# HISTORY
14-# Date Remarks
15-# ---------- -----------------------------------------------------------
16-# 2018-03-13 Created. ES.
17-# =========================================================================
18-
19-try:
20- import Tkinter as tk
21-except:
22- import tkinter as tk
23-
24-import tkpane
25-import tkpane.lib
26-import tklayout
27-
28-# Add a method to the AppLayout class to get a pane: the first child of
29-# a frame's widgets.
30-def layout_pane(self, pane_name):
31- return self.frame_widgets(pane_name)[0]
32-tklayout.AppLayout.pane = layout_pane
33-
34-
35-lo = tklayout.AppLayout()
36-# Use an empty pane to push buttons right.
37-buttons = lo.row_elements(["empty_pane", "button1", "button2"], column_weights=[1,0,0])
38-app = lo.column_elements(["entry1", "entry2", "rb1", "text_pane", buttons], row_weights=[0,0,1,0])
39-
40-root = tk.Tk()
41-root.title("Testing of Radiobutton")
42-
43-appframe = tk.Frame(root, padx=11, pady=11)
44-appframe.pack(expand=True, fill=tk.BOTH)
45-
46-lo.create_layout(appframe, app)
47-
48-# Use the pane class constructors to populate each UI element in the layout.
49-# Panes from the library are used; no custom panes are created for this demo.
50-lo.build_elements({
51- "entry1": lambda p: tkpane.lib.EntryPane(p, "entry1", "Entry 1:", "entry1"),
52- "entry2": lambda p: tkpane.lib.EntryPane(p, "entry2", "Details:", "entry2"),
53- "text_pane": tkpane.lib.TextPane
54- })
55-tkpane.lib.current_panestyle = "closex"
56-lo.build_elements({
57- "button1": lambda p: tkpane.lib.ButtonPane(p, "Show data"),
58- "button2": lambda p: tkpane.lib.ButtonPane(p, "Clear")
59- })
60-tkpane.lib.current_panestyle = "closey"
61-lo.build_elements({
62- "rb1": lambda p: tkpane.lib.RadiobuttonPane(p, "rb1", "Radiobutton1", [("Option1",1), ("Option2",2), ("Option3", 3)], "Option1")
63- })
64-
65-
66-
67-# Get references to the actual pane objects so that they can be customized.
68-entry1 = lo.pane("entry1")
69-entry2 = lo.pane("entry2")
70-text_pane = lo.pane("text_pane")
71-rb1 = lo.pane("rb1")
72-#rb1.required = True
73-button1 = lo.pane("button1")
74-button2 = lo.pane("button2")
75-
76-# Create dependencies.
77-rb1.requires(entry1, clear_on_disable=True)
78-
79-
80-button1.can_use(rb1)
81-button1.can_use(entry2)
82-
83-
84-# Ensure panes are initially enabled or disabled as appropriate.
85-tkpane.en_or_dis_able_all([rb1, button1])
86-
87-# Make the radiobutton report status changes to the text pane.
88-rb1.status_reporter = text_pane
89-
90-entry2.status_reporter=text_pane
91-
92-# Disable user entry into the text pane; it is used only as a status log.
93-text_pane.disable()
94-
95-# Make button1 report its data to the text pane.
96-def b1_click(*args):
97- # This does not work
98- text_pane.set_status("\nButton's data:\n%s\n" % str(button1.all_data()))
99-
100- # This works
101- #text_pane.set_status("\nButton's data:\n%s\n" % str(rb1.all_data()))
102-
103-button1.set_button_action(b1_click)
104-
105-# Button2 clears the entry.
106-def b2_click(*args):
107- entry1.clear()
108-button2.set_button_action(b2_click)
109-
110-
111-
112-# Run the application
113-root.mainloop()
1+#!/usr/bin/python
2+# test_radiobutton.py
3+#
4+# PURPOSE
5+# Test the RadiobuttonPane.
6+#
7+# NOTES
8+# 1.
9+#
10+# AUTHOR
11+# Elizabeth Shea
12+#
13+# HISTORY
14+# Date Remarks
15+# ---------- -----------------------------------------------------------
16+# 2018-03-13 Created. ES.
17+# =========================================================================
18+
19+try:
20+ import Tkinter as tk
21+except:
22+ import tkinter as tk
23+
24+import tkpane
25+import tkpane.lib
26+import tklayout
27+
28+# Add a method to the AppLayout class to get a pane: the first child of
29+# a frame's widgets.
30+def layout_pane(self, pane_name):
31+ return self.frame_widgets(pane_name)[0]
32+tklayout.AppLayout.pane = layout_pane
33+
34+
35+lo = tklayout.AppLayout()
36+# Use an empty pane to push buttons right.
37+buttons = lo.row_elements(["empty_pane", "button1", "button2"], column_weights=[1,0,0])
38+app = lo.column_elements(["entry1", "entry2", "rb1", "text_pane", buttons], row_weights=[0,0,1,0])
39+
40+root = tk.Tk()
41+root.title("Testing of Radiobutton")
42+
43+appframe = tk.Frame(root, padx=11, pady=11)
44+appframe.pack(expand=True, fill=tk.BOTH)
45+
46+lo.create_layout(appframe, app)
47+
48+# Use the pane class constructors to populate each UI element in the layout.
49+# Panes from the library are used; no custom panes are created for this demo.
50+lo.build_elements({
51+ "entry1": lambda p: tkpane.lib.EntryPane(p, "entry1", "Entry 1:", "entry1"),
52+ "entry2": lambda p: tkpane.lib.EntryPane(p, "entry2", "Details:", "entry2"),
53+ "text_pane": tkpane.lib.TextPane
54+ })
55+tkpane.lib.current_panestyle = "closex"
56+lo.build_elements({
57+ "button1": lambda p: tkpane.lib.ButtonPane(p, "Show data"),
58+ "button2": lambda p: tkpane.lib.ButtonPane(p, "Clear")
59+ })
60+tkpane.lib.current_panestyle = "closey"
61+lo.build_elements({
62+ "rb1": lambda p: tkpane.lib.RadiobuttonPane(p, "rb1", "Radiobutton1", [("Option1",1), ("Option2",2), ("Option3", 3)], "Option1")
63+ })
64+
65+
66+
67+# Get references to the actual pane objects so that they can be customized.
68+entry1 = lo.pane("entry1")
69+entry2 = lo.pane("entry2")
70+text_pane = lo.pane("text_pane")
71+rb1 = lo.pane("rb1")
72+#rb1.required = True
73+button1 = lo.pane("button1")
74+button2 = lo.pane("button2")
75+
76+# Create dependencies.
77+rb1.requires(entry1, clear_on_disable=True)
78+
79+
80+button1.can_use(rb1)
81+button1.can_use(entry2)
82+
83+
84+# Propagate initial values to dependent panes.
85+tkpane.run_validity_callbacks([entry1, rb1, entry2])
86+
87+# Ensure panes are initially enabled or disabled as appropriate.
88+tkpane.en_or_dis_able_all([rb1, button1])
89+
90+# Make the radiobutton report status changes to the text pane.
91+rb1.status_reporter = text_pane
92+
93+entry2.status_reporter=text_pane
94+
95+# Disable user entry into the text pane; it is used only as a status log.
96+text_pane.disable()
97+
98+# Make button1 report its data to the text pane.
99+def b1_click(*args):
100+ # This does not work
101+ text_pane.set_status("\nButton's data:\n%s\n" % str(button1.all_data()))
102+
103+ # This works
104+ #text_pane.set_status("\nButton's data:\n%s\n" % str(rb1.all_data()))
105+
106+button1.set_button_action(b1_click)
107+
108+# Button2 clears the entry.
109+def b2_click(*args):
110+ entry1.clear()
111+button2.set_button_action(b2_click)
112+
113+
114+
115+# Run the application
116+root.mainloop()
diff -r 4fa1db37dc30 -r f082099bdfa7 tkpane/lib.py
--- a/tkpane/lib.py Wed Mar 14 11:44:05 2018 -0700
+++ b/tkpane/lib.py Wed Mar 14 21:16:06 2018 -0700
@@ -24,7 +24,7 @@
2424 for creation of other custom pane classes.
2525 """
2626
27-__version__ = "0.15.0"
27+__version__ = "0.16.0"
2828
2929
3030 try:
@@ -390,7 +390,7 @@
390390 def entry_widgets(self):
391391 return [self.checkbox]
392392
393- def valid_data(self, widget):
393+ def valid_data(self, widget=None):
394394 """Returns an indication of whether the checkbox is in a valid state."""
395395 if self.valid_state is None:
396396 return True
@@ -1128,7 +1128,7 @@
11281128 def entry_widgets(self):
11291129 return [self.listbox]
11301130
1131- def valid_data(self, widget):
1131+ def valid_data(self, widget=None):
11321132 """Returns an indication of whether the listbox is both required and has at least one selection."""
11331133 if self.required:
11341134 selected = self.listbox.curselection()
@@ -1682,15 +1682,13 @@
16821682 :param pane_name: The name to be used to identify this pane in status messages.
16831683 :param prompt: The text associated with the set of radiobuttons.
16841684 :param option_list: List of radiobutton options, consisting of tuples in the format: (label, value).
1685- :param default_option: The label of the default option, e.g., "radioopt1" for ("radioopt", 1). If the default
1686- option is not actually present on the option list (or if no default option is specified), the default value is
1687- set to the empty string "".
1688- :param orient_vertical: Whether the radio button group should be oriented horizontally or vertically. Default is True (vertical)
1685+ :param default_option: The label of the default option, e.g., "radioopt1" for ("radioopt", 1). If the default option is not actually present on the option list (or if no default option is specified), the default value is set to the empty string "".
1686+ :param orient_vertical: Whether the radio button group should be oriented horizontally or vertically. Default is True (vertical)
16891687 :param button_action: A callback to perform an action when a value is selected.
16901688 :param key_name: The name to be used with the internal data dictionary to identify the entry data; use to avoid name conflicts with other RadiobuttonPane panes on the same UI (optional).
16911689 :param config_opts: A dictionary of configuration options for the Radiobutton widget
16921690
1693- Data keys managed by this pane "radio" or the key name specified during initialization.
1691+ Data keys managed by this pane: "radio" or the key name specified during initialization.
16941692
16951693 Name used by this pane: user-defined on initialization.
16961694
@@ -1709,8 +1707,6 @@
17091707 * set_key
17101708 * set_button_action
17111709 * do_button_action
1712-
1713-
17141710 """
17151711
17161712 def __init__(self, parent, pane_name, prompt, option_list, default_option=None, orient_vertical=True, button_action=None, key_name=None, config_opts=None):
@@ -1744,7 +1740,7 @@
17441740 if config_opts is not None:
17451741 self.radio_btn.configure(**config_opts)
17461742 if orient_vertical:
1747- radio_btn.grid(row=i, column=1, padx=3, pady=3, sticky=tk.EW)
1743+ radio_btn.grid(row=i, column=1, padx=3, pady=1, sticky=tk.EW)
17481744 self.rowconfigure(i, weight=1)
17491745 else:
17501746 radio_btn.grid(row=0, column=i+1, padx=3, pady=3, sticky=tk.EW)
@@ -1767,14 +1763,13 @@
17671763 return radio != ""
17681764 return True
17691765
1770-
17711766 def save_data(self, is_valid, entry_widget=None):
17721767 """Update the pane's data dictionary with data from the Radiobutton widget."""
17731768 state = self.radio_var.get()
17741769 self.datadict[self.datakeyname] = state
17751770
17761771 def clear_pane(self):
1777- self.radio_var.set(self.default_item if self.default_item else u"")
1772+ self.radio_var.set(self.default_item if self.default_item else u"")
17781773
17791774 def enable_pane(self):
17801775 self._enablewidgets(self.winfo_children())
@@ -1865,6 +1860,7 @@
18651860 self.datakeylist = [self.datakeyname]
18661861 self.entry_var = tk.DoubleVar()
18671862 self.entry_var.set(init_value)
1863+ self.datadict[self.datakeyname] = self.entry_var.get()
18681864 if tkpane.use_ttk:
18691865 self.entrywidget = ttk.Scale(self, variable=self.entry_var, length=length, orient=orientation, from_=min_value, to=max_value, value=init_value)
18701866 self.widget_type = "ttk"
@@ -1967,6 +1963,7 @@
19671963 self.datakeylist = [self.datakeyname]
19681964 self.entry_var = tk.DoubleVar()
19691965 self.entry_var.set(init_value)
1966+ self.datadict[self.datakeyname] = self.entry_var.get()
19701967 if tkpane.use_ttk:
19711968 self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
19721969 self.entrywidget = ttk.Scale(self, variable=self.entry_var, orient=tk.HORIZONTAL, from_=min_value, to=max_value, value=init_value)
@@ -2091,6 +2088,8 @@
20912088 self.datakeyname = "spinbox" if key_name is None else key_name
20922089 self.datakeylist = [self.datakeyname]
20932090 self.entry_var = tk.StringVar()
2091+ self.entry_var.set(min_value)
2092+ self.datadict[self.datakeyname] = self.entry_var.get()
20942093 if tkpane.use_ttk:
20952094 self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
20962095 try:
@@ -2625,13 +2624,15 @@
26252624 * set_status
26262625 * clear_status
26272626 * set_key
2627+ * set_entry_validator
26282628 """
26292629
2630- def __init__(self, parent, key_name=None, optiondict=None, initial_text=None):
2630+ def __init__(self, parent, key_name=None, optiondict=None, initial_text=None, required=False, blank_is_valid=False):
26312631 tkpane.TkPane.__init__(self, parent, "Text", frame_config_opts(), frame_grid_opts())
26322632 opts = {} if optiondict is None else optiondict
26332633 self.datakeyname = "text" if key_name is None else key_name
26342634 self.datakeylist = [self.datakeyname]
2635+ self.entry_validator = None
26352636 self.widget_type = "tk"
26362637 self.textwidget = tk.Text(self, exportselection=False, **opts)
26372638 self.textwidget.bind("<Key>", self.check_entrychange)
@@ -2644,6 +2645,7 @@
26442645 self.textwidget.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
26452646 if initial_text is not None:
26462647 self.replace_all(initial_text)
2648+ self.datadict[self.datakeyname] = initial_text
26472649 self.textwidget.grid(row=0, column=0, padx=(3,0), pady=(3,0), sticky=tk.NSEW)
26482650 self.ysb.grid(column=1, row=0, padx=(0,3), pady=(3,0), sticky=tk.NS)
26492651 self.xsb.grid(column=0, row=1, padx=(3,0), pady=(0,3), sticky=tk.EW)
@@ -2663,17 +2665,20 @@
26632665 """Update the pane's data dictionary with data from the text widget."""
26642666 text = self.textwidget.get("1.0", tk.END)
26652667 if is_valid:
2666- if text == "":
2667- self.clear_own()
2668- else:
2669- self.datadict["text"] = text
2668+ self.datadict[self.datakeyname] = text
26702669 else:
26712670 self.clear_own()
26722671
26732672 def valid_data(self, entry_widget=None):
26742673 text = self.textwidget.get("1.0", tk.END)
2675- return not (text == "" and self.required)
2676-
2674+ if text == "":
2675+ return (not self.required) and self.blank_is_valid
2676+ else:
2677+ if self.entry_validator is not None:
2678+ return self.entry_validator(text)
2679+ else:
2680+ return True
2681+
26772682 def clear_pane(self):
26782683 widget_state = self.textwidget.cget("state")
26792684 self.textwidget.configure(state=tk.NORMAL)
@@ -2756,7 +2761,14 @@
27562761 del self.datadict[self.datakeyname]
27572762 self.datakeyname = key_name
27582763 self.datakeylist = [key_name]
2759-
2764+
2765+ def set_entry_validator(self, fn):
2766+ """Set the callback function that will be used to check the entered value.
2767+
2768+ This function must take the entry value as an argument and return a Boolean.
2769+ """
2770+ self.entry_validator = fn
2771+
27602772
27612773
27622774 class UserPane(tkpane.TkPane):
diff -r 4fa1db37dc30 -r f082099bdfa7 tkpane/tkpane.py
--- a/tkpane/tkpane.py Wed Mar 14 11:44:05 2018 -0700
+++ b/tkpane/tkpane.py Wed Mar 14 21:16:06 2018 -0700
@@ -28,7 +28,7 @@
2828 that pass specific data to allow communication between panes, and callback methods
2929 for reporting status and progress."""
3030
31-__version__ = "0.29.0"
31+__version__ = "0.30.0"
3232
3333
3434 try:
@@ -792,6 +792,18 @@
792792 # Utility functions
793793 #-------------------------------------------------------------------------------
794794
795+def run_validity_callbacks(panelist):
796+ """Run the 'on_exit_data_(in)valid' callbacks for all panes in the list,
797+
798+ This function is intended to be used after all panes have been instantiated,
799+ when some panes have default values and are required by other panes. This
800+ function will ensure that the dependent panes have the required initial data
801+ values.
802+ """
803+ for p in panelist:
804+ p.handle_exit_validity(p.valid_data())
805+
806+
795807 def enable_or_disable_all(panelist):
796808 """Enable or disable all panes in the list, as required by their data status.
797809
Show on old repository browser