| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | import queue | 
					
						
							|  |  |  | import threading | 
					
						
							| 
									
										
										
										
											2017-12-08 17:33:59 -05:00
										 |  |  | import tkinter as tk | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | from Utils import local_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_icon(window): | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  |     er16 = tk.PhotoImage(file=local_path('data', 'ER16.gif')) | 
					
						
							|  |  |  |     er32 = tk.PhotoImage(file=local_path('data', 'ER32.gif')) | 
					
						
							|  |  |  |     er48 = tk.PhotoImage(file=local_path('data', 'ER32.gif')) | 
					
						
							|  |  |  |     window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)  # pylint: disable=protected-access | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | # Although tkinter is intended to be thread safe, there are many reports of issues | 
					
						
							|  |  |  | # some which may be platform specific, or depend on if the TCL library was compiled without | 
					
						
							|  |  |  | # multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems | 
					
						
							|  |  |  | class BackgroundTask(object): | 
					
						
							| 
									
										
										
										
											2020-11-11 13:45:21 +01:00
										 |  |  |     def __init__(self, window, code_to_run, *args): | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |         self.window = window | 
					
						
							|  |  |  |         self.queue = queue.Queue() | 
					
						
							|  |  |  |         self.running = True | 
					
						
							|  |  |  |         self.process_queue() | 
					
						
							| 
									
										
										
										
											2020-11-11 13:45:21 +01:00
										 |  |  |         self.task = threading.Thread(target=code_to_run, args=(self, *args)) | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |         self.task.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def stop(self): | 
					
						
							|  |  |  |         self.running = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     # safe to call from worker | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |     def queue_event(self, event): | 
					
						
							|  |  |  |         self.queue.put(event) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def process_queue(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             while True: | 
					
						
							|  |  |  |                 if not self.running: | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 event = self.queue.get_nowait() | 
					
						
							|  |  |  |                 event() | 
					
						
							|  |  |  |                 if self.running: | 
					
						
							|  |  |  |                     #if self is no longer running self.window may no longer be valid | 
					
						
							|  |  |  |                     self.window.update_idletasks() | 
					
						
							|  |  |  |         except queue.Empty: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  |         if self.running: | 
					
						
							|  |  |  |             self.window.after(100, self.process_queue) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class BackgroundTaskProgress(BackgroundTask): | 
					
						
							| 
									
										
										
										
											2020-11-11 13:45:21 +01:00
										 |  |  |     def __init__(self, parent, code_to_run, title, *args): | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |         self.parent = parent | 
					
						
							|  |  |  |         self.window = tk.Toplevel(parent) | 
					
						
							|  |  |  |         self.window['padx'] = 5 | 
					
						
							|  |  |  |         self.window['pady'] = 5 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-01 11:50:35 -05:00
										 |  |  |         try: | 
					
						
							|  |  |  |             self.window.attributes("-toolwindow", 1) | 
					
						
							|  |  |  |         except tk.TclError: | 
					
						
							|  |  |  |             pass | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.window.wm_title(title) | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         self.label_var = tk.StringVar() | 
					
						
							|  |  |  |         self.label_var.set("") | 
					
						
							|  |  |  |         self.label = tk.Label(self.window, textvariable=self.label_var, width=50) | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |         self.label.pack() | 
					
						
							|  |  |  |         self.window.resizable(width=False, height=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         set_icon(self.window) | 
					
						
							|  |  |  |         self.window.focus() | 
					
						
							| 
									
										
										
										
											2020-11-11 13:45:21 +01:00
										 |  |  |         super().__init__(self.window, code_to_run, *args) | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     #safe to call from worker thread | 
					
						
							|  |  |  |     def update_status(self, text): | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         self.queue_event(lambda: self.label_var.set(text)) | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # only call this in an event callback | 
					
						
							|  |  |  |     def close_window(self): | 
					
						
							|  |  |  |         self.stop() | 
					
						
							|  |  |  |         self.window.destroy() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 17:33:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ToolTips(object): | 
					
						
							|  |  |  |     # This class derived from wckToolTips which is available under the following license: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Copyright (c) 1998-2007 by Secret Labs AB | 
					
						
							|  |  |  |     # Copyright (c) 1998-2007 by Fredrik Lundh | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # By obtaining, using, and/or copying this software and/or its | 
					
						
							|  |  |  |     # associated documentation, you agree that you have read, understood, | 
					
						
							|  |  |  |     # and will comply with the following terms and conditions: | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # Permission to use, copy, modify, and distribute this software and its | 
					
						
							|  |  |  |     # associated documentation for any purpose and without fee is hereby | 
					
						
							|  |  |  |     # granted, provided that the above copyright notice appears in all | 
					
						
							|  |  |  |     # copies, and that both that copyright notice and this permission notice | 
					
						
							|  |  |  |     # appear in supporting documentation, and that the name of Secret Labs | 
					
						
							|  |  |  |     # AB or the author not be used in advertising or publicity pertaining to | 
					
						
							|  |  |  |     # distribution of the software without specific, written prior | 
					
						
							|  |  |  |     # permission. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO | 
					
						
							|  |  |  |     # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | 
					
						
							|  |  |  |     # FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR | 
					
						
							|  |  |  |     # ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
					
						
							|  |  |  |     # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | 
					
						
							|  |  |  |     # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT | 
					
						
							|  |  |  |     # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     label = None | 
					
						
							|  |  |  |     window = None | 
					
						
							|  |  |  |     active = 0 | 
					
						
							|  |  |  |     tag = None | 
					
						
							| 
									
										
										
										
											2018-01-01 12:13:39 -05:00
										 |  |  |     after_id = None | 
					
						
							| 
									
										
										
										
											2017-12-08 17:33:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def getcontroller(cls, widget): | 
					
						
							|  |  |  |         if cls.tag is None: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             cls.tag = "ui_tooltip_%d" % id(cls) | 
					
						
							|  |  |  |             widget.bind_class(cls.tag, "<Enter>", cls.enter) | 
					
						
							|  |  |  |             widget.bind_class(cls.tag, "<Leave>", cls.leave) | 
					
						
							|  |  |  |             widget.bind_class(cls.tag, "<Motion>", cls.motion) | 
					
						
							| 
									
										
										
										
											2017-12-10 11:10:04 -05:00
										 |  |  |             widget.bind_class(cls.tag, "<Destroy>", cls.leave) | 
					
						
							| 
									
										
										
										
											2017-12-08 17:33:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # pick suitable colors for tooltips | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 cls.bg = "systeminfobackground" | 
					
						
							|  |  |  |                 cls.fg = "systeminfotext" | 
					
						
							|  |  |  |                 widget.winfo_rgb(cls.fg)  # make sure system colors exist | 
					
						
							|  |  |  |                 widget.winfo_rgb(cls.bg) | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |             except Exception: | 
					
						
							| 
									
										
										
										
											2017-12-08 17:33:59 -05:00
										 |  |  |                 cls.bg = "#ffffe0" | 
					
						
							|  |  |  |                 cls.fg = "black" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return cls.tag | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def register(cls, widget, text): | 
					
						
							|  |  |  |         widget.ui_tooltip_text = text | 
					
						
							|  |  |  |         tags = list(widget.bindtags()) | 
					
						
							|  |  |  |         tags.append(cls.getcontroller(widget)) | 
					
						
							|  |  |  |         widget.bindtags(tuple(tags)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def unregister(cls, widget): | 
					
						
							|  |  |  |         tags = list(widget.bindtags()) | 
					
						
							|  |  |  |         tags.remove(cls.getcontroller(widget)) | 
					
						
							|  |  |  |         widget.bindtags(tuple(tags)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # event handlers | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def enter(cls, event): | 
					
						
							|  |  |  |         widget = event.widget | 
					
						
							|  |  |  |         if not cls.label: | 
					
						
							|  |  |  |             # create and hide balloon help window | 
					
						
							|  |  |  |             cls.popup = tk.Toplevel(bg=cls.fg, bd=1) | 
					
						
							|  |  |  |             cls.popup.overrideredirect(1) | 
					
						
							|  |  |  |             cls.popup.withdraw() | 
					
						
							|  |  |  |             cls.label = tk.Label( | 
					
						
							|  |  |  |                 cls.popup, fg=cls.fg, bg=cls.bg, bd=0, padx=2, justify=tk.LEFT | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             cls.label.pack() | 
					
						
							|  |  |  |             cls.active = 0 | 
					
						
							|  |  |  |         cls.xy = event.x_root + 16, event.y_root + 10 | 
					
						
							|  |  |  |         cls.event_xy = event.x, event.y | 
					
						
							|  |  |  |         cls.after_id = widget.after(200, cls.display, widget) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def motion(cls, event): | 
					
						
							|  |  |  |         cls.xy = event.x_root + 16, event.y_root + 10 | 
					
						
							|  |  |  |         cls.event_xy = event.x, event.y | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def display(cls, widget): | 
					
						
							|  |  |  |         if not cls.active: | 
					
						
							|  |  |  |             # display balloon help window | 
					
						
							|  |  |  |             text = widget.ui_tooltip_text | 
					
						
							|  |  |  |             if callable(text): | 
					
						
							|  |  |  |                 text = text(widget, cls.event_xy) | 
					
						
							|  |  |  |             cls.label.config(text=text) | 
					
						
							|  |  |  |             cls.popup.deiconify() | 
					
						
							|  |  |  |             cls.popup.lift() | 
					
						
							|  |  |  |             cls.popup.geometry("+%d+%d" % cls.xy) | 
					
						
							|  |  |  |             cls.active = 1 | 
					
						
							|  |  |  |             cls.after_id = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def leave(cls, event): | 
					
						
							|  |  |  |         widget = event.widget | 
					
						
							|  |  |  |         if cls.active: | 
					
						
							|  |  |  |             cls.popup.withdraw() | 
					
						
							|  |  |  |             cls.active = 0 | 
					
						
							|  |  |  |         if cls.after_id: | 
					
						
							|  |  |  |             widget.after_cancel(cls.after_id) | 
					
						
							|  |  |  |             cls.after_id = None |