• R/O
  • SSH

tkpane: Commit

Default repository for tkpane.py.


Commit MetaInfo

Revisão7ba694f7c4fd0390b6fe0a305317c162d7507ce9 (tree)
Hora2018-03-01 04:04:57
AutorDreas Nielsen <dnielsen@inte...>
CommiterDreas Nielsen

Mensagem de Log

Added missing 'entry_widgets()' method to UserPasswordPane. Ordered custom panes in lib alphabetically. Added CanvasPane and ScaleSpinPane.

Mudança Sumário

Diff

diff -r 60c2c4d93c8b -r 7ba694f7c4fd setup.py
--- a/setup.py Tue Feb 27 19:33:15 2018 -0800
+++ b/setup.py Wed Feb 28 11:04:57 2018 -0800
@@ -2,7 +2,7 @@
22
33 setup(name='tkpane',
44 packages=['tkpane'],
5- version='0.18.0',
5+ version='0.18.1',
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 60c2c4d93c8b -r 7ba694f7c4fd tkpane/lib.py
--- a/tkpane/lib.py Tue Feb 27 19:33:15 2018 -0800
+++ b/tkpane/lib.py Wed Feb 28 11:04:57 2018 -0800
@@ -24,7 +24,7 @@
2424 for creation of other custom pane classes.
2525 """
2626
27-__version__ = "0.8.0"
27+__version__ = "0.9.0"
2828
2929
3030 try:
@@ -161,6 +161,808 @@
161161 # Pane classes
162162 #-------------------------------------------------------------------------------
163163
164+class ButtonPane(tkpane.TkPane):
165+ """Display a simple text button with configurable text and action.
166+
167+ :param pane_name: The name to be used to identify this pane in status messages.
168+ :param button_text: The text to display on the button.
169+ :param button_action: A callback to perform an action when the button is clicked.
170+ :param width: The width of the button, in characters (optional).
171+
172+ There are no data keys specific to this pane.
173+
174+ Overridden methods:
175+
176+ * disable_pane
177+ * enable_pane
178+ * set_style
179+ * focus
180+ * set_data
181+
182+ Custom methods:
183+
184+ * set_button_action
185+ * do_button_action
186+ """
187+ def __init__(self, parent, button_text, pane_name="button", button_action=None, width=None):
188+ def do_nothing(data_dict):
189+ pass
190+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
191+ self.button_text = button_text
192+ self.action = button_action if button_action is not None else do_nothing
193+ self.btn = ttk.Button(self, text=self.button_text, command=self.do_button_action)
194+ if width is not None:
195+ self.btn.configure(width=width)
196+ self.btn.grid(row=0, column=1, padx=3, sticky=tk.E)
197+ self.columnconfigure(0, weight=1)
198+ self.rowconfigure(0, weight=1)
199+ parent.rowconfigure(0, weight=0)
200+ parent.columnconfigure(0, weight=1)
201+
202+ def enable_pane(self):
203+ self._enablewidgets([self.btn])
204+
205+ def disable_pane(self):
206+ self._disablewidgets([self.btn])
207+
208+ def set_style(self, ttk_style):
209+ self._setstyle([self.btn], ttk_style)
210+
211+ def focus(self):
212+ """Set the focus to the button."""
213+ self.btn.focus_set()
214+
215+ def set_data(self, data_dict):
216+ """Update the pane's data dictionary with the provided data.
217+
218+ Special keys are:
219+ * button_text: Contains the text to place on the button.
220+ * button_width: Contains the width for the button.
221+
222+ All other data in the passed dictionary are added to the button's own data dictionary.
223+ """
224+ if "button_text" in data_dict:
225+ self.btn.configure(text=data_dict["button_text"])
226+ if "button_width" in data_dict:
227+ self.btn.configure(width=data_dict["button_width"])
228+ self.set_allbut(data, ["button_text", "button_width"])
229+
230+ def set_button_action(self, button_action):
231+ """Specify the callback function to be called when the button is clicked."""
232+ self.action = button_action if button_action is not None else do_nothing
233+ self.btn.configure(command=self.action)
234+
235+ def do_button_action(self):
236+ """Trigger this pane's action. The callback function will be passed this pane's data dictionary."""
237+ self.action(self.datadict)
238+
239+
240+
241+class CanvasPane(tkpane.TkPane):
242+ """Display a Tkinter Canvas widget.
243+
244+ :param width: The width of the Canvas widget, in pixels (optional).
245+ :param height: The height of the Canvas widget, in pixels (optional).
246+ :param config_opts: A dictionary of configuration options for the Canvas widget (optional).
247+
248+ Because of the variety of types of information that can be display on a Canvas
249+ widget, and associated metadata (e.g., position), the CanvasPane class does
250+ not maintain a data dictionary representing any of that information. Nor
251+ is there any built-in determination of whether the Canvas' contents are valid
252+ or invalid. The ``canvas_widget()`` method should be used for direct
253+ access to the Canvas widget, to either add or access data.
254+
255+ Overridden methods:
256+
257+ * clear_pane
258+ * enable_pane
259+ * disable_pane
260+ * focus
261+
262+ Custom method:
263+
264+ * canvas_widget
265+ """
266+ def __init__(self, parent, width=None, height=None, config_opts=None):
267+ self.pane_name = "Canvas"
268+ tkpane.TkPane.__init__(self, parent, self.pane_name, frame_config_opts(), frame_grid_opts())
269+ self.canvaswidget = tk.Canvas(self)
270+ if width is not None:
271+ self.canvaswidget.configure(width=width)
272+ if height is not None:
273+ self.canvaswidget.configure(height=height)
274+ if config_opts is not None:
275+ self.canvaswidget.configure(**config_opts)
276+ self.ysb = ttk.Scrollbar(self, orient='vertical', command=self.canvaswidget.yview)
277+ self.xsb = ttk.Scrollbar(self, orient='horizontal', command=self.canvaswidget.xview)
278+ self.canvaswidget.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
279+ self.canvaswidget.grid(row=0, column=0, padx=(3,0), pady=(3,0), sticky=tk.NSEW)
280+ self.ysb.grid(column=1, row=0, padx=(0,3), pady=(3,0), sticky=tk.NS)
281+ self.xsb.grid(column=0, row=1, padx=(3,0), pady=(0,3), sticky=tk.EW)
282+ self.rowconfigure(0, weight=1)
283+ self.columnconfigure(0, weight=1)
284+ parent.rowconfigure(0, weight=1)
285+ parent.columnconfigure(0, weight=1)
286+
287+ #---------------------------------------------------------------------------
288+ # Overrides of class methods.
289+ #...........................................................................
290+ def clear_pane(self):
291+ """Clear the canvas."""
292+ self.canvaswidget.delete("all")
293+
294+ def enable_pane(self):
295+ """Enable the canvas."""
296+ self._enablewidgets([self.canvaswidget])
297+
298+ def disable_pane(self):
299+ """Disable the canvas."""
300+ self._disablewidgets([self.canvaswidget])
301+
302+ def focus(self):
303+ """Set the focus to the canvas widget."""
304+ self.canvaswidget.focus_set()
305+
306+ #---------------------------------------------------------------------------
307+ # Custom methods.
308+ #...........................................................................
309+
310+ def canvas_widget(self):
311+ """Return the canvas widget object, to allow direct manipulation."""
312+ return self.canvaswidget
313+
314+
315+
316+class CheckboxPane(tkpane.TkPane):
317+ """Display a Tkinter Checkbutton widget to accept True and False values.
318+
319+ :param pane_name: The name to be used to identify this pane in status messages.
320+ :param prompt: The text associated with the checkbox.
321+ :param valid_state: Either True or False to indicate that either the checked or unchecked state (only) is to be considered valid. If not specified (the default), the checkbox will always be considered to have valid data.
322+ :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 CheckboxPane panes on the same UI (optional).
323+ :param config_opts: A dictionary of configuration options for the Checkbutton widget.
324+
325+ Data keys managed by this pane: "check" or the key name specified during initialization.
326+ The value of this item is always True or False, and defaults to False.
327+
328+ Name used by this pane: user-defined on initialization.
329+
330+ Overridden methods:
331+
332+ * entry_widgets
333+ * valid_data
334+ * save_data
335+ * clear_pane
336+ * enable_pane
337+ * disable_pane
338+ * set_style
339+ * focus
340+ * set_data
341+
342+ Custom methods:
343+
344+ * set_key
345+ """
346+
347+ def __init__(self, parent, pane_name, prompt, valid_state=None, key_name=None, config_opts=None):
348+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
349+ self.valid_state = valid_state
350+ self.datakeyname = "check" if key_name is None else key_name
351+ self.datakeylist = [self.datakeyname]
352+ self.checkvar = tk.BooleanVar()
353+ self.checkvar.set(False)
354+ self.datadict[self.datakeyname] = self.checkvar.get()
355+ self.checkbox = ttk.Checkbutton(self, text=prompt, variable=self.checkvar, onvalue=True, offvalue=False)
356+ if config_opts is not None:
357+ self.checkbox.configure(**config_opts)
358+ self.checkbox.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
359+ self.rowconfigure(0, weight=1)
360+ self.columnconfigure(0, weight=1)
361+ parent.rowconfigure(0, weight=1)
362+ parent.columnconfigure(0, weight=1)
363+ self.checkvar.trace("w", self.check_checkchange)
364+
365+ #---------------------------------------------------------------------------
366+ # Overrides of class methods.
367+ #...........................................................................
368+
369+ def entry_widgets(self):
370+ return [self.checkbox]
371+
372+ def valid_data(self, widget):
373+ """Returns an indication of whether the checkbox is in a valid state."""
374+ if self.valid_state is None:
375+ return True
376+ else:
377+ return self.checkvar.get() == self.valid_state
378+
379+ def save_data(self, is_valid, entry_widget):
380+ """Update the pane's data dictionary with data from the Checkbutton widget."""
381+ state = self.checkvar.get()
382+ self.datadict[self.datakeyname] = state
383+
384+ def clear_pane(self):
385+ self.checkvar.set(False)
386+
387+ def enable_pane(self):
388+ self._enablewidgets([self.checkbox])
389+
390+ def disable_pane(self):
391+ self._disablewidgets([self.checkbox])
392+
393+ def set_style(self, ttk_style):
394+ self._setstyle([self.checkbox], ttk_style)
395+
396+ def focus(self):
397+ """Set the focus to the checkbox."""
398+ self.checkbox.focus_set()
399+
400+ def set_data(self, data):
401+ """Update the pane's data dictionary with the provided data.
402+
403+ Special key supported: 'prompt' changes the pane's prompt.
404+ """
405+ spkey = "prompt"
406+ if spkey in data:
407+ self.checkbox.configure(text=data[spkey])
408+ self.set_allbut(data, [spkey])
409+
410+ #---------------------------------------------------------------------------
411+ # Custom methods.
412+ #...........................................................................
413+
414+ def check_checkchange(self, *args):
415+ self.handle_change_validity(self.valid_data(self.checkbox), self.checkbox)
416+
417+ def set_key(self, key_name):
418+ """Change the name of the data key used for the entered data.
419+
420+ :param key_name: New name for the data key.
421+
422+ This method allows the name of the data key to be customized to
423+ eliminate conflicts with other CheckboxPane objects on the same UI.
424+ """
425+ if self.datakeyname in self.datadict:
426+ self.datadict[key_name] = self.datadict[self.datakeyname]
427+ del self.datadict[self.datakeyname]
428+ self.datakeyname = key_name
429+ self.datakeylist = [key_name]
430+
431+
432+
433+class ComboboxPane(tkpane.TkPane):
434+ """Display a Tkinter Combobox widget with a prompt.
435+
436+ :param pane_name: The name to be used to identify this pane in status messages.
437+ :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
438+ :param items: The list of items to be included in the drop-down list.
439+ :param item_only: A Boolean indicating whether or not items from the list are the only valid entries. Default: False.
440+ :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 EntryPane objects on the same UI (optional).
441+
442+ Data keys managed by this pane: "combobox" or the key name specified during initialization.
443+
444+ Name used by this pane: user-defined on initialization.
445+
446+ Overridden methods:
447+
448+ * entry_widgets
449+ * valid_data
450+ * save_data
451+ * clear_pane
452+ * enable_pane
453+ * disable_pane
454+ * set_style
455+ * focus
456+ * set_data
457+
458+ Custom method:
459+
460+ * set_key
461+ """
462+
463+ def __init__(self, parent, pane_name, prompt, items, item_only=False, key_name=None):
464+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
465+ self.items = items
466+ self.item_only = item_only
467+ self.datakeyname = "combobox" if key_name is None else key_name
468+ self.datakeylist = [self.datakeyname]
469+ self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
470+ self.entry_var = tk.StringVar()
471+ self.entrywidget = ttk.Combobox(self, textvariable=self.entry_var, values=items, width=max(map(len, map(str, items)))+1, exportselection=False)
472+ self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
473+ self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.W)
474+ self.rowconfigure(0, weight=1)
475+ self.columnconfigure(0, weight=0)
476+ self.columnconfigure(1, weight=1)
477+ parent.rowconfigure(0, weight=1)
478+ parent.columnconfigure(0, weight=1)
479+ self.entry_var.trace("w", self.check_entrychange)
480+
481+ #---------------------------------------------------------------------------
482+ # Overrides of class methods.
483+ #...........................................................................
484+
485+ def entry_widgets(self):
486+ return [self.entrywidget]
487+
488+ def valid_data(self, entry_widget=None):
489+ text = self.entry_var.get()
490+ if self.required:
491+ if self.item_only:
492+ return text in self.items
493+ else:
494+ return text != ""
495+ else:
496+ if self.item_only:
497+ return text in self.items
498+ else:
499+ return True
500+
501+ def save_data(self, is_valid, entry_widget):
502+ """Update the pane's data dictionary with data from the Combobox widget."""
503+ text = self.entry_var.get()
504+ if is_valid:
505+ if text == "":
506+ self.clear_own()
507+ else:
508+ self.datadict[self.datakeyname] = text
509+ else:
510+ self.clear_own()
511+
512+ def clear_pane(self):
513+ self.entry_var.set("")
514+
515+ def enable_pane(self):
516+ self._enablewidgets([self.prompt, self.entrywidget])
517+
518+ def disable_pane(self):
519+ self._disablewidgets([self.prompt, self.entrywidget])
520+
521+ def set_style(self, ttk_style):
522+ self._setstyle([self.prompt, self.entrywidget], ttk_style)
523+
524+ def focus(self):
525+ """Set the focus to the entry."""
526+ self.entrywidget.focus_set()
527+
528+ def set_data(self, data):
529+ """Update the pane's data dictionary with the provided data.
530+
531+ Special key supported: 'prompt' changes the pane's prompt.
532+ """
533+ spkey = "prompt"
534+ if spkey in data:
535+ self.prompt.configure(text=data[spkey])
536+ self.set_allbut(data, [spkey])
537+
538+ #---------------------------------------------------------------------------
539+ # Custom methods.
540+ #...........................................................................
541+
542+ def check_entrychange(self, *args):
543+ self.handle_change_validity(self.valid_data(None), self.entrywidget)
544+
545+ def set_key(self, key_name):
546+ """Change the name of the data key used for the entered data.
547+
548+ :param key_name: New name for the data key.
549+
550+ This method allows the name of the data key to be customized to
551+ eliminate conflicts with other EntryPane objects on the same UI.
552+ """
553+ if self.datakeyname in self.datadict:
554+ self.datadict[key_name] = self.datadict[self.datakeyname]
555+ del self.datadict[self.datakeyname]
556+ self.datakeyname = key_name
557+ self.datakeylist = [key_name]
558+
559+ def set_newitems(self, items):
560+ """Change the items displayed in the list. This will clear any selection."""
561+ if any(list(set(self.items) ^ set(items))):
562+ self.clear_pane()
563+ if self.datakeyname in self.datadict:
564+ del self.datadict[self.datakeyname]
565+ self.entrywidget.configure(values=items)
566+ self.items = items
567+ self.handle_change_validity(self.valid_data(self.entrywidget), self.entrywidget)
568+
569+
570+
571+class EmptyPane(tkpane.TkPane):
572+ """A pane with no widgets that can be used as a spacer."""
573+ def __init__(self, parent):
574+ tkpane.TkPane.__init__(self, parent, "empty_pane", frame_config_opts(), frame_grid_opts())
575+ self.pack(expand=True, fill=tk.BOTH)
576+ parent.rowconfigure(0, weight=1)
577+ parent.columnconfigure(0, weight=1)
578+
579+
580+class EntryPane(tkpane.TkPane):
581+ """Display a Tkinter Entry widget.
582+
583+ :param pane_name: The name to be used to identify this pane in status messages.
584+ :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
585+ :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 EntryPane objects on the same UI (optional).
586+
587+ Data keys managed by this pane: "entry" or the key name specified during initialization.
588+
589+ Name used by this pane: user-defined on initialization.
590+
591+ Overridden methods:
592+
593+ * entry_widgets
594+ * valid_data
595+ * save_data
596+ * clear_pane
597+ * enable_pane
598+ * disable_pane
599+ * set_style
600+ * focus
601+ * set_data
602+
603+ Custom method:
604+
605+ * set_key
606+ """
607+
608+ def __init__(self, parent, pane_name, prompt, key_name=None):
609+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
610+ self.datakeyname = "entry" if key_name is None else key_name
611+ self.datakeylist = [self.datakeyname]
612+ self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
613+ self.entry_var = tk.StringVar()
614+ self.entrywidget = ttk.Entry(self, textvariable=self.entry_var, exportselection=False)
615+ self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
616+ self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
617+ self.rowconfigure(0, weight=1)
618+ self.columnconfigure(0, weight=0)
619+ self.columnconfigure(1, weight=1)
620+ parent.rowconfigure(0, weight=1)
621+ parent.columnconfigure(0, weight=1)
622+ self.entry_var.trace("w", self.check_entrychange)
623+
624+ #---------------------------------------------------------------------------
625+ # Overrides of class methods.
626+ #...........................................................................
627+
628+ def entry_widgets(self):
629+ return [self.entrywidget]
630+
631+ def valid_data(self, entry_widget=None):
632+ text = self.entry_var.get()
633+ return not (text == "" and self.required)
634+
635+ def save_data(self, is_valid, entry_widget):
636+ """Update the pane's data dictionary with data from the Entry widget."""
637+ text = self.entry_var.get()
638+ if is_valid:
639+ if text == "":
640+ self.clear_own()
641+ else:
642+ self.datadict[self.datakeyname] = text
643+ else:
644+ self.clear_own()
645+
646+ def clear_pane(self):
647+ self.entry_var.set("")
648+
649+ def enable_pane(self):
650+ self._enablewidgets([self.prompt, self.entrywidget])
651+
652+ def disable_pane(self):
653+ self._disablewidgets([self.prompt, self.entrywidget])
654+
655+ def set_style(self, ttk_style):
656+ self._setstyle([self.prompt, self.entrywidget], ttk_style)
657+
658+ def focus(self):
659+ """Set the focus to the entry."""
660+ self.entrywidget.focus_set()
661+
662+ def set_data(self, data):
663+ """Update the pane's data dictionary with the provided data.
664+
665+ Special key supported: 'prompt' changes the pane's prompt.
666+ """
667+ spkey = "prompt"
668+ if spkey in data:
669+ self.prompt.configure(text=data[spkey])
670+ self.set_allbut(data, [spkey])
671+
672+ #---------------------------------------------------------------------------
673+ # Custom methods.
674+ #...........................................................................
675+
676+ def check_entrychange(self, *args):
677+ self.handle_change_validity(self.valid_data(None), self.entrywidget)
678+
679+ def set_key(self, key_name):
680+ """Change the name of the data key used for the entered data.
681+
682+ :param key_name: New name for the data key.
683+
684+ This method allows the name of the data key to be customized to
685+ eliminate conflicts with other EntryPane objects on the same UI.
686+ """
687+ if self.datakeyname in self.datadict:
688+ self.datadict[key_name] = self.datadict[self.datakeyname]
689+ del self.datadict[self.datakeyname]
690+ self.datakeyname = key_name
691+ self.datakeylist = [key_name]
692+
693+
694+class InputFilePane(tkpane.TkPane):
695+ """Get and display an input filename.
696+
697+ :param optiondict: a dictionary of option names and values for the Tkinter 'askopenfilename' method (optional).
698+
699+ Data key managed by this pane: "input_filename".
700+
701+ Name used by this pane: "Input filename".
702+
703+ Overridden methods:
704+
705+ * entry_widgets
706+ * valid_data
707+ * save_data
708+ * clear_pane
709+ * disable_pane
710+ * enable_pane
711+ * set_style
712+ * focus
713+ * set_data
714+ """
715+
716+ def __init__(self, parent, optiondict=None):
717+ tkpane.TkPane.__init__(self, parent, "Input filename", frame_config_opts(), frame_grid_opts())
718+ # Customize attributes
719+ self.optiondict = {} if optiondict is None else optiondict
720+ self.datakey = "input_filename"
721+ self.datakeylist = [self.datakey]
722+ # Create, configure, and place widgets.
723+ self.dir_label = ttk.Label(self, text='Input file:', width=12, anchor=tk.E)
724+ self.file_var = tk.StringVar()
725+ self.file_var.trace("w", self.check_entrychange)
726+ self.file_display = ttk.Entry(self, textvariable=self.file_var)
727+ self.valid_color = self.file_display.cget("background")
728+ self.browse_button = ttk.Button(self, text='Browse', width=8, command=self.set_inputfile)
729+ self.dir_label.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
730+ self.file_display.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
731+ self.browse_button.grid(row=1, column=1, padx=3, pady=1, sticky=tk.W)
732+ self.columnconfigure(0, weight=0)
733+ self.columnconfigure(1, weight=1)
734+ self.rowconfigure(1, weight=1)
735+ parent.rowconfigure(0, weight=0)
736+ parent.columnconfigure(0, weight=1)
737+
738+ #---------------------------------------------------------------------------
739+ # Overrides of class methods.
740+ #...........................................................................
741+
742+ def entry_widgets(self):
743+ """Return a list of widgets used for data entry."""
744+ return [self.file_display]
745+
746+ def valid_data(self, widget):
747+ """Return True or False indicating the validity of the filename entry.
748+
749+ Overrides TkPane class method.
750+ """
751+ import os.path
752+ filename = self.file_display.get()
753+ if filename == "":
754+ return not self.required
755+ else:
756+ return os.path.isfile(filename)
757+
758+ def save_data(self, is_valid, entry_widget):
759+ """Update the pane's data dictionary with data from the entry widget.
760+
761+ Overrides TkPane class method.
762+ """
763+ # entry_widget should be self.file_display.
764+ filename = self.file_display.get()
765+ if is_valid:
766+ if filename == "":
767+ self.clear_own()
768+ else:
769+ self.datadict[self.datakey] = filename
770+ else:
771+ self.clear_own()
772+
773+
774+ def clear_pane(self):
775+ self.file_var.set(u'')
776+
777+ def enable_pane(self):
778+ self._enablewidgets([self.dir_label, self.file_display, self.browse_button])
779+
780+ def disable_pane(self):
781+ self._disablewidgets([self.dir_label, self.file_display, self.browse_button])
782+
783+ def set_style(self, ttk_style):
784+ self._setstyle([self.dir_label, self.file_display, self.browse_button], ttk_style)
785+
786+ def focus(self):
787+ """Set the focus to the entry."""
788+ self.file_display.focus_set()
789+
790+ def set_data(self, data):
791+ """Update the pane's data dictionary with the provide data.
792+
793+ Special key supported: 'input_filename' changes the filename in the entry widget.
794+ """
795+ if self.datakey in data:
796+ self.file_var.set(data[self.datakey])
797+ self.handle_change_validity(True, self.file_display)
798+ self.send_status_message(True)
799+ self.set_allbut(data, [self.datakey])
800+
801+ #---------------------------------------------------------------------------
802+ # Custom methods.
803+ #...........................................................................
804+
805+ def set_key(self, key_name):
806+ """Change the name of the data key used for the entered data.
807+
808+ :param key_name: New name for the data key.
809+
810+ This method allows the name of the data key to be customized to
811+ eliminate conflicts with other InputFilePane objects on the same UI.
812+ """
813+ if self.datakeyname in self.datadict:
814+ self.datadict[key_name] = self.datadict[self.datakeyname]
815+ del self.datadict[self.datakeyname]
816+ self.datakeyname = key_name
817+ self.datakeylist = [key_name]
818+
819+ def check_entrychange(self, *args):
820+ self.handle_change_validity(self.valid_data(None), self.file_display)
821+
822+ def set_inputfile(self):
823+ fn = tk_file.askopenfilename(**self.optiondict)
824+ if fn != "":
825+ # The order of the following steps is important.
826+ self.file_var.set(fn)
827+ self.handle_change_validity(True, self.file_display)
828+ self.send_status_message(True)
829+
830+
831+class ListboxPane(tkpane.TkPane):
832+ """Display a Tkinter Listbox.
833+
834+ :param pane_name: The name to be used to identify this pane in status messages.
835+ :param items: The list of items to be initially displayed in the listbox.
836+ :param rows: The number of rows (items) to be shown; the listbox will have a scrollbar (optional).
837+ :param key_name: The name to be used with the internal data dictionary to identify the selected list entries; use to avoid name conflicts with other ListboxPane objects on the same UI (optional).
838+ :param mode: The selection mode to use; "single", "browse", "multiple", or "extended" (optional; default is "extended").
839+
840+ Data key managed by this pane: "listbox" or the key name specified during initialization.
841+
842+ The value of the data managed by this pane is a list of the selected items.
843+
844+ Name used by this pane: user-defined on initialization.
845+
846+ Overridden methods:
847+
848+ * entry_widgets
849+ * valid_data
850+ * save_data
851+ * clear_pane
852+ * enable_pane
853+ * disable_pane
854+ * set_style
855+ * focus
856+
857+ Custom methods:
858+
859+ * set_newitems
860+ * set_key
861+ """
862+
863+ def __init__(self, parent, pane_name, items, rows=None, width=None, key_name=None, mode=None):
864+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
865+ self.datakeyname = "listbox" if key_name is None else key_name
866+ self.datakeylist = [self.datakeyname]
867+ self.scroller = ttk.Scrollbar(self, orient=tk.VERTICAL)
868+ ht = 10 if rows is None else rows
869+ selmode = "extended" if mode is None else mode
870+ self.listbox = tk.Listbox(self, selectmode=selmode, exportselection=False, yscrollcommand=self.scroller.set, height=ht)
871+ if width is not None:
872+ self.listbox.configure(width=width)
873+ self.listbox.bind("<<ListboxSelect>>", self.check_entrychange)
874+ self.scroller.config(command=self.listbox.yview)
875+ for item in items:
876+ self.listbox.insert(tk.END, item)
877+ self.listbox.grid(row=0, column=0, padx=(3,0), pady=3, sticky=tk.NSEW)
878+ self.scroller.grid(row=0, column=1, padx=(0,3), pady=3, sticky=tk.NS)
879+ self.rowconfigure(0, weight=1)
880+ self.columnconfigure(0, weight=1)
881+ self.columnconfigure(1, weight=0)
882+ parent.rowconfigure(0, weight=1)
883+ parent.columnconfigure(0, weight=1)
884+
885+ #---------------------------------------------------------------------------
886+ # Overrides of class methods.
887+ #...........................................................................
888+
889+ def entry_widgets(self):
890+ return [self.listbox]
891+
892+ def valid_data(self, widget):
893+ """Returns an indication of whether the listbox is both required and has at least one selection."""
894+ if self.required:
895+ selected = self.listbox.curselection()
896+ if isinstance(selected, tuple):
897+ return len(selected) > 0
898+ else:
899+ return selected is not None
900+ else:
901+ return True
902+
903+ def save_data(self, is_valid, entry_widget):
904+ """Update the pane's data dictionary with data from the Listbox widget."""
905+ if is_valid:
906+ items = map(int, self.listbox.curselection())
907+ self.datadict[self.datakeyname] = [self.listbox.get(i) for i in items]
908+ else:
909+ self.clear_own()
910+
911+ def clear_pane(self):
912+ self.listbox.selection_clear(0, tk.END)
913+
914+ def enable_pane(self):
915+ self.listbox.configure(state=tk.NORMAL)
916+
917+ def disable_pane(self):
918+ self.listbox.configure(state=tk.DISABLED)
919+
920+ def set_style(self, ttk_style):
921+ """Sets the style of the scrollbar accompanying the listbox.
922+
923+ The Listbox widget is not a themed ttk widget and cannot have a style applied.
924+ """
925+ self._setstyle([self.scroller], ttk_style)
926+
927+ def focus(self):
928+ """Set the focus to the listbox."""
929+ self.listbox.focus_set()
930+
931+ #---------------------------------------------------------------------------
932+ # Custom methods.
933+ #...........................................................................
934+
935+ def check_entrychange(self, event):
936+ self.handle_change_validity(self.valid_data(self.listbox), self.listbox)
937+
938+ def set_newitems(self, items):
939+ """Change the items displayed in the list. This will clear any selection."""
940+ contents = self.listbox.get(0, tk.END)
941+ if any(list(set(contents) ^ set(items))):
942+ self.clear_pane()
943+ if self.datakeyname in self.datadict:
944+ del self.datadict[self.datakeyname]
945+ self.listbox.delete(0, tk.END)
946+ for item in items:
947+ self.listbox.insert(tk.END, item)
948+ self.handle_change_validity(self.valid_data(self.listbox), self.listbox)
949+
950+ def set_key(self, key_name):
951+ """Change the name of the data key used for the entered data.
952+
953+ :param key_name: New name for the data key.
954+
955+ This method allows the name of the data key to be customized to
956+ eliminate conflicts with other ListboxPane objects on the same UI.
957+ """
958+ if self.datakeyname in self.datadict:
959+ self.datadict[key_name] = self.datadict[self.datakeyname]
960+ del self.datadict[self.datakeyname]
961+ self.datakeyname = key_name
962+ self.datakeylist = [key_name]
963+
964+
965+
164966 class MessagePane(tkpane.TkPane):
165967 """Display a text message.
166968
@@ -201,300 +1003,138 @@
2011003 self.set_allbut(data, [spkey])
2021004
2031005
204-class UserPane(tkpane.TkPane):
205- """Display a user's name and a button to prompt for a user's name and password.
1006+class NotebookPane(tkpane.TkPane):
1007+ """Create and populate a Tkinter Notebook widget.
2061008
207- Data keys managed by this pane: "name" and "password".
1009+ :param pane_name: The name to be used to identify this pane in status messages.
1010+ :param tab_specs: A list or tuple of two-element tuples; each two-element tuple contains the tab's label and a `build` function that is passed the Notebook widget and should populate the tab page with widgets and return the frame enclosing all widgets on that page.
2081011
209- Name used by this pane: "User authorization".
1012+ This pane does not manage any data.
1013+
1014+ Name used by this pane: user-defined on initialization.
2101015
2111016 Overridden methods:
2121017
213- * valid_data
214- * clear_pane
215- * send_status_message
216- * focus
1018+ * set_style
2171019
2181020 Custom methods:
2191021
220- * set_user
221- * set_user_validator
1022+ * notebook_widget
2221023 """
2231024
224- class GetUserDialog(Dialog):
225- def makebody(self, master):
226- ttk.Label(master, text="User name:", width=12, anchor=tk.E).grid(row=0, column=0, sticky=tk.E, padx=3, pady=3)
227- ttk.Label(master, text="Password:", width=12, anchor=tk.E).grid(row=1, column=0, sticky=tk.E, padx=3, pady=3)
228- self.e1 = tk.Entry(master, width=36)
229- self.e2 = tk.Entry(master, width=36, show="*")
230- self.e1.grid(row=0, column=1, sticky=tk.W, padx=3, pady=3)
231- self.e2.grid(row=1, column=1, sticky=tk.W, padx=3, pady=3)
232- return self.e1
233- def validate(self):
234- return self.e1.get() != u'' and self.e2.get() != u''
235- def apply(self):
236- self.result = {u"name": self.e1.get(), u"password": self.e2.get()}
237-
238- def __init__(self, parent):
239- tkpane.TkPane.__init__(self, parent, "User authorization", config_opts=frame_config_opts(), grid_opts=frame_grid_opts())
240- self.user_validator = None
241- self.user_label = ttk.Label(self, text='User name:', width=10, anchor=tk.E)
242- self.user_var = tk.StringVar()
243- self.userkeyname = "name"
244- self.passkeyname = "password"
245- self.datakeylist = [self.userkeyname, self.passkeyname]
246- self.datadict = {}
247- self.previous_values = {}
248- self.user_display = ttk.Entry(self, textvariable=self.user_var)
249- self.user_display.config(state='readonly')
250- self.user_button = ttk.Button(self, text='Change', width=8, command=self.set_user)
251- self.user_label.grid(row=0, column=0, padx=6, pady=3, sticky=tk.EW)
252- self.user_display.grid(row=0, column=1, padx=6, pady=3, sticky=tk.EW)
253- self.user_button.grid(row=1, column=1, padx=6, pady=1, sticky=tk.W)
254- self.columnconfigure(0, weight=0)
255- self.columnconfigure(1, weight=1)
256- self.rowconfigure(1, weight=1)
257- parent.rowconfigure(0, weight=0)
1025+ def __init__(self, parent, pane_name, tab_specs):
1026+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1027+ self.tabids = {}
1028+ self.tabframes = {}
1029+ self.nb_widget = ttk.Notebook(self)
1030+ id_no = 0
1031+ for tab in tab_specs:
1032+ tabframe = tab[1](self.nb_widget)
1033+ self.nb_widget.add(tabframe, text=tab[0])
1034+ self.tabids[tab[0]] = id_no
1035+ self.tabframes[tab[0]] = tabframe
1036+ id_no += 1
1037+ self.nb_widget.enable_traversal()
1038+ self.nb_widget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.NSEW)
1039+ self.rowconfigure(0, weight=1)
1040+ self.columnconfigure(0, weight=1)
1041+ parent.rowconfigure(0, weight=1)
2581042 parent.columnconfigure(0, weight=1)
2591043
2601044 #---------------------------------------------------------------------------
2611045 # Overrides of class methods.
2621046 #...........................................................................
2631047
264- def valid_data(self, widget=None):
265- """Return True or False indicating whether or not a name and password have been entered."""
266- # Although this method is meant to check the data in the widgets, because
267- # name and password can be entered only from a dialog box, and the dialog
268- # box requires entry, and the values are then assigned to the data
269- # dictionary, this routine checks the data dictionary rather than the widgets.
270- if self.required:
271- v = "name" in self.datadict and "password" in self.datadict
272- else:
273- v = True
274- if v:
275- if self.user_validator is not None:
276- return self.user_validator(self.datadict["name"], self.datadict["password"])
277- else:
278- return True
279- else:
280- return False
281-
282-
283- def send_status_message(self, is_valid):
284- """Send a status message reporting data values and/or validity if data have changed."""
285- # This overrides the class method because only the user name should be reported.
286- if self.datadict != self.original_values:
287- if is_valid:
288- if "name" in self.datadict.keys():
289- self.report_status(u"User name set to %s." % self.datadict["name"])
290- else:
291- self.report_status(u"User name cleared.")
292- else:
293- self.report_status("User name is invalid.")
294-
295- def clear_pane(self):
296- self.user_var.set(u'')
297- self.user_pw = None
1048+ def set_style(self, ttk_style):
1049+ self._setstyle([self.nb_widget], ttk_style)
2981050
299- def enable_pane(self):
300- self._enablewidgets([self.user_label,self.user_display, self.user_button])
301-
302- def disable_pane(self):
303- self._disablewidgets([self.user_label,self.user_display, self.user_button])
304-
305- def set_style(self, ttk_style):
306- self._setstyle([self.user_label, self.user_display], ttk_style)
307-
308- def focus(self):
309- """Set the focus to the button."""
310- self.user_button.focus_set()
311-
3121051 #---------------------------------------------------------------------------
3131052 # Custom methods.
3141053 #...........................................................................
3151054
316- def set_keys(self, user_key_name, password_key_name):
317- """Change the names of the data keys used for the entered data.
318-
319- :param user_key_name: New name for the key for the user's name.
320- :param password_key_name: New name for the key for the user's password.
321-
322- This method allows the name of the data key to be customized to
323- eliminate conflicts with other UserPane objects on the same UI.
324- """
325- if self.userkeyname in self.datadict:
326- self.datadict[user_key_name] = self.datadict[self.userkeyname]
327- del self.datadict[self.userkeyname]
328- self.userkeyname = user_key_name
329- if self.passkeyname in self.datadict:
330- self.datadict[password_key_name] = self.datadict[self.passkeyname]
331- self.passkeyname = password_key_name
332- self.userkeylist = [user_key_name, password_key_name]
1055+ def notebook_widget(self):
1056+ """Return the Notebook widget."""
1057+ return self.nb_widget
1058+
1059+ def tab_id(self, tab_name):
1060+ """Return the tab ID (integer) corresponding to the tab's name or label."""
1061+ return self.tabid[tab_name]
3331062
334- def set_user(self):
335- # Open a dialog box to prompt for the user's name and password.
336- dlg = self.GetUserDialog(self)
337- if dlg.result is not None:
338- self.user_var.set(dlg.result["name"])
339- self.user_pw = dlg.result["password"]
340- self.datadict["name"] = dlg.result["name"]
341- self.datadict["password"] = dlg.result["password"]
342- self.report_status(u"Name set to %s." % dlg.result[u"name"])
343- self.handle_change_validity(True, None)
344- self.send_status_message(True)
345-
346- def set_user_validator(self, fn):
347- """Set the callback function that will be used to check the entered user name and password.
348-
349- This function must take the user name and password as arguments and return a Boolean.
350- """
351- self.user_validator = fn
1063+ def tab_frames(self):
1064+ """Return a dictionary of tab names and the frame enclosing the contents for the tab."""
1065+ return self.tabframes
1066+
1067+ def tab_frame(self, tab_name):
1068+ """Return the frame corresponding to the tab's name or label."""
1069+ return self.tabframes[tab_name]
3521070
3531071
3541072
355-class UserPasswordPane(tkpane.TkPane):
356- """Display a user's name and a button to prompt for a user's name and password.
1073+class OkCancelPane(tkpane.TkPane):
1074+ """Display OK and Cancel buttons.
3571075
358- Data keys managed by this pane: "name" and "password".
359-
360- Name used by this pane: "User credentials".
1076+ There are no data keys specific to this pane.
3611077
3621078 Overridden methods:
3631079
364- * valid_data
365- * save_data
366- * clear_pane
1080+ * disable_pane
3671081 * enable_pane
368- * disable_pane
369- * send_status_message
3701082 * set_style
3711083 * focus
3721084
373- Custom method:
1085+ Custom methods:
3741086
375- * set_user_validator
1087+ * set_cancel_action
1088+ * set_ok_action
1089+ * ok
1090+ * cancel
3761091 """
377-
378- def __init__(self, parent):
379- tkpane.TkPane.__init__(self, parent, "User credentials", config_opts=frame_config_opts(), grid_opts=frame_grid_opts())
380- self.user_validator = None
381- self.user_label = ttk.Label(self, text='User name:', width=10, anchor=tk.E)
382- self.pw_label = ttk.Label(self, text='Password:', width=10, anchor=tk.E)
383- self.user_var = tk.StringVar()
384- self.user_var.trace("w", self.check_namechange)
385- self.pw_var = tk.StringVar()
386- self.pw_var.trace("w", self.check_pwchange)
387- self.userkeyname = "name"
388- self.passkeyname = "password"
389- self.datakeylist = [self.userkeyname, self.passkeyname]
390- self.user_display = ttk.Entry(self, textvariable=self.user_var)
391- self.pw_display = ttk.Entry(self, textvariable=self.pw_var, show="*")
392- self.user_label.grid(row=0, column=0, padx=6, pady=3, sticky=tk.EW)
393- self.pw_label.grid(row=1, column=0, padx=6, pady=3, sticky=tk.EW)
394- self.user_display.grid(row=0, column=1, padx=6, pady=3, sticky=tk.EW)
395- self.pw_display.grid(row=1, column=1, padx=6, pady=3, sticky=tk.EW)
396- self.columnconfigure(0, weight=0)
397- self.columnconfigure(1, weight=1)
398- self.rowconfigure(1, weight=1)
1092+ def __init__(self, parent, ok_action=None, cancel_action=None):
1093+ def do_nothing(data_dict):
1094+ pass
1095+ tkpane.TkPane.__init__(self, parent, "OK/Cancel", frame_config_opts(), frame_grid_opts())
1096+ self.ok_action = ok_action if ok_action is not None else do_nothing
1097+ self.cancel_action = cancel_action if cancel_action is not None else do_nothing
1098+ self.cancel_btn = ttk.Button(self, text="Cancel", command=self.cancel_action)
1099+ self.cancel_btn.grid(row=0, column=1, padx=3, sticky=tk.E)
1100+ self.ok_btn = ttk.Button(self, text="OK", command=self.ok_action)
1101+ self.ok_btn.grid(row=0, column=0, padx=3, sticky=tk.E)
1102+ self.columnconfigure(0, weight=1)
1103+ self.columnconfigure(1, weight=0)
1104+ self.rowconfigure(0, weight=1)
3991105 parent.rowconfigure(0, weight=0)
4001106 parent.columnconfigure(0, weight=1)
4011107
402- #---------------------------------------------------------------------------
403- # Overrides of class methods.
404- #...........................................................................
405-
406- def valid_data(self, widget=None):
407- """Return True or False indicating whether or not a name and password have been entered."""
408- name = self.user_var.get()
409- pw = self.pw_var.get()
410- if self.required:
411- v = (len(name) > 0 and len(pw) > 0)
412- else:
413- v = (not (len(name) == 0 and len(pw) > 0))
414- if v:
415- if self.user_validator is not None:
416- return self.user_validator(name, pw)
417- else:
418- return True
419- else:
420- return False
1108+ def enable_pane(self):
1109+ self._enablewidgets([self.ok_btn])
4211110
422- def save_data(self, is_valid, entry_widget):
423- """Update the pane's data dictionary with data from the Entry widgets."""
424- name = self.user_var.get()
425- pw = self.pw_var.get()
426- if is_valid:
427- if name == "":
428- self.clear_own()
429- else:
430- self.datadict["name"] = name
431- self.datadict["password"] = pw
432- else:
433- self.clear_own()
1111+ def disable_pane(self):
1112+ self._disablewidgets([self.ok_btn])
4341113
435- def send_status_message(self, is_valid):
436- """Send a status message reporting data values and/or validity if data have changed."""
437- # This overrides the class method because only the user name should be reported.
438- if self.datadict != self.original_values:
439- if is_valid:
440- if "name" in self.datadict:
441- self.report_status(u"User name set to %s." % self.datadict["name"])
442- else:
443- self.report_status(u"User name cleared.")
444- else:
445- self.report_status("User name is invalid.")
446-
447- def clear_pane(self):
448- self.user_var.set("")
449- self.pw_var.set("")
450-
451- def enable_pane(self):
452- self._enablewidgets([self.user_label, self.pw_label, self.user_display, self.pw_display])
453-
454- def disable_pane(self):
455- self._disablewidgets([self.user_label, self.pw_label, self.user_display, self.pw_display])
456-
4571114 def set_style(self, ttk_style):
458- self._setstyle([self.user_label, self.pw_label, self.user_display, self.pw_display], ttk_style)
1115+ self._setstyle([self.cancel_btn, self.ok_btn], ttk_style)
4591116
4601117 def focus(self):
461- """Set the focus to the user name."""
462- self.user_display.focus_set()
463-
464- #---------------------------------------------------------------------------
465- # Custom methods.
466- #...........................................................................
1118+ """Set the focus to the OK button."""
1119+ self.ok_btn.focus_set()
4671120
468- def set_keys(self, user_key_name, password_key_name):
469- """Change the names of the data keys used for the entered data.
470-
471- :param user_key_name: New name for the key for the user's name.
472- :param password_key_name: New name for the key for the user's password.
473-
474- This method allows the name of the data key to be customized to
475- eliminate conflicts with other UserPasswordPane objects on the same UI.
476- """
477- if self.userkeyname in self.datadict:
478- self.datadict[user_key_name] = self.datadict[self.userkeyname]
479- del self.datadict[self.userkeyname]
480- self.userkeyname = user_key_name
481- if self.passkeyname in self.datadict:
482- self.datadict[password_key_name] = self.datadict[self.passkeyname]
483- self.passkeyname = password_key_name
484- self.userkeylist = [user_key_name, password_key_name]
1121+ def set_ok_action(self, ok_action):
1122+ """Specify the callback function to be called when the 'OK' button is clicked."""
1123+ self.ok_action = ok_action if ok_action is not None else do_nothing
1124+ self.ok_btn.configure(command=self.ok_action)
4851125
486- def check_namechange(self, *args):
487- self.handle_change_validity(self.valid_data(None), self.user_display)
488-
489- def check_pwchange(self, *args):
490- self.handle_change_validity(self.valid_data(None), self.pw_display)
1126+ def set_cancel_action(self, cancel_action):
1127+ """Specify the callback function to be called when the 'Cancel' button is clicked."""
1128+ self.cancel_action = cancel_action if cancel_action is not None else do_nothing
1129+ self.cancel_btn.configure(command=self.cancel_action)
4911130
492- def set_user_validator(self, fn):
493- """Set the callback function that will be used to check the entered user name and password.
494-
495- This function must take the user name and password as arguments and return a Boolean.
496- """
497- self.user_validator = fn
1131+ def ok(self):
1132+ """Trigger this pane's 'OK' action. The callback function will be passed this pane's data dictionary."""
1133+ self.ok_action(self.datadict)
1134+
1135+ def cancel(self):
1136+ """Trigger this pane's 'Cancel' action."""
1137+ self.cancel_action()
4981138
4991139
5001140 class OutputDirPane(tkpane.TkPane):
@@ -771,124 +1411,99 @@
7711411
7721412
7731413
774-class InputFilePane(tkpane.TkPane):
775- """Get and display an input filename.
1414+class ScalePane(tkpane.TkPane):
1415+ """Display a Tkinter Scale widget.
7761416
777- :param optiondict: a dictionary of option names and values for the Tkinter 'askopenfilename' method (optional).
1417+ :param pane_name: The name to be used to identify this pane in status messages.
1418+ :param orientation:
1419+ :param length:
1420+ :param min_value:
1421+ :param max_value:
1422+ :param init_value:
1423+ :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 EntryPane objects on the same UI (optional).
1424+ :param config_opts: A dictionary of scale widget configuration options
1425+
1426+ Data keys managed by this pane: "scale" or the key name specified during initialization.
7781427
779- Data key managed by this pane: "input_filename".
1428+ Name used by this pane: user-defined on initialization.
7801429
781- Name used by this pane: "Input filename".
1430+ This pane (the Scale widget) is considered to always have valid data.
7821431
7831432 Overridden methods:
7841433
7851434 * entry_widgets
786- * valid_data
7871435 * save_data
788- * clear_pane
1436+ * enable_pane
7891437 * disable_pane
790- * enable_pane
7911438 * set_style
7921439 * focus
793- * set_data
1440+
1441+ Custom methods:
1442+
1443+ * scalewidget
1444+ * set_key
7941445 """
7951446
796- def __init__(self, parent, optiondict=None):
797- tkpane.TkPane.__init__(self, parent, "Input filename", frame_config_opts(), frame_grid_opts())
798- # Customize attributes
799- self.optiondict = {} if optiondict is None else optiondict
800- self.datakey = "input_filename"
801- self.datakeylist = [self.datakey]
802- # Create, configure, and place widgets.
803- self.dir_label = ttk.Label(self, text='Input file:', width=12, anchor=tk.E)
804- self.file_var = tk.StringVar()
805- self.file_var.trace("w", self.check_entrychange)
806- self.file_display = ttk.Entry(self, textvariable=self.file_var)
807- self.valid_color = self.file_display.cget("background")
808- self.browse_button = ttk.Button(self, text='Browse', width=8, command=self.set_inputfile)
809- self.dir_label.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
810- self.file_display.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
811- self.browse_button.grid(row=1, column=1, padx=3, pady=1, sticky=tk.W)
812- self.columnconfigure(0, weight=0)
813- self.columnconfigure(1, weight=1)
814- self.rowconfigure(1, weight=1)
815- parent.rowconfigure(0, weight=0)
1447+ def __init__(self, parent, pane_name, orientation, length, min_value, max_value, init_value, key_name=None, config_opts=None):
1448+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1449+ self.datakeyname = "scale" if key_name is None else key_name
1450+ self.datakeylist = [self.datakeyname]
1451+ self.entry_var = tk.DoubleVar()
1452+ self.entry_var.set(init_value)
1453+ self.entrywidget = ttk.Scale(self, variable=self.entry_var, length=length, orient=orientation, from_=min_value, to=max_value, value=init_value)
1454+ if config_opts is not None:
1455+ self.entrywidget.configure(**config_opts)
1456+ sticky = tk.EW if orientation == tk.HORIZONTAL else tk.NS
1457+ self.entrywidget.grid(row=0, column=0, padx=3, pady=3, sticky=sticky)
1458+ self.rowconfigure(0, weight=1)
1459+ self.columnconfigure(0, weight=1)
1460+ parent.rowconfigure(0, weight=1)
8161461 parent.columnconfigure(0, weight=1)
1462+ self.entry_var.trace("w", self.check_entrychange)
8171463
8181464 #---------------------------------------------------------------------------
8191465 # Overrides of class methods.
8201466 #...........................................................................
8211467
8221468 def entry_widgets(self):
823- """Return a list of widgets used for data entry."""
824- return [self.file_display]
1469+ return [self.entrywidget]
8251470
826- def valid_data(self, widget):
827- """Return True or False indicating the validity of the filename entry.
828-
829- Overrides TkPane class method.
830- """
831- import os.path
832- filename = self.file_display.get()
833- if filename == "":
834- return not self.required
835- else:
836- return os.path.isfile(filename)
837-
8381471 def save_data(self, is_valid, entry_widget):
839- """Update the pane's data dictionary with data from the entry widget.
840-
841- Overrides TkPane class method.
842- """
843- # entry_widget should be self.file_display.
844- filename = self.file_display.get()
845- if is_valid:
846- if filename == "":
847- self.clear_own()
848- else:
849- self.datadict[self.datakey] = filename
850- else:
851- self.clear_own()
852-
853-
854- def clear_pane(self):
855- self.file_var.set(u'')
1472+ """Update the pane's data dictionary with data from the Scale widget."""
1473+ scale_value = self.entry_var.get()
1474+ self.datadict[self.datakeyname] = scale_value
8561475
8571476 def enable_pane(self):
858- self._enablewidgets([self.dir_label, self.file_display, self.browse_button])
859-
1477+ self._enablewidgets([self.entrywidget])
1478+
8601479 def disable_pane(self):
861- self._disablewidgets([self.dir_label, self.file_display, self.browse_button])
862-
1480+ self._disablewidgets([self.entrywidget])
1481+
8631482 def set_style(self, ttk_style):
864- self._setstyle([self.dir_label, self.file_display, self.browse_button], ttk_style)
1483+ self._setstyle([self.entrywidget], ttk_style)
8651484
8661485 def focus(self):
8671486 """Set the focus to the entry."""
868- self.file_display.focus_set()
869-
870- def set_data(self, data):
871- """Update the pane's data dictionary with the provide data.
872-
873- Special key supported: 'input_filename' changes the filename in the entry widget.
874- """
875- if self.datakey in data:
876- self.file_var.set(data[self.datakey])
877- self.handle_change_validity(True, self.file_display)
878- self.send_status_message(True)
879- self.set_allbut(data, [self.datakey])
1487+ self.entrywidget.focus_set()
8801488
8811489 #---------------------------------------------------------------------------
8821490 # Custom methods.
8831491 #...........................................................................
1492+
1493+ def scalewidget(self):
1494+ """Returns the Scale widget."""
1495+ return self.entrywidget
8841496
1497+ def check_entrychange(self, *args):
1498+ self.handle_change_validity(self.valid_data(None), self.entrywidget)
1499+
8851500 def set_key(self, key_name):
8861501 """Change the name of the data key used for the entered data.
8871502
8881503 :param key_name: New name for the data key.
8891504
8901505 This method allows the name of the data key to be customized to
891- eliminate conflicts with other InputFilePane objects on the same UI.
1506+ eliminate conflicts with other ScalePane objects on the same UI.
8921507 """
8931508 if self.datakeyname in self.datadict:
8941509 self.datadict[key_name] = self.datadict[self.datakeyname]
@@ -896,183 +1511,234 @@
8961511 self.datakeyname = key_name
8971512 self.datakeylist = [key_name]
8981513
899- def check_entrychange(self, *args):
900- self.handle_change_validity(self.valid_data(None), self.file_display)
901-
902- def set_inputfile(self):
903- fn = tk_file.askopenfilename(**self.optiondict)
904- if fn != "":
905- # The order of the following steps is important.
906- self.file_var.set(fn)
907- self.handle_change_validity(True, self.file_display)
908- self.send_status_message(True)
909-
9101514
911-class TextPane(tkpane.TkPane):
912- """Display a Tkinter Text widget.
913-
914- :param key_name: The name to be used with the internal data dictionary to identify the text data; use to avoid name conflicts with other TextPane objecs on the same UI (optional).
915- :param optiondict: A dictionary of option names and values for initial configuration of the Text widget (optional).
916- :parame initial_text: Initial contents for the Text widget (optional).
1515+class ScaleSpinPane(tkpane.TkPane):
1516+ """Display a Scale and Spinbox, both displaying the same value.
9171517
918- Because of the large number of uses of the Text widget, this pane
919- provides direct access to the Text widget via the 'textwidget' method.
920- To simplify use, this pane also provides direct methods for appending to,
921- replacing, and clearing the contents of the Text widget.
922- The custom methods 'set_status' and 'clear_status' allow a TextPane to
923- be used as a status_reporter callback for any other type of pane.
1518+ :param pane_name: The name to be used to identify this pane in status messages.
1519+ :param prompt: The prompt to be presented in a Label widget adjacent to the Scale widget.
1520+ :param min_value: The minimum value allowed by the controls.
1521+ :param max_value: The maximum value allowed by the controls
1522+ :param init_value: The initial value displayed by the controls.
1523+ :param length: The length of the Scale widget, in pixels (optional; default=100).
1524+ :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 ScaleSpinPane panes on the same UI (optional).
9241525
925- Data keys managed by this pane: "text" or the key name specified during initialization.
926-
927- Name used by this pane: "Text".
1526+ The Scale widget is horizontal, with the prompt to the left of it and the
1527+ Spinbox to the right of it.
9281528
9291529 Overridden methods:
9301530
9311531 * entry_widgets
932- * save_data
933- * valid_data
934- * clear_pane
9351532 * enable_pane
9361533 * disable_pane
9371534 * set_style
9381535 * focus
939- * set_data
9401536
9411537 Custom methods:
9421538
943- * textwidget
944- * replace_all
945- * append
946- * set_status
947- * clear_status
1539+ * scalewidget
1540+ * spinwidget
9481541 * set_key
9491542 """
950-
951- def __init__(self, parent, key_name=None, optiondict=None, initial_text=None):
952- tkpane.TkPane.__init__(self, parent, "Text", frame_config_opts(), frame_grid_opts())
953- opts = {} if optiondict is None else optiondict
954- self.datakeyname = "text" if key_name is None else key_name
1543+ def __init__(self, parent, pane_name, prompt, min_value, max_value, init_value, length=None, key_name=None,
1544+ scale_config_opts=None, spin_config_opts=None):
1545+ tkpane.TkPane.__init__(self, parent, pane_name, {}, {})
1546+ self.datakeyname = "scalespin" if key_name is None else key_name
9551547 self.datakeylist = [self.datakeyname]
956- self.textwidget = tk.Text(self, exportselection=False, **opts)
957- self.textwidget.bind("<Key>", self.check_entrychange)
958- self.ysb = ttk.Scrollbar(self, orient='vertical', command=self.textwidget.yview)
959- self.xsb = ttk.Scrollbar(self, orient='horizontal', command=self.textwidget.xview)
960- self.textwidget.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
961- if initial_text is not None:
962- self.replace_all(initial_text)
963- self.textwidget.grid(row=0, column=0, padx=(3,0), pady=(3,0), sticky=tk.NSEW)
964- self.ysb.grid(column=1, row=0, padx=(0,3), pady=(3,0), sticky=tk.NS)
965- self.xsb.grid(column=0, row=1, padx=(3,0), pady=(0,3), sticky=tk.EW)
1548+ self.entry_var = tk.DoubleVar()
1549+ self.entry_var.set(init_value)
1550+ self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
1551+ self.entrywidget = ttk.Scale(self, variable=self.entry_var, orient=tk.HORIZONTAL, from_=min_value, to=max_value, value=init_value)
1552+ if length is not None:
1553+ self.entrywidget.configure(length=length)
1554+ self.spinwidget = tk.Spinbox(self, from_=min_value, to=max_value, textvariable=self.entry_var, width=len(str(max_value))+1, exportselection=False)
1555+ if scale_config_opts is not None:
1556+ self.entrywidget.configure(**scale_config_opts)
1557+ if spin_config_opts is not None:
1558+ self.spinwidget.configure(**spin_config_opts)
1559+ self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.E)
1560+ self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
1561+ self.spinwidget.grid(row=0, column=2, padx=3, pady=3, sticky=tk.W)
9661562 self.rowconfigure(0, weight=1)
967- self.columnconfigure(0, weight=1)
1563+ self.columnconfigure(0, weight=0)
1564+ self.columnconfigure(1, weight=1)
1565+ self.columnconfigure(2, weight=0)
9681566 parent.rowconfigure(0, weight=1)
9691567 parent.columnconfigure(0, weight=1)
1568+ self.entry_var.trace("w", self.check_entrychange)
9701569
9711570 #---------------------------------------------------------------------------
9721571 # Overrides of class methods.
9731572 #...........................................................................
9741573
9751574 def entry_widgets(self):
976- return [self.textwidget]
977-
978- def save_data(self, is_valid, entry_widget):
979- """Update the pane's data dictionary with data from the text widget."""
980- text = self.textwidget.get("1.0", tk.END)
981- if is_valid:
982- if text == "":
983- self.clear_own()
984- else:
985- self.datadict["text"] = text
986- else:
987- self.clear_own()
1575+ return [self.entrywidget, self.spinwidget]
9881576
989- def valid_data(self, entry_widget=None):
990- text = self.textwidget.get("1.0", tk.END)
991- return not (text == "" and self.required)
992-
993- def clear_pane(self):
994- widget_state = self.textwidget.cget("state")
995- self.textwidget.configure(state=tk.NORMAL)
996- self.textwidget.delete("1.0", tk.END)
997- self.textwidget.configure(state=widget_state)
998-
1577+ def save_data(self, is_valid, entry_widget):
1578+ scale_value = self.entry_var.get()
1579+ self.datadict[self.datakeyname] = scale_value
1580+
9991581 def enable_pane(self):
1000- self._enablewidgets([self.textwidget, self.xsb, self.ysb])
1001- self.textwidget.configure(state=tk.NORMAL)
1582+ """Enable the scale and spinbox widgets."""
1583+ self._enablewidgets([self.entrywidget, self.spinwidget])
10021584
10031585 def disable_pane(self):
1004- self._disablewidgets([self.xsb, self.ysb])
1005- self.textwidget.configure(state=tk.DISABLED)
1586+ """Disable the scale and spinbox widgets."""
1587+ self._disablewidgets([self.entrywidget, self.spinwidget])
10061588
10071589 def set_style(self, ttk_style):
1008- """Sets the style of the scrollbars.
1009-
1010- Note that the Text widget is not a ttk themed widget, and so no ttk style can be applied.
1011- """
1012- self._setstyle([self.xsb, self.ysb], ttk_style)
1590+ """Change the style of the scale widget."""
1591+ self._setstyle([self.entrywidget], ttk_style)
10131592
10141593 def focus(self):
1015- """Set the focus to the text widget."""
1016- self.textwidget.focus_set()
1017-
1018- def set_data(self, data):
1019- """Update the pane's data dictionary with the provided data.
1020-
1021- Special keys supported: 'text' changes the contents of the text widget.
1022- """
1023- if "text" in data:
1024- self.replace_all(data["text"])
1025- self.set_allbut(data, ["text"])
1594+ """Set the focus to the scale widget."""
1595+ self.entrywidget.focus_set()
10261596
10271597 #---------------------------------------------------------------------------
10281598 # Custom methods.
10291599 #...........................................................................
1030-
1031- def textwidget(self):
1032- """Return the text widget object, to allow direct manipulation."""
1033- return self.textwidget
1034-
1035- def check_entrychange(self, *args):
1036- self.handle_change_validity(self.valid_data(None), self.textwidget)
1037-
1038- def replace_all(self, new_contents):
1039- self.clear_pane()
1040- self.textwidget.insert(tk.END, new_contents)
1041- self.datadict["text"] = new_contents
10421600
1043- def append(self, more_text, scroll=True):
1044- """Inserts the given text at the end of the Text widget's contents."""
1045- widget_state = self.textwidget.cget("state")
1046- self.textwidget.configure(state=tk.NORMAL)
1047- self.textwidget.insert(tk.END, more_text)
1048- if scroll:
1049- self.textwidget.see("end")
1050- self.textwidget.configure(state=widget_state)
1051-
1052- def set_status(self, status_msg):
1053- """Inserts the status message at the end of the Text widget's contents."""
1054- if len(status_msg) > 0 and status_msg[-1] != u"\n":
1055- status_msg += u"\n"
1056- self.append(status_msg)
1057-
1058- def clear_status(self):
1059- """Clear the entire widget."""
1060- self.clear()
1601+ def scalewidget(self):
1602+ """Return the Scale widget."""
1603+ return self.entrywidget
1604+
1605+ def spinwidget(self):
1606+ """Return the Spinbox widget."""
1607+ return self.spinwidget
1608+
1609+ def check_entrychange(self, *args):
1610+ self.handle_change_validity(self.valid_data(None))
10611611
10621612 def set_key(self, key_name):
1063- """Change the name of the data key used for the text data.
1613+ """Change the name of the data key used for the entered data.
10641614
1065- :param key_name: New name for the text data key.
1615+ :param key_name: New name for the data key.
10661616
10671617 This method allows the name of the data key to be customized to
1068- eliminate conflicts with other TextPane objects on the same UI.
1618+ eliminate conflicts with other ScalePane objects on the same UI.
10691619 """
10701620 if self.datakeyname in self.datadict:
10711621 self.datadict[key_name] = self.datadict[self.datakeyname]
10721622 del self.datadict[self.datakeyname]
10731623 self.datakeyname = key_name
10741624 self.datakeylist = [key_name]
1625+
1626+
1627+
1628+class SpinboxPane(tkpane.TkPane):
1629+ """Display a Tkinter Spinbox widget with a prompt.
10751630
1631+ :param pane_name: The name to be used to identify this pane in status messages.
1632+ :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
1633+ :param min_value: The minimum value that can be selected.
1634+ :param max_value: The maximum value that can be selected.
1635+ :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 EntryPane objects on the same UI (optional).
1636+
1637+ Data keys managed by this pane: "spinbox" or the key name specified during initialization.
1638+
1639+ Name used by this pane: user-defined on initialization.
1640+
1641+ Overridden methods:
1642+
1643+ * entry_widgets
1644+ * valid_data
1645+ * save_data
1646+ * clear_pane
1647+ * enable_pane
1648+ * disable_pane
1649+ * focus
1650+ * set_data
1651+
1652+ Custom method:
1653+
1654+ * set_key
1655+ """
1656+
1657+ def __init__(self, parent, pane_name, prompt, min_value, max_value, key_name=None, optiondict=None):
1658+ tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1659+ self.min_value = min_value
1660+ self.datakeyname = "spinbox" if key_name is None else key_name
1661+ self.datakeylist = [self.datakeyname]
1662+ self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
1663+ self.entry_var = tk.StringVar()
1664+ self.entrywidget = tk.Spinbox(self, from_=min_value, to=max_value, textvariable=self.entry_var, width=len(str(max_value))+1, exportselection=False)
1665+ self.entry_var.set(min_value)
1666+ if optiondict is not None:
1667+ self.entrywidget.configure(**optiondict)
1668+ self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
1669+ self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.W)
1670+ self.rowconfigure(0, weight=1)
1671+ self.columnconfigure(0, weight=0)
1672+ self.columnconfigure(1, weight=1)
1673+ parent.rowconfigure(0, weight=1)
1674+ parent.columnconfigure(0, weight=1)
1675+ self.entry_var.trace("w", self.check_entrychange)
1676+
1677+ #---------------------------------------------------------------------------
1678+ # Overrides of class methods.
1679+ #...........................................................................
1680+
1681+ def entry_widgets(self):
1682+ return [self.entrywidget]
1683+
1684+ def valid_data(self, entry_widget=None):
1685+ text = self.entry_var.get()
1686+ return not (text == "" and self.required)
1687+
1688+ def save_data(self, is_valid, entry_widget):
1689+ """Update the pane's data dictionary with data from the Spinbox widget."""
1690+ text = self.entry_var.get()
1691+ if is_valid:
1692+ if text == "":
1693+ self.clear_own()
1694+ else:
1695+ self.datadict[self.datakeyname] = text
1696+ else:
1697+ self.clear_own()
1698+
1699+ def clear_pane(self):
1700+ self.entry_var.set(self.min_value)
1701+
1702+ def enable_pane(self):
1703+ self._enablewidgets([self.prompt, self.entrywidget])
1704+
1705+ def disable_pane(self):
1706+ self._disablewidgets([self.prompt, self.entrywidget])
1707+
1708+ def focus(self):
1709+ """Set the focus to the entry."""
1710+ self.entrywidget.focus_set()
1711+
1712+ def set_data(self, data):
1713+ """Update the pane's data dictionary with the provided data.
1714+
1715+ Special key supported: 'prompt' changes the pane's prompt.
1716+ """
1717+ spkey = "prompt"
1718+ if spkey in data:
1719+ self.prompt.configure(text=data[spkey])
1720+ self.set_allbut(data, [spkey])
1721+
1722+ #---------------------------------------------------------------------------
1723+ # Custom methods.
1724+ #...........................................................................
1725+
1726+ def check_entrychange(self, *args):
1727+ self.handle_change_validity(self.valid_data(None), self.entrywidget)
1728+
1729+ def set_key(self, key_name):
1730+ """Change the name of the data key used for the entered data.
1731+
1732+ :param key_name: New name for the data key.
1733+
1734+ This method allows the name of the data key to be customized to
1735+ eliminate conflicts with other SpinboxPane objects on the same UI.
1736+ """
1737+ if self.datakeyname in self.datadict:
1738+ self.datadict[key_name] = self.datadict[self.datakeyname]
1739+ del self.datadict[self.datakeyname]
1740+ self.datakeyname = key_name
1741+ self.datakeylist = [key_name]
10761742
10771743
10781744 class StatusProgressPane(tkpane.TkPane):
@@ -1470,291 +2136,30 @@
14702136 self.datakeylist = [key_name]
14712137
14722138
1473-class OkCancelPane(tkpane.TkPane):
1474- """Display OK and Cancel buttons.
1475-
1476- There are no data keys specific to this pane.
1477-
1478- Overridden methods:
1479-
1480- * disable_pane
1481- * enable_pane
1482- * set_style
1483- * focus
1484-
1485- Custom methods:
1486-
1487- * set_cancel_action
1488- * set_ok_action
1489- * ok
1490- * cancel
1491- """
1492- def __init__(self, parent, ok_action=None, cancel_action=None):
1493- def do_nothing(data_dict):
1494- pass
1495- tkpane.TkPane.__init__(self, parent, "OK/Cancel", frame_config_opts(), frame_grid_opts())
1496- self.ok_action = ok_action if ok_action is not None else do_nothing
1497- self.cancel_action = cancel_action if cancel_action is not None else do_nothing
1498- self.cancel_btn = ttk.Button(self, text="Cancel", command=self.cancel_action)
1499- self.cancel_btn.grid(row=0, column=1, padx=3, sticky=tk.E)
1500- self.ok_btn = ttk.Button(self, text="OK", command=self.ok_action)
1501- self.ok_btn.grid(row=0, column=0, padx=3, sticky=tk.E)
1502- self.columnconfigure(0, weight=1)
1503- self.columnconfigure(1, weight=0)
1504- self.rowconfigure(0, weight=1)
1505- parent.rowconfigure(0, weight=0)
1506- parent.columnconfigure(0, weight=1)
1507-
1508- def enable_pane(self):
1509- self._enablewidgets([self.ok_btn])
15102139
1511- def disable_pane(self):
1512- self._disablewidgets([self.ok_btn])
1513-
1514- def set_style(self, ttk_style):
1515- self._setstyle([self.cancel_btn, self.ok_btn], ttk_style)
2140+class TextPane(tkpane.TkPane):
2141+ """Display a Tkinter Text widget.
15162142
1517- def focus(self):
1518- """Set the focus to the OK button."""
1519- self.ok_btn.focus_set()
1520-
1521- def set_ok_action(self, ok_action):
1522- """Specify the callback function to be called when the 'OK' button is clicked."""
1523- self.ok_action = ok_action if ok_action is not None else do_nothing
1524- self.ok_btn.configure(command=self.ok_action)
1525-
1526- def set_cancel_action(self, cancel_action):
1527- """Specify the callback function to be called when the 'Cancel' button is clicked."""
1528- self.cancel_action = cancel_action if cancel_action is not None else do_nothing
1529- self.cancel_btn.configure(command=self.cancel_action)
1530-
1531- def ok(self):
1532- """Trigger this pane's 'OK' action. The callback function will be passed this pane's data dictionary."""
1533- self.ok_action(self.datadict)
1534-
1535- def cancel(self):
1536- """Trigger this pane's 'Cancel' action."""
1537- self.cancel_action()
1538-
1539-
1540-class EntryPane(tkpane.TkPane):
1541- """Display a Tkinter Entry widget.
2143+ :param key_name: The name to be used with the internal data dictionary to identify the text data; use to avoid name conflicts with other TextPane objecs on the same UI (optional).
2144+ :param optiondict: A dictionary of option names and values for initial configuration of the Text widget (optional).
2145+ :parame initial_text: Initial contents for the Text widget (optional).
15422146
1543- :param pane_name: The name to be used to identify this pane in status messages.
1544- :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
1545- :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 EntryPane objects on the same UI (optional).
1546-
1547- Data keys managed by this pane: "entry" or the key name specified during initialization.
2147+ Because of the large number of uses of the Text widget, this pane
2148+ provides direct access to the Text widget via the 'textwidget' method.
2149+ To simplify use, this pane also provides direct methods for appending to,
2150+ replacing, and clearing the contents of the Text widget.
2151+ The custom methods 'set_status' and 'clear_status' allow a TextPane to
2152+ be used as a status_reporter callback for any other type of pane.
15482153
1549- Name used by this pane: user-defined on initialization.
2154+ Data keys managed by this pane: "text" or the key name specified during initialization.
2155+
2156+ Name used by this pane: "Text".
15502157
15512158 Overridden methods:
15522159
15532160 * entry_widgets
1554- * valid_data
15552161 * save_data
1556- * clear_pane
1557- * enable_pane
1558- * disable_pane
1559- * set_style
1560- * focus
1561- * set_data
1562-
1563- Custom method:
1564-
1565- * set_key
1566- """
1567-
1568- def __init__(self, parent, pane_name, prompt, key_name=None):
1569- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1570- self.datakeyname = "entry" if key_name is None else key_name
1571- self.datakeylist = [self.datakeyname]
1572- self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
1573- self.entry_var = tk.StringVar()
1574- self.entrywidget = ttk.Entry(self, textvariable=self.entry_var, exportselection=False)
1575- self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
1576- self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.EW)
1577- self.rowconfigure(0, weight=1)
1578- self.columnconfigure(0, weight=0)
1579- self.columnconfigure(1, weight=1)
1580- parent.rowconfigure(0, weight=1)
1581- parent.columnconfigure(0, weight=1)
1582- self.entry_var.trace("w", self.check_entrychange)
1583-
1584- #---------------------------------------------------------------------------
1585- # Overrides of class methods.
1586- #...........................................................................
1587-
1588- def entry_widgets(self):
1589- return [self.entrywidget]
1590-
1591- def valid_data(self, entry_widget=None):
1592- text = self.entry_var.get()
1593- return not (text == "" and self.required)
1594-
1595- def save_data(self, is_valid, entry_widget):
1596- """Update the pane's data dictionary with data from the Entry widget."""
1597- text = self.entry_var.get()
1598- if is_valid:
1599- if text == "":
1600- self.clear_own()
1601- else:
1602- self.datadict[self.datakeyname] = text
1603- else:
1604- self.clear_own()
1605-
1606- def clear_pane(self):
1607- self.entry_var.set("")
1608-
1609- def enable_pane(self):
1610- self._enablewidgets([self.prompt, self.entrywidget])
1611-
1612- def disable_pane(self):
1613- self._disablewidgets([self.prompt, self.entrywidget])
1614-
1615- def set_style(self, ttk_style):
1616- self._setstyle([self.prompt, self.entrywidget], ttk_style)
1617-
1618- def focus(self):
1619- """Set the focus to the entry."""
1620- self.entrywidget.focus_set()
1621-
1622- def set_data(self, data):
1623- """Update the pane's data dictionary with the provided data.
1624-
1625- Special key supported: 'prompt' changes the pane's prompt.
1626- """
1627- spkey = "prompt"
1628- if spkey in data:
1629- self.prompt.configure(text=data[spkey])
1630- self.set_allbut(data, [spkey])
1631-
1632- #---------------------------------------------------------------------------
1633- # Custom methods.
1634- #...........................................................................
1635-
1636- def check_entrychange(self, *args):
1637- self.handle_change_validity(self.valid_data(None), self.entrywidget)
1638-
1639- def set_key(self, key_name):
1640- """Change the name of the data key used for the entered data.
1641-
1642- :param key_name: New name for the data key.
1643-
1644- This method allows the name of the data key to be customized to
1645- eliminate conflicts with other EntryPane objects on the same UI.
1646- """
1647- if self.datakeyname in self.datadict:
1648- self.datadict[key_name] = self.datadict[self.datakeyname]
1649- del self.datadict[self.datakeyname]
1650- self.datakeyname = key_name
1651- self.datakeylist = [key_name]
1652-
1653-
1654-class ButtonPane(tkpane.TkPane):
1655- """Display a simple text button with configurable text and action.
1656-
1657- :param pane_name: The name to be used to identify this pane in status messages.
1658- :param button_text: The text to display on the button.
1659- :param button_action: A callback to perform an action when the button is clicked.
1660- :param width: The width of the button, in characters (optional).
1661-
1662- There are no data keys specific to this pane.
1663-
1664- Overridden methods:
1665-
1666- * disable_pane
1667- * enable_pane
1668- * set_style
1669- * focus
1670- * set_data
1671-
1672- Custom methods:
1673-
1674- * set_button_action
1675- * do_button_action
1676- """
1677- def __init__(self, parent, button_text, pane_name="button", button_action=None, width=None):
1678- def do_nothing(data_dict):
1679- pass
1680- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1681- self.button_text = button_text
1682- self.action = button_action if button_action is not None else do_nothing
1683- self.btn = ttk.Button(self, text=self.button_text, command=self.do_button_action)
1684- if width is not None:
1685- self.btn.configure(width=width)
1686- self.btn.grid(row=0, column=1, padx=3, sticky=tk.E)
1687- self.columnconfigure(0, weight=1)
1688- self.rowconfigure(0, weight=1)
1689- parent.rowconfigure(0, weight=0)
1690- parent.columnconfigure(0, weight=1)
1691-
1692- def enable_pane(self):
1693- self._enablewidgets([self.btn])
1694-
1695- def disable_pane(self):
1696- self._disablewidgets([self.btn])
1697-
1698- def set_style(self, ttk_style):
1699- self._setstyle([self.btn], ttk_style)
1700-
1701- def focus(self):
1702- """Set the focus to the button."""
1703- self.btn.focus_set()
1704-
1705- def set_data(self, data_dict):
1706- """Update the pane's data dictionary with the provided data.
1707-
1708- Special keys are:
1709- * button_text: Contains the text to place on the button.
1710- * button_width: Contains the width for the button.
1711-
1712- All other data in the passed dictionary are added to the button's own data dictionary.
1713- """
1714- if "button_text" in data_dict:
1715- self.btn.configure(text=data_dict["button_text"])
1716- if "button_width" in data_dict:
1717- self.btn.configure(width=data_dict["button_width"])
1718- self.set_allbut(data, ["button_text", "button_width"])
1719-
1720- def set_button_action(self, button_action):
1721- """Specify the callback function to be called when the button is clicked."""
1722- self.action = button_action if button_action is not None else do_nothing
1723- self.btn.configure(command=self.action)
1724-
1725- def do_button_action(self):
1726- """Trigger this pane's action. The callback function will be passed this pane's data dictionary."""
1727- self.action(self.datadict)
1728-
1729-
1730-class EmptyPane(tkpane.TkPane):
1731- """A pane with no widgets that can be used as a spacer."""
1732- def __init__(self, parent):
1733- tkpane.TkPane.__init__(self, parent, "empty_pane", frame_config_opts(), frame_grid_opts())
1734- self.pack(expand=True, fill=tk.BOTH)
1735- parent.rowconfigure(0, weight=1)
1736- parent.columnconfigure(0, weight=1)
1737-
1738-
1739-class CheckboxPane(tkpane.TkPane):
1740- """Display a Tkinter Checkbutton widget to accept True and False values.
1741-
1742- :param pane_name: The name to be used to identify this pane in status messages.
1743- :param prompt: The text associated with the checkbox.
1744- :param valid_state: Either True or False to indicate that either the checked or unchecked state (only) is to be considered valid. If not specified (the default), the checkbox will always be considered to have valid data.
1745- :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 CheckboxPane objects on the same UI (optional).
1746- :param config_opts: A dictionary of configuration options for the Checkbutton widget.
1747-
1748- Data keys managed by this pane: "check" or the key name specified during initialization.
1749- The value of this item is always True or False, and defaults to False.
1750-
1751- Name used by this pane: user-defined on initialization.
1752-
1753- Overridden methods:
1754-
1755- * entry_widgets
17562162 * valid_data
1757- * save_data
17582163 * clear_pane
17592164 * enable_pane
17602165 * disable_pane
@@ -1764,262 +2169,29 @@
17642169
17652170 Custom methods:
17662171
1767- * set_key
1768- """
1769-
1770- def __init__(self, parent, pane_name, prompt, valid_state=None, key_name=None, config_opts=None):
1771- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1772- self.valid_state = valid_state
1773- self.datakeyname = "check" if key_name is None else key_name
1774- self.datakeylist = [self.datakeyname]
1775- self.checkvar = tk.BooleanVar()
1776- self.checkvar.set(False)
1777- self.datadict[self.datakeyname] = self.checkvar.get()
1778- self.checkbox = ttk.Checkbutton(self, text=prompt, variable=self.checkvar, onvalue=True, offvalue=False)
1779- if config_opts is not None:
1780- self.checkbox.configure(**config_opts)
1781- self.checkbox.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
1782- self.rowconfigure(0, weight=1)
1783- self.columnconfigure(0, weight=1)
1784- parent.rowconfigure(0, weight=1)
1785- parent.columnconfigure(0, weight=1)
1786- self.checkvar.trace("w", self.check_checkchange)
1787-
1788- #---------------------------------------------------------------------------
1789- # Overrides of class methods.
1790- #...........................................................................
1791-
1792- def entry_widgets(self):
1793- return [self.checkbox]
1794-
1795- def valid_data(self, widget):
1796- """Returns an indication of whether the checkbox is in a valid state."""
1797- if self.valid_state is None:
1798- return True
1799- else:
1800- return self.checkvar.get() == self.valid_state
1801-
1802- def save_data(self, is_valid, entry_widget):
1803- """Update the pane's data dictionary with data from the Checkbutton widget."""
1804- state = self.checkvar.get()
1805- self.datadict[self.datakeyname] = state
1806-
1807- def clear_pane(self):
1808- self.checkvar.set(False)
1809-
1810- def enable_pane(self):
1811- self._enablewidgets([self.checkbox])
1812-
1813- def disable_pane(self):
1814- self._disablewidgets([self.checkbox])
1815-
1816- def set_style(self, ttk_style):
1817- self._setstyle([self.checkbox], ttk_style)
1818-
1819- def focus(self):
1820- """Set the focus to the checkbox."""
1821- self.checkbox.focus_set()
1822-
1823- def set_data(self, data):
1824- """Update the pane's data dictionary with the provided data.
1825-
1826- Special key supported: 'prompt' changes the pane's prompt.
1827- """
1828- spkey = "prompt"
1829- if spkey in data:
1830- self.checkbox.configure(text=data[spkey])
1831- self.set_allbut(data, [spkey])
1832-
1833- #---------------------------------------------------------------------------
1834- # Custom methods.
1835- #...........................................................................
1836-
1837- def check_checkchange(self, *args):
1838- self.handle_change_validity(self.valid_data(self.checkbox), self.checkbox)
1839-
1840- def set_key(self, key_name):
1841- """Change the name of the data key used for the entered data.
1842-
1843- :param key_name: New name for the data key.
1844-
1845- This method allows the name of the data key to be customized to
1846- eliminate conflicts with other CheckboxPane objects on the same UI.
1847- """
1848- if self.datakeyname in self.datadict:
1849- self.datadict[key_name] = self.datadict[self.datakeyname]
1850- del self.datadict[self.datakeyname]
1851- self.datakeyname = key_name
1852- self.datakeylist = [key_name]
1853-
1854-
1855-class ListboxPane(tkpane.TkPane):
1856- """Display a Tkinter Listbox.
1857-
1858- :param pane_name: The name to be used to identify this pane in status messages.
1859- :param items: The list of items to be initially displayed in the listbox.
1860- :param rows: The number of rows (items) to be shown; the listbox will have a scrollbar (optional).
1861- :param key_name: The name to be used with the internal data dictionary to identify the selected list entries; use to avoid name conflicts with other ListboxPane objects on the same UI (optional).
1862- :param mode: The selection mode to use; "single", "browse", "multiple", or "extended" (optional; default is "extended").
1863-
1864- Data key managed by this pane: "listbox" or the key name specified during initialization.
1865-
1866- The value of the data managed by this pane is a list of the selected items.
1867-
1868- Name used by this pane: user-defined on initialization.
1869-
1870- Overridden methods:
1871-
1872- * entry_widgets
1873- * valid_data
1874- * save_data
1875- * clear_pane
1876- * enable_pane
1877- * disable_pane
1878- * set_style
1879- * focus
1880-
1881- Custom methods:
1882-
1883- * set_newitems
2172+ * textwidget
2173+ * replace_all
2174+ * append
2175+ * set_status
2176+ * clear_status
18842177 * set_key
18852178 """
18862179
1887- def __init__(self, parent, pane_name, items, rows=None, width=None, key_name=None, mode=None):
1888- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
1889- self.datakeyname = "listbox" if key_name is None else key_name
2180+ def __init__(self, parent, key_name=None, optiondict=None, initial_text=None):
2181+ tkpane.TkPane.__init__(self, parent, "Text", frame_config_opts(), frame_grid_opts())
2182+ opts = {} if optiondict is None else optiondict
2183+ self.datakeyname = "text" if key_name is None else key_name
18902184 self.datakeylist = [self.datakeyname]
1891- self.scroller = ttk.Scrollbar(self, orient=tk.VERTICAL)
1892- ht = 10 if rows is None else rows
1893- selmode = "extended" if mode is None else mode
1894- self.listbox = tk.Listbox(self, selectmode=selmode, exportselection=False, yscrollcommand=self.scroller.set, height=ht)
1895- if width is not None:
1896- self.listbox.configure(width=width)
1897- self.listbox.bind("<<ListboxSelect>>", self.check_entrychange)
1898- self.scroller.config(command=self.listbox.yview)
1899- for item in items:
1900- self.listbox.insert(tk.END, item)
1901- self.listbox.grid(row=0, column=0, padx=(3,0), pady=3, sticky=tk.NSEW)
1902- self.scroller.grid(row=0, column=1, padx=(0,3), pady=3, sticky=tk.NS)
1903- self.rowconfigure(0, weight=1)
1904- self.columnconfigure(0, weight=1)
1905- self.columnconfigure(1, weight=0)
1906- parent.rowconfigure(0, weight=1)
1907- parent.columnconfigure(0, weight=1)
1908-
1909- #---------------------------------------------------------------------------
1910- # Overrides of class methods.
1911- #...........................................................................
1912-
1913- def entry_widgets(self):
1914- return [self.listbox]
1915-
1916- def valid_data(self, widget):
1917- """Returns an indication of whether the listbox is both required and has at least one selection."""
1918- if self.required:
1919- selected = self.listbox.curselection()
1920- if isinstance(selected, tuple):
1921- return len(selected) > 0
1922- else:
1923- return selected is not None
1924- else:
1925- return True
1926-
1927- def save_data(self, is_valid, entry_widget):
1928- """Update the pane's data dictionary with data from the Listbox widget."""
1929- if is_valid:
1930- items = map(int, self.listbox.curselection())
1931- self.datadict[self.datakeyname] = [self.listbox.get(i) for i in items]
1932- else:
1933- self.clear_own()
1934-
1935- def clear_pane(self):
1936- self.listbox.selection_clear(0, tk.END)
1937-
1938- def enable_pane(self):
1939- self.listbox.configure(state=tk.NORMAL)
1940-
1941- def disable_pane(self):
1942- self.listbox.configure(state=tk.DISABLED)
1943-
1944- def set_style(self, ttk_style):
1945- """Sets the style of the scrollbar accompanying the listbox.
1946-
1947- The Listbox widget is not a themed ttk widget and cannot have a style applied.
1948- """
1949- self._setstyle([self.scroller], ttk_style)
1950-
1951- def focus(self):
1952- """Set the focus to the listbox."""
1953- self.listbox.focus_set()
1954-
1955- #---------------------------------------------------------------------------
1956- # Custom methods.
1957- #...........................................................................
1958-
1959- def check_entrychange(self, event):
1960- self.handle_change_validity(self.valid_data(self.listbox), self.listbox)
1961-
1962- def set_newitems(self, items):
1963- """Change the items displayed in the list. This will clear any selection."""
1964- contents = self.listbox.get(0, tk.END)
1965- if any(list(set(contents) ^ set(items))):
1966- self.clear_pane()
1967- if self.datakeyname in self.datadict:
1968- del self.datadict[self.datakeyname]
1969- self.listbox.delete(0, tk.END)
1970- for item in items:
1971- self.listbox.insert(tk.END, item)
1972- self.handle_change_validity(self.valid_data(self.listbox), self.listbox)
1973-
1974- def set_key(self, key_name):
1975- """Change the name of the data key used for the entered data.
1976-
1977- :param key_name: New name for the data key.
1978-
1979- This method allows the name of the data key to be customized to
1980- eliminate conflicts with other ListboxPane objects on the same UI.
1981- """
1982- if self.datakeyname in self.datadict:
1983- self.datadict[key_name] = self.datadict[self.datakeyname]
1984- del self.datadict[self.datakeyname]
1985- self.datakeyname = key_name
1986- self.datakeylist = [key_name]
1987-
1988-
1989-
1990-class NotebookPane(tkpane.TkPane):
1991- """Create and populate a Tkinter Notebook widget.
1992-
1993- :param pane_name: The name to be used to identify this pane in status messages.
1994- :param tab_specs: A list or tuple of two-element tuples; each two-element tuple contains the tab's label and a `build` function that is passed the Notebook widget and should populate the tab page with widgets and return the frame enclosing all widgets on that page.
1995-
1996- This pane does not manage any data.
1997-
1998- Name used by this pane: user-defined on initialization.
1999-
2000- Overridden methods:
2001-
2002- * set_style
2003-
2004- Custom methods:
2005-
2006- * notebook_widget
2007- """
2008-
2009- def __init__(self, parent, pane_name, tab_specs):
2010- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
2011- self.tabids = {}
2012- self.tabframes = {}
2013- self.nb_widget = ttk.Notebook(self)
2014- id_no = 0
2015- for tab in tab_specs:
2016- tabframe = tab[1](self.nb_widget)
2017- self.nb_widget.add(tabframe, text=tab[0])
2018- self.tabids[tab[0]] = id_no
2019- self.tabframes[tab[0]] = tabframe
2020- id_no += 1
2021- self.nb_widget.enable_traversal()
2022- self.nb_widget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.NSEW)
2185+ self.textwidget = tk.Text(self, exportselection=False, **opts)
2186+ self.textwidget.bind("<Key>", self.check_entrychange)
2187+ self.ysb = ttk.Scrollbar(self, orient='vertical', command=self.textwidget.yview)
2188+ self.xsb = ttk.Scrollbar(self, orient='horizontal', command=self.textwidget.xview)
2189+ self.textwidget.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
2190+ if initial_text is not None:
2191+ self.replace_all(initial_text)
2192+ self.textwidget.grid(row=0, column=0, padx=(3,0), pady=(3,0), sticky=tk.NSEW)
2193+ self.ysb.grid(column=1, row=0, padx=(0,3), pady=(3,0), sticky=tk.NS)
2194+ self.xsb.grid(column=0, row=1, padx=(3,0), pady=(0,3), sticky=tk.EW)
20232195 self.rowconfigure(0, weight=1)
20242196 self.columnconfigure(0, weight=1)
20252197 parent.rowconfigure(0, weight=1)
@@ -2029,381 +2201,405 @@
20292201 # Overrides of class methods.
20302202 #...........................................................................
20312203
2204+ def entry_widgets(self):
2205+ return [self.textwidget]
2206+
2207+ def save_data(self, is_valid, entry_widget):
2208+ """Update the pane's data dictionary with data from the text widget."""
2209+ text = self.textwidget.get("1.0", tk.END)
2210+ if is_valid:
2211+ if text == "":
2212+ self.clear_own()
2213+ else:
2214+ self.datadict["text"] = text
2215+ else:
2216+ self.clear_own()
2217+
2218+ def valid_data(self, entry_widget=None):
2219+ text = self.textwidget.get("1.0", tk.END)
2220+ return not (text == "" and self.required)
2221+
2222+ def clear_pane(self):
2223+ widget_state = self.textwidget.cget("state")
2224+ self.textwidget.configure(state=tk.NORMAL)
2225+ self.textwidget.delete("1.0", tk.END)
2226+ self.textwidget.configure(state=widget_state)
2227+
2228+ def enable_pane(self):
2229+ self._enablewidgets([self.textwidget, self.xsb, self.ysb])
2230+ self.textwidget.configure(state=tk.NORMAL)
2231+
2232+ def disable_pane(self):
2233+ self._disablewidgets([self.xsb, self.ysb])
2234+ self.textwidget.configure(state=tk.DISABLED)
2235+
20322236 def set_style(self, ttk_style):
2033- self._setstyle([self.nb_widget], ttk_style)
2237+ """Sets the style of the scrollbars.
2238+
2239+ Note that the Text widget is not a ttk themed widget, and so no ttk style can be applied.
2240+ """
2241+ self._setstyle([self.xsb, self.ysb], ttk_style)
20342242
2243+ def focus(self):
2244+ """Set the focus to the text widget."""
2245+ self.textwidget.focus_set()
2246+
2247+ def set_data(self, data):
2248+ """Update the pane's data dictionary with the provided data.
2249+
2250+ Special keys supported: 'text' changes the contents of the text widget.
2251+ """
2252+ if "text" in data:
2253+ self.replace_all(data["text"])
2254+ self.set_allbut(data, ["text"])
2255+
20352256 #---------------------------------------------------------------------------
20362257 # Custom methods.
20372258 #...........................................................................
20382259
2039- def notebook_widget(self):
2040- """Return the Notebook widget."""
2041- return self.nb_widget
2042-
2043- def tab_id(self, tab_name):
2044- """Return the tab ID (integer) corresponding to the tab's name or label."""
2045- return self.tabid[tab_name]
2046-
2047- def tab_frames(self):
2048- """Return a dictionary of tab names and the frame enclosing the contents for the tab."""
2049- return self.tabframes
2050-
2051- def tab_frame(self, tab_name):
2052- """Return the frame corresponding to the tab's name or label."""
2053- return self.tabframes[tab_name]
2054-
2055-
2056-
2057-class ScalePane(tkpane.TkPane):
2058- """Display a Tkinter Scale widget.
2260+ def textwidget(self):
2261+ """Return the text widget object, to allow direct manipulation."""
2262+ return self.textwidget
20592263
2060- :param pane_name: The name to be used to identify this pane in status messages.
2061- :param orientation:
2062- :param length:
2063- :param min_value:
2064- :param max_value:
2065- :param init_value:
2066- :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 EntryPane objects on the same UI (optional).
2067- :param config_opts: A dictionary of scale widget configuration options
2068-
2069- Data keys managed by this pane: "scale" or the key name specified during initialization.
2070-
2071- Name used by this pane: user-defined on initialization.
2072-
2073- This pane (the Scale widget) is considered to always have valid data.
2074-
2075- Overridden methods:
2076-
2077- * entry_widgets
2078- * save_data
2079- * enable_pane
2080- * disable_pane
2081- * set_style
2082- * focus
2083-
2084- Custom methods:
2085-
2086- * scalewidget
2087- * set_key
2088- """
2264+ def check_entrychange(self, *args):
2265+ self.handle_change_validity(self.valid_data(None), self.textwidget)
20892266
2090- def __init__(self, parent, pane_name, orientation, length, min_value, max_value, init_value, key_name=None, config_opts=None):
2091- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
2092- self.datakeyname = "scale" if key_name is None else key_name
2093- self.datakeylist = [self.datakeyname]
2094- self.entry_var = tk.DoubleVar()
2095- self.entry_var.set(init_value)
2096- self.entrywidget = ttk.Scale(self, variable=self.entry_var, length=length, orient=orientation, from_=min_value, to=max_value, value=init_value)
2097- if config_opts is not None:
2098- self.entrywidget.configure(**config_opts)
2099- sticky = tk.EW if orientation == tk.HORIZONTAL else tk.NS
2100- self.entrywidget.grid(row=0, column=0, padx=3, pady=3, sticky=sticky)
2101- self.rowconfigure(0, weight=1)
2102- self.columnconfigure(0, weight=1)
2103- parent.rowconfigure(0, weight=1)
2104- parent.columnconfigure(0, weight=1)
2105- self.entry_var.trace("w", self.check_entrychange)
2106-
2107- #---------------------------------------------------------------------------
2108- # Overrides of class methods.
2109- #...........................................................................
2110-
2111- def entry_widgets(self):
2112- return [self.entrywidget]
2113-
2114- def save_data(self, is_valid, entry_widget):
2115- """Update the pane's data dictionary with data from the Scale widget."""
2116- scale_value = self.entry_var.get()
2117- self.datadict[self.datakeyname] = scale_value
2118-
2119- def enable_pane(self):
2120- self._enablewidgets([self.entrywidget])
2267+ def replace_all(self, new_contents):
2268+ self.clear_pane()
2269+ self.textwidget.insert(tk.END, new_contents)
2270+ self.datadict["text"] = new_contents
21212271
2122- def disable_pane(self):
2123- self._disablewidgets([self.entrywidget])
2124-
2125- def set_style(self, ttk_style):
2126- self._setstyle([self.entrywidget], ttk_style)
2272+ def append(self, more_text, scroll=True):
2273+ """Inserts the given text at the end of the Text widget's contents."""
2274+ widget_state = self.textwidget.cget("state")
2275+ self.textwidget.configure(state=tk.NORMAL)
2276+ self.textwidget.insert(tk.END, more_text)
2277+ if scroll:
2278+ self.textwidget.see("end")
2279+ self.textwidget.configure(state=widget_state)
21272280
2128- def focus(self):
2129- """Set the focus to the entry."""
2130- self.entrywidget.focus_set()
2131-
2132- #---------------------------------------------------------------------------
2133- # Custom methods.
2134- #...........................................................................
2281+ def set_status(self, status_msg):
2282+ """Inserts the status message at the end of the Text widget's contents."""
2283+ if len(status_msg) > 0 and status_msg[-1] != u"\n":
2284+ status_msg += u"\n"
2285+ self.append(status_msg)
21352286
2136- def scalewidget(self):
2137- """Returns the Scale widget."""
2138- return self.entrywidget
2139-
2140- def check_entrychange(self, *args):
2141- self.handle_change_validity(self.valid_data(None), self.entrywidget)
2287+ def clear_status(self):
2288+ """Clear the entire widget."""
2289+ self.clear()
21422290
21432291 def set_key(self, key_name):
2144- """Change the name of the data key used for the entered data.
2292+ """Change the name of the data key used for the text data.
21452293
2146- :param key_name: New name for the data key.
2294+ :param key_name: New name for the text data key.
21472295
21482296 This method allows the name of the data key to be customized to
2149- eliminate conflicts with other ScalePane objects on the same UI.
2297+ eliminate conflicts with other TextPane objects on the same UI.
21502298 """
21512299 if self.datakeyname in self.datadict:
21522300 self.datadict[key_name] = self.datadict[self.datakeyname]
21532301 del self.datadict[self.datakeyname]
21542302 self.datakeyname = key_name
21552303 self.datakeylist = [key_name]
2304+
21562305
21572306
2158-class SpinboxPane(tkpane.TkPane):
2159- """Display a Tkinter Spinbox widget with a prompt.
2307+class UserPane(tkpane.TkPane):
2308+ """Display a user's name and a button to prompt for a user's name and password.
21602309
2161- :param pane_name: The name to be used to identify this pane in status messages.
2162- :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
2163- :param min_value: The minimum value that can be selected.
2164- :param max_value: The maximum value that can be selected.
2165- :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 EntryPane objects on the same UI (optional).
2166-
2167- Data keys managed by this pane: "spinbox" or the key name specified during initialization.
2310+ Data keys managed by this pane: "name" and "password".
21682311
2169- Name used by this pane: user-defined on initialization.
2312+ Name used by this pane: "User authorization".
21702313
21712314 Overridden methods:
21722315
2173- * entry_widgets
2316+ * valid_data
2317+ * clear_pane
2318+ * send_status_message
2319+ * focus
2320+
2321+ Custom methods:
2322+
2323+ * set_user
2324+ * set_user_validator
2325+ """
2326+
2327+ class GetUserDialog(Dialog):
2328+ def makebody(self, master):
2329+ ttk.Label(master, text="User name:", width=12, anchor=tk.E).grid(row=0, column=0, sticky=tk.E, padx=3, pady=3)
2330+ ttk.Label(master, text="Password:", width=12, anchor=tk.E).grid(row=1, column=0, sticky=tk.E, padx=3, pady=3)
2331+ self.e1 = tk.Entry(master, width=36)
2332+ self.e2 = tk.Entry(master, width=36, show="*")
2333+ self.e1.grid(row=0, column=1, sticky=tk.W, padx=3, pady=3)
2334+ self.e2.grid(row=1, column=1, sticky=tk.W, padx=3, pady=3)
2335+ return self.e1
2336+ def validate(self):
2337+ return self.e1.get() != u'' and self.e2.get() != u''
2338+ def apply(self):
2339+ self.result = {u"name": self.e1.get(), u"password": self.e2.get()}
2340+
2341+ def __init__(self, parent):
2342+ tkpane.TkPane.__init__(self, parent, "User authorization", config_opts=frame_config_opts(), grid_opts=frame_grid_opts())
2343+ self.user_validator = None
2344+ self.user_label = ttk.Label(self, text='User name:', width=10, anchor=tk.E)
2345+ self.user_var = tk.StringVar()
2346+ self.userkeyname = "name"
2347+ self.passkeyname = "password"
2348+ self.datakeylist = [self.userkeyname, self.passkeyname]
2349+ self.datadict = {}
2350+ self.previous_values = {}
2351+ self.user_display = ttk.Entry(self, textvariable=self.user_var)
2352+ self.user_display.config(state='readonly')
2353+ self.user_button = ttk.Button(self, text='Change', width=8, command=self.set_user)
2354+ self.user_label.grid(row=0, column=0, padx=6, pady=3, sticky=tk.EW)
2355+ self.user_display.grid(row=0, column=1, padx=6, pady=3, sticky=tk.EW)
2356+ self.user_button.grid(row=1, column=1, padx=6, pady=1, sticky=tk.W)
2357+ self.columnconfigure(0, weight=0)
2358+ self.columnconfigure(1, weight=1)
2359+ self.rowconfigure(1, weight=1)
2360+ parent.rowconfigure(0, weight=0)
2361+ parent.columnconfigure(0, weight=1)
2362+
2363+ #---------------------------------------------------------------------------
2364+ # Overrides of class methods.
2365+ #...........................................................................
2366+
2367+ def valid_data(self, widget=None):
2368+ """Return True or False indicating whether or not a name and password have been entered."""
2369+ # Although this method is meant to check the data in the widgets, because
2370+ # name and password can be entered only from a dialog box, and the dialog
2371+ # box requires entry, and the values are then assigned to the data
2372+ # dictionary, this routine checks the data dictionary rather than the widgets.
2373+ if self.required:
2374+ v = "name" in self.datadict and "password" in self.datadict
2375+ else:
2376+ v = True
2377+ if v:
2378+ if self.user_validator is not None:
2379+ return self.user_validator(self.datadict["name"], self.datadict["password"])
2380+ else:
2381+ return True
2382+ else:
2383+ return False
2384+
2385+
2386+ def send_status_message(self, is_valid):
2387+ """Send a status message reporting data values and/or validity if data have changed."""
2388+ # This overrides the class method because only the user name should be reported.
2389+ if self.datadict != self.original_values:
2390+ if is_valid:
2391+ if "name" in self.datadict.keys():
2392+ self.report_status(u"User name set to %s." % self.datadict["name"])
2393+ else:
2394+ self.report_status(u"User name cleared.")
2395+ else:
2396+ self.report_status("User name is invalid.")
2397+
2398+ def clear_pane(self):
2399+ self.user_var.set(u'')
2400+ self.user_pw = None
2401+
2402+ def enable_pane(self):
2403+ self._enablewidgets([self.user_label,self.user_display, self.user_button])
2404+
2405+ def disable_pane(self):
2406+ self._disablewidgets([self.user_label,self.user_display, self.user_button])
2407+
2408+ def set_style(self, ttk_style):
2409+ self._setstyle([self.user_label, self.user_display], ttk_style)
2410+
2411+ def focus(self):
2412+ """Set the focus to the button."""
2413+ self.user_button.focus_set()
2414+
2415+ #---------------------------------------------------------------------------
2416+ # Custom methods.
2417+ #...........................................................................
2418+
2419+ def set_keys(self, user_key_name, password_key_name):
2420+ """Change the names of the data keys used for the entered data.
2421+
2422+ :param user_key_name: New name for the key for the user's name.
2423+ :param password_key_name: New name for the key for the user's password.
2424+
2425+ This method allows the name of the data key to be customized to
2426+ eliminate conflicts with other UserPane objects on the same UI.
2427+ """
2428+ if self.userkeyname in self.datadict:
2429+ self.datadict[user_key_name] = self.datadict[self.userkeyname]
2430+ del self.datadict[self.userkeyname]
2431+ self.userkeyname = user_key_name
2432+ if self.passkeyname in self.datadict:
2433+ self.datadict[password_key_name] = self.datadict[self.passkeyname]
2434+ self.passkeyname = password_key_name
2435+ self.userkeylist = [user_key_name, password_key_name]
2436+
2437+ def set_user(self):
2438+ # Open a dialog box to prompt for the user's name and password.
2439+ dlg = self.GetUserDialog(self)
2440+ if dlg.result is not None:
2441+ self.user_var.set(dlg.result["name"])
2442+ self.user_pw = dlg.result["password"]
2443+ self.datadict["name"] = dlg.result["name"]
2444+ self.datadict["password"] = dlg.result["password"]
2445+ self.report_status(u"Name set to %s." % dlg.result[u"name"])
2446+ self.handle_change_validity(True, None)
2447+ self.send_status_message(True)
2448+
2449+ def set_user_validator(self, fn):
2450+ """Set the callback function that will be used to check the entered user name and password.
2451+
2452+ This function must take the user name and password as arguments and return a Boolean.
2453+ """
2454+ self.user_validator = fn
2455+
2456+
2457+
2458+class UserPasswordPane(tkpane.TkPane):
2459+ """Display a user's name and a button to prompt for a user's name and password.
2460+
2461+ Data keys managed by this pane: "name" and "password".
2462+
2463+ Name used by this pane: "User credentials".
2464+
2465+ Overridden methods:
2466+
21742467 * valid_data
21752468 * save_data
21762469 * clear_pane
21772470 * enable_pane
21782471 * disable_pane
2472+ * send_status_message
2473+ * set_style
21792474 * focus
2180- * set_data
21812475
21822476 Custom method:
21832477
2184- * set_key
2478+ * set_user_validator
21852479 """
21862480
2187- def __init__(self, parent, pane_name, prompt, min_value, max_value, key_name=None, optiondict=None):
2188- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
2189- self.min_value = min_value
2190- self.datakeyname = "spinbox" if key_name is None else key_name
2191- self.datakeylist = [self.datakeyname]
2192- self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
2193- self.entry_var = tk.StringVar()
2194- self.entrywidget = tk.Spinbox(self, from_=min_value, to=max_value, textvariable=self.entry_var, width=len(str(max_value))+1, exportselection=False)
2195- self.entry_var.set(min_value)
2196- if optiondict is not None:
2197- self.entrywidget.configure(**optiondict)
2198- self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
2199- self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.W)
2200- self.rowconfigure(0, weight=1)
2481+ def __init__(self, parent):
2482+ tkpane.TkPane.__init__(self, parent, "User credentials", config_opts=frame_config_opts(), grid_opts=frame_grid_opts())
2483+ self.user_validator = None
2484+ self.user_label = ttk.Label(self, text='User name:', width=10, anchor=tk.E)
2485+ self.pw_label = ttk.Label(self, text='Password:', width=10, anchor=tk.E)
2486+ self.user_var = tk.StringVar()
2487+ self.user_var.trace("w", self.check_namechange)
2488+ self.pw_var = tk.StringVar()
2489+ self.pw_var.trace("w", self.check_pwchange)
2490+ self.userkeyname = "name"
2491+ self.passkeyname = "password"
2492+ self.datakeylist = [self.userkeyname, self.passkeyname]
2493+ self.user_display = ttk.Entry(self, textvariable=self.user_var)
2494+ self.pw_display = ttk.Entry(self, textvariable=self.pw_var, show="*")
2495+ self.user_label.grid(row=0, column=0, padx=6, pady=3, sticky=tk.EW)
2496+ self.pw_label.grid(row=1, column=0, padx=6, pady=3, sticky=tk.EW)
2497+ self.user_display.grid(row=0, column=1, padx=6, pady=3, sticky=tk.EW)
2498+ self.pw_display.grid(row=1, column=1, padx=6, pady=3, sticky=tk.EW)
22012499 self.columnconfigure(0, weight=0)
22022500 self.columnconfigure(1, weight=1)
2203- parent.rowconfigure(0, weight=1)
2501+ self.rowconfigure(1, weight=1)
2502+ parent.rowconfigure(0, weight=0)
22042503 parent.columnconfigure(0, weight=1)
2205- self.entry_var.trace("w", self.check_entrychange)
22062504
22072505 #---------------------------------------------------------------------------
22082506 # Overrides of class methods.
22092507 #...........................................................................
22102508
22112509 def entry_widgets(self):
2212- return [self.entrywidget]
2510+ return [self.user_display, self.pw_display]
22132511
2214- def valid_data(self, entry_widget=None):
2215- text = self.entry_var.get()
2216- return not (text == "" and self.required)
2217-
2512+ def valid_data(self, widget=None):
2513+ """Return True or False indicating whether or not a name and password have been entered."""
2514+ name = self.user_var.get()
2515+ pw = self.pw_var.get()
2516+ if self.required:
2517+ v = (len(name) > 0 and len(pw) > 0)
2518+ else:
2519+ v = (not (len(name) == 0 and len(pw) > 0))
2520+ if v:
2521+ if self.user_validator is not None:
2522+ return self.user_validator(name, pw)
2523+ else:
2524+ return True
2525+ else:
2526+ return False
2527+
22182528 def save_data(self, is_valid, entry_widget):
2219- """Update the pane's data dictionary with data from the Spinbox widget."""
2220- text = self.entry_var.get()
2529+ """Update the pane's data dictionary with data from the Entry widgets."""
2530+ name = self.user_var.get()
2531+ pw = self.pw_var.get()
22212532 if is_valid:
2222- if text == "":
2533+ if name == "":
22232534 self.clear_own()
22242535 else:
2225- self.datadict[self.datakeyname] = text
2536+ self.datadict["name"] = name
2537+ self.datadict["password"] = pw
22262538 else:
22272539 self.clear_own()
22282540
2541+ def send_status_message(self, is_valid):
2542+ """Send a status message reporting data values and/or validity if data have changed."""
2543+ # This overrides the class method because only the user name should be reported.
2544+ if self.datadict != self.original_values:
2545+ if is_valid:
2546+ if "name" in self.datadict:
2547+ self.report_status(u"User name set to %s." % self.datadict["name"])
2548+ else:
2549+ self.report_status(u"User name cleared.")
2550+ else:
2551+ self.report_status("User name is invalid.")
2552+
22292553 def clear_pane(self):
2230- self.entry_var.set(self.min_value)
2554+ self.user_var.set("")
2555+ self.pw_var.set("")
22312556
22322557 def enable_pane(self):
2233- self._enablewidgets([self.prompt, self.entrywidget])
2558+ self._enablewidgets([self.user_label, self.pw_label, self.user_display, self.pw_display])
22342559
22352560 def disable_pane(self):
2236- self._disablewidgets([self.prompt, self.entrywidget])
2561+ self._disablewidgets([self.user_label, self.pw_label, self.user_display, self.pw_display])
2562+
2563+ def set_style(self, ttk_style):
2564+ self._setstyle([self.user_label, self.pw_label, self.user_display, self.pw_display], ttk_style)
22372565
22382566 def focus(self):
2239- """Set the focus to the entry."""
2240- self.entrywidget.focus_set()
2241-
2242- def set_data(self, data):
2243- """Update the pane's data dictionary with the provided data.
2244-
2245- Special key supported: 'prompt' changes the pane's prompt.
2246- """
2247- spkey = "prompt"
2248- if spkey in data:
2249- self.prompt.configure(text=data[spkey])
2250- self.set_allbut(data, [spkey])
2567+ """Set the focus to the user name."""
2568+ self.user_display.focus_set()
22512569
22522570 #---------------------------------------------------------------------------
22532571 # Custom methods.
22542572 #...........................................................................
22552573
2256- def check_entrychange(self, *args):
2257- self.handle_change_validity(self.valid_data(None), self.entrywidget)
2258-
2259- def set_key(self, key_name):
2260- """Change the name of the data key used for the entered data.
2574+ def set_keys(self, user_key_name, password_key_name):
2575+ """Change the names of the data keys used for the entered data.
22612576
2262- :param key_name: New name for the data key.
2577+ :param user_key_name: New name for the key for the user's name.
2578+ :param password_key_name: New name for the key for the user's password.
22632579
22642580 This method allows the name of the data key to be customized to
2265- eliminate conflicts with other SpinboxPane objects on the same UI.
2581+ eliminate conflicts with other UserPasswordPane objects on the same UI.
22662582 """
2267- if self.datakeyname in self.datadict:
2268- self.datadict[key_name] = self.datadict[self.datakeyname]
2269- del self.datadict[self.datakeyname]
2270- self.datakeyname = key_name
2271- self.datakeylist = [key_name]
2583+ if self.userkeyname in self.datadict:
2584+ self.datadict[user_key_name] = self.datadict[self.userkeyname]
2585+ del self.datadict[self.userkeyname]
2586+ self.userkeyname = user_key_name
2587+ if self.passkeyname in self.datadict:
2588+ self.datadict[password_key_name] = self.datadict[self.passkeyname]
2589+ self.passkeyname = password_key_name
2590+ self.userkeylist = [user_key_name, password_key_name]
2591+
2592+ def check_namechange(self, *args):
2593+ self.handle_change_validity(self.valid_data(None), self.user_display)
2594+
2595+ def check_pwchange(self, *args):
2596+ self.handle_change_validity(self.valid_data(None), self.pw_display)
2597+
2598+ def set_user_validator(self, fn):
2599+ """Set the callback function that will be used to check the entered user name and password.
2600+
2601+ This function must take the user name and password as arguments and return a Boolean.
2602+ """
2603+ self.user_validator = fn
22722604
22732605
2274-class ComboboxPane(tkpane.TkPane):
2275- """Display a Tkinter Combobox widget with a prompt.
2276-
2277- :param pane_name: The name to be used to identify this pane in status messages.
2278- :param prompt: The prompt to be presented in a Label widget adjacent to the entry.
2279- :param items: The list of items to be included in the drop-down list.
2280- :param item_only: A Boolean indicating whether or not items from the list are the only valid entries. Default: False.
2281- :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 EntryPane objects on the same UI (optional).
2282-
2283- Data keys managed by this pane: "combobox" or the key name specified during initialization.
2284-
2285- Name used by this pane: user-defined on initialization.
2286-
2287- Overridden methods:
2288-
2289- * entry_widgets
2290- * valid_data
2291- * save_data
2292- * clear_pane
2293- * enable_pane
2294- * disable_pane
2295- * set_style
2296- * focus
2297- * set_data
2298-
2299- Custom method:
2300-
2301- * set_key
2302- """
2303-
2304- def __init__(self, parent, pane_name, prompt, items, item_only=False, key_name=None):
2305- tkpane.TkPane.__init__(self, parent, pane_name, frame_config_opts(), frame_grid_opts())
2306- self.items = items
2307- self.item_only = item_only
2308- self.datakeyname = "combobox" if key_name is None else key_name
2309- self.datakeylist = [self.datakeyname]
2310- self.prompt = ttk.Label(self, text=prompt, width=max(12, len(prompt)), anchor=tk.E)
2311- self.entry_var = tk.StringVar()
2312- self.entrywidget = ttk.Combobox(self, textvariable=self.entry_var, values=items, width=max(map(len, map(str, items)))+1, exportselection=False)
2313- self.prompt.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
2314- self.entrywidget.grid(row=0, column=1, padx=3, pady=3, sticky=tk.W)
2315- self.rowconfigure(0, weight=1)
2316- self.columnconfigure(0, weight=0)
2317- self.columnconfigure(1, weight=1)
2318- parent.rowconfigure(0, weight=1)
2319- parent.columnconfigure(0, weight=1)
2320- self.entry_var.trace("w", self.check_entrychange)
2321-
2322- #---------------------------------------------------------------------------
2323- # Overrides of class methods.
2324- #...........................................................................
2325-
2326- def entry_widgets(self):
2327- return [self.entrywidget]
2328-
2329- def valid_data(self, entry_widget=None):
2330- text = self.entry_var.get()
2331- if self.required:
2332- if self.item_only:
2333- return text in self.items
2334- else:
2335- return text != ""
2336- else:
2337- if self.item_only:
2338- return text in self.items
2339- else:
2340- return True
2341-
2342- def save_data(self, is_valid, entry_widget):
2343- """Update the pane's data dictionary with data from the Combobox widget."""
2344- text = self.entry_var.get()
2345- if is_valid:
2346- if text == "":
2347- self.clear_own()
2348- else:
2349- self.datadict[self.datakeyname] = text
2350- else:
2351- self.clear_own()
2352-
2353- def clear_pane(self):
2354- self.entry_var.set("")
2355-
2356- def enable_pane(self):
2357- self._enablewidgets([self.prompt, self.entrywidget])
2358-
2359- def disable_pane(self):
2360- self._disablewidgets([self.prompt, self.entrywidget])
2361-
2362- def set_style(self, ttk_style):
2363- self._setstyle([self.prompt, self.entrywidget], ttk_style)
2364-
2365- def focus(self):
2366- """Set the focus to the entry."""
2367- self.entrywidget.focus_set()
2368-
2369- def set_data(self, data):
2370- """Update the pane's data dictionary with the provided data.
2371-
2372- Special key supported: 'prompt' changes the pane's prompt.
2373- """
2374- spkey = "prompt"
2375- if spkey in data:
2376- self.prompt.configure(text=data[spkey])
2377- self.set_allbut(data, [spkey])
2378-
2379- #---------------------------------------------------------------------------
2380- # Custom methods.
2381- #...........................................................................
2382-
2383- def check_entrychange(self, *args):
2384- self.handle_change_validity(self.valid_data(None), self.entrywidget)
2385-
2386- def set_key(self, key_name):
2387- """Change the name of the data key used for the entered data.
2388-
2389- :param key_name: New name for the data key.
2390-
2391- This method allows the name of the data key to be customized to
2392- eliminate conflicts with other EntryPane objects on the same UI.
2393- """
2394- if self.datakeyname in self.datadict:
2395- self.datadict[key_name] = self.datadict[self.datakeyname]
2396- del self.datadict[self.datakeyname]
2397- self.datakeyname = key_name
2398- self.datakeylist = [key_name]
2399-
2400- def set_newitems(self, items):
2401- """Change the items displayed in the list. This will clear any selection."""
2402- if any(list(set(self.items) ^ set(items))):
2403- self.clear_pane()
2404- if self.datakeyname in self.datadict:
2405- del self.datadict[self.datakeyname]
2406- self.entrywidget.configure(values=items)
2407- self.items = items
2408- self.handle_change_validity(self.valid_data(self.entrywidget), self.entrywidget)
2409-
diff -r 60c2c4d93c8b -r 7ba694f7c4fd tkpane/tkpane.py
--- a/tkpane/tkpane.py Tue Feb 27 19:33:15 2018 -0800
+++ b/tkpane/tkpane.py Wed Feb 28 11:04:57 2018 -0800
@@ -27,7 +27,7 @@
2727 that pass specific data to allow communication between panes, and callback methods
2828 for reporting status and progress."""
2929
30-__version__ = "0.18.0"
30+__version__ = "0.18.1"
3131
3232
3333 try:
Show on old repository browser