Source code for utils_widgets

print('Loading widgets utility functions...')

from omfit_classes.utils_base import *
from omfit_classes.utils_math import array_info
from tkinter import Tk as tk
from tkinter import ttk
import tkinter.font as tkFont
import numpy as np
import pandas
import xarray
import collections
from uncertainties.unumpy import nominal_values, std_devs

# -------------
# Tk utils
# -------------
rightClick = 'Button-3'
middleClick = 'Button-2'
rightClickIndex = 3
middleClickIndex = 2

hide_ptrn = re.compile(r'^__.*__$')


[docs]def treeText(inv, strApex=False, n=-1, escape=True): """ Returns the string that is shown in the OMFITtree for the input object `inv` :param inv: input object :param strApex: should the string be returned with apexes :param n: maximum string length :param escape: escape backslash characters :return: string representation of object `inv` """ if isinstance(inv, str): if strApex: return short_str(repr(inv), n, escape) elif isinstance(inv, str): return short_str(repr(inv)[1:-1], n, escape) elif isinstance(inv, list): def listType(x): lst = [] for k in x[:10]: if isinstance(k, str): kr = repr(k) if len(kr) > 12: k = kr[:11] + '..' elif hasattr(k, '__iter__'): kr = '<%s>' % k.__class__.__name__ elif isinstance(k, (int, np.integer, float, np.float, complex, np.complex)): kr = repr(k) else: kr = '<object>' lst.append(kr) if len(x) > 10: lst.append('...') return lst return short_str('[%s]' % ', '.join(listType(inv)), n, escape) elif is_float(inv): return format(inv, '3.8g') elif isinstance(inv, np.ndarray): return array_info(inv) elif pandas is not None and isinstance(inv, pandas.DataFrame): ncol = len(inv.columns) if ncol > 5: return ', '.join(k for k in inv.columns[:5]) + '...' else: return ', '.join(k for k in inv.columns) elif pandas is not None and isinstance(inv, pandas.Series): return (', '.join(l for l in str(inv.describe()).split('\n')[:2] if '%' not in l)).replace('\n', '') elif xarray is not None and isinstance(inv, xarray.DataArray): return ', '.join('%s: %s' % (k, v) for k, v in zip(inv.dims, inv.shape)) elif xarray is not None and isinstance(inv, xarray.Dataset): return ', '.join(['%s: %s' % (k, inv.dims[k]) for k in inv.dims]) elif isinstance(inv, (dict, collections.MutableMapping)): if hasattr(inv, 'dynaLoad') and inv.dynaLoad: return '--{\t?\t}--' vshow = [k for k in inv if not isinstance(k, str) or not re.match(hide_ptrn, k)] vhide = [k for k in inv if isinstance(k, str) and re.match(hide_ptrn, k)] values = '--{\t' if vshow: values += '%d' % len(vshow) if vshow and vhide: values += ' ' if vhide: values += '(%d)' % len(vhide) values += '\t}--' for k in ['modifyOriginal', 'readOnly']: if hasattr(inv, k) and getattr(inv, k): values += ' ' + k + ' ' return values elif inspect.isroutine(inv): if inv.__doc__ is not None: tmp = inv.__doc__.strip().split('\n') tmp = [line for line in map(lambda x: x.strip(), tmp) if line and not line.startswith(':') and not line.startswith('>')] if tmp: return short_str(' | '.join(tmp), n, escape) try: return short_str('(' + function_arguments(inv, [], True).replace('\n', '').replace('self,', '') + ')', n, escape) except TypeError: return short_str('built-in method', n, escape) else: return short_str(repr(inv), n, escape)
[docs]def ctrlCmd(): return 'Control'
# return 'Command'
[docs]def short_str(inv, n=-1, escape=True, snip='[...]'): """ :param inv: input string :param n: maximum length of the string (negative is unlimited) :param escape: escape backslash characters :param snip: string to replace the central part of the input string :return: new string """ l = len(inv) s = len(snip) if not l: inv = "\'\'" elif l > n and n > s + 1: nn = (n - s) / 2.0 a = int(round(nn)) b = int(nn) inv = inv[:a] + snip + inv[-b:] if escape: inv = re.sub(r'\\', r'\\\\', inv) return inv
[docs]def tkStringEncode(inv): r""" Tk requires ' ', '\', '{' and '}' characters to be escaped Use of this function dependes on the system and the behaviour can be set on a system-by-system basis using the OMFIT_ESCAPE_TK_SPACES environmental variable :param inv: input string :return: escaped string """ if int(os.environ.get('OMFIT_ESCAPE_TK_SPACES', '1')) and len(inv): inv = re.sub(r'([ \\\{\}])', r'\\\1', treeText(inv, strApex=False, n=-1, escape=False)) return inv
_defaultFont = {}
[docs]def OMFITfont(weight='', size=0, family='', slant=''): """ The first time that OMFITfont is called, the system defaults are gathered :param weight: 'normal' or 'bold' :param size: positive or negative number relative to the tkinter default :param family: family font :return: tk font object to be used in tkinter calls """ font = {'family': 'Helvetica', 'size': 11, 'weight': 'normal', 'slant': 'roman'} # save default font if not done yet if not len(_defaultFont): ttk_style = ttk.Style() # generate font f = tkFont.Font(font=ttk_style.lookup('TLabel', 'font')) _defaultFont.update(f.metrics()) # make sure the default font has all the categories for key, val in list(font.items()): _defaultFont.setdefault(key, val) if not _defaultFont['family']: # bad things happen if this somehow gets set to '' _defaultFont['family'] = 'Helvetica' if _defaultFont['size'] < 6: # bad things happen if this somehow gets set to 0 _defaultFont['size'] = 6 # modifications to default font font = {} font['size'] = abs(_defaultFont['size']) + size if weight: font['weight'] = weight elif _defaultFont['weight']: font['weight'] = _defaultFont['weight'] if slant: font['slant'] = slant elif _defaultFont['slant']: font['slant'] = _defaultFont['slant'] if family: font['family'] = family elif _defaultFont['family']: font['family'] = _defaultFont['family'] return (font['family'], font['size'], font['weight'], font['slant'])
[docs]def tk_center(win, parent=None, width=None, height=None, xoff=None, yoff=None, allow_negative=False): """ Function used to center a tkInter GUI Note: by using this function tk does not manage the GUI geometry which is beneficial when switching between desktop on some window managers, which would otherwise re-center all windows to the desktop. :param win: window to be centered :param parent: window with respect to be centered (center with respect to screen if `None`) :width: set this window width :height: set this window height :param xoff: x offset in pixels :param yoff: y offset in pixels :param allow_negative: whether window can be off the left/upper part of the screen """ win.update_idletasks() if parent is None: utils_tk._winfo_screen(reset=True) swidth, sheight, x0, y0 = screen_geometry() else: swidth, sheight, x0, y0 = _parse_tk_geometry(TKtopGUI(parent).geometry()) if xoff is None: xoff = 0 if yoff is None: yoff = 0 _width = width if width is None: _width = win.winfo_reqwidth() _height = height if height is None: _height = win.winfo_reqheight() x = (swidth - _width) // 2 + x0 + xoff y = (sheight - _height) // 2 + y0 + yoff if not allow_negative: x = np.max([xoff, x]) y = np.max([yoff, y]) if width or height: win.geometry('%dx%d+%d+%d' % (_width, _height, x, y)) else: win.geometry('+%d+%d' % (x, y))
# ---------------- # extra Tk widgets # ---------------
[docs]class HelpTip(object): def __init__(self, widget=None, move=False): self.widget = widget self.tipwindow = None self.mode = 0 self.location = None self.text = None
[docs] def showtip(self, text, location=None, mode=None, move=True, strip=False): "Display text in helptip window" self.hidetip() if location is not None: self.location = location if mode is not None: self.mode = mode if text: if strip: text = text.strip() self.text = text if isinstance(text, list): text = '\n'.join(text) text = wrap(text, 100) self.tipwindow = tk.Toplevel(self.widget) self.tipwindow.withdraw() color = "#ffffe0" if self.mode == 1: # editor mode self.tipwindow.wm_transient(self.widget) wmax = np.max([max(np.array([len(line) for line in text.split('\n')])), 50]) hmax = 11 self.tipwindow.wm_title('Tip') self.widget.winfo_containing(self.widget.winfo_pointerx(), self.widget.winfo_pointery()).unbind("<Leave>") self.widget.bind_all("<Escape>", self.hidetip) self.widget.unbind_all("<F4>") else: # view mode if platform.system() != 'Darwin': self.tipwindow.wm_overrideredirect(1) wmax = np.max(np.array([len(line) for line in text.split('\n')])) hmax = np.min([text.count('\n') + 1, 11]) if hmax > 10 and move: ttk.Label(self.tipwindow, text='Press F4 to detach', font=OMFITfont('bold', -1)).pack() self.widget.winfo_containing(self.widget.winfo_pointerx(), self.widget.winfo_pointery()).bind("<Leave>", self.hidetip) self.widget.unbind_all("<Escape>") if not move: self.widget.bind_all("<F4>", self.changeMode) frm = ttk.Frame(self.tipwindow, relief=tk.SOLID, borderwidth=1) frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.BOTH) label = tk.Text(frm, width=wmax, height=hmax, undo=tk.TRUE, maxundo=-1, wrap='word') label.config(borderwidth=0, font=OMFITfont('normal', 0, 'Courier')) label.insert(1.0, text) label.delete('end -1 chars', 'end') label.grid(column=0, row=0, sticky='nsew') sb = ttk.Scrollbar(frm, command=label.yview, orient='vertical') if hmax > 10: sb.grid(column=1, row=0, sticky='ns') if self.mode == 1 and self.location: label.insert(1.0, '') label.focus_set() def onButton(event=None): self.text = label.get(1.0, tk.END) exec((self.location + '=' + repr(self.text)), globals(), locals()) self.changeMode() return "break" bt = ttk.Button(frm, text='Update variables description <{ctrlCmd()}-Return>', command=onButton) bt.grid(column=0, row=1, sticky='nsew') label.bind(f'<{ctrlCmd()}-Return>', onButton) else: label.config(state=tk.DISABLED) label['yscrollcommand'] = sb.set frm.columnconfigure(0, weight=1) frm.rowconfigure(0, weight=1) if not move: x = self.widget.winfo_pointerx() y = self.widget.winfo_pointery() self.tipwindow.update_idletasks() # place window smack in the middle of the cursor self.x = int(x - self.tipwindow.winfo_reqwidth() / 2.0) self.y = int(y - self.tipwindow.winfo_reqheight() / 2.0) # move window off the edges if (self.x + self.tipwindow.winfo_reqwidth()) > screen_geometry()[0]: self.x = self.x - self.tipwindow.winfo_reqwidth() if (self.y + self.tipwindow.winfo_reqheight()) > screen_geometry()[1]: self.y = self.y - self.tipwindow.winfo_reqheight() if self.x < 0: self.x = 0 if self.y < 0: self.y = 0 self.tipwindow.wm_geometry("+%d+%d" % (self.x, self.y)) self.tipwindow.update_idletasks() # place cursor smack in the middle of the window x = int(self.x + self.tipwindow.winfo_reqwidth() / 2.0) y = int(self.y + self.tipwindow.winfo_reqheight() / 2.0) self.tipwindow.event_generate('<Motion>', warp=True, x=x, y=y) self.tipwindow.update_idletasks() self.tipwindow.deiconify() self.tipwindow.bind("<Leave>", self.hidetip) else: self.movetip() self.tipwindow.deiconify()
[docs] def hidetip(self, event=None): try: self.mode = 0 self.tipwindow.destroy() self.tipwindow = None except Exception: pass
[docs] def movetip(self, event=None): try: if self.tipwindow is not None: if event is None: x = self.widget.winfo_pointerx() y = self.widget.winfo_pointery() else: x = event.x_root y = event.y_root self.tipwindow.update_idletasks() self.x = x + 10 self.y = y + 12 if self.x > screen_geometry()[0] - (self.tipwindow.winfo_reqwidth() + 10): self.x = self.x - self.tipwindow.winfo_reqwidth() - 20 if self.y > screen_geometry()[1] - (self.tipwindow.winfo_reqheight() + 12): self.y = self.y - self.tipwindow.winfo_reqheight() - 24 self.tipwindow.wm_geometry("+%d+%d" % (self.x, self.y)) if self.mode == 0: self.tipwindow.after(25, self.movetip) except Exception as _excp: warnings.warn(repr(_excp))
[docs] def changeMode(self, event=None): self.showtip(self.text, mode=np.mod(self.mode + 1, 2))
helpTip = HelpTip()
[docs]class ToolTip(object): """ Tooltip recipe from http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387 """
[docs] @staticmethod def createToolTip(widget, text): toolTip = ToolTip(widget) def enter(event): toolTip.showtip(text) def leave(event): toolTip.hidetip() widget.bind('<Enter>', enter) widget.bind('<Leave>', leave)
def __init__(self, widget): self.widget = widget self.tipwindow = None self.id = None self.x = self.y = 0
[docs] def showtip(self, text): "Display text in tooltip window" self.text = text if self.tipwindow or not self.text: return x, y, _, _ = self.widget.bbox("insert") x = x + self.widget.winfo_rootx() + 27 y = y + self.widget.winfo_rooty() self.tipwindow = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(1) tw.wm_geometry("+%d+%d" % (x, y)) try: # For Mac OS tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, "help", "noActivates") except tk.TclError: pass label = tk.Label(tw, text=self.text, justify=tk.LEFT, background="#ffffe0", relief=tk.SOLID, borderwidth=1) label.pack(ipadx=1)
[docs] def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: tw.destroy()
[docs]def dialog(message='Are You Sure?', answers=['Yes', 'No'], icon='question', title=None, parent=None, options=None, entries=None, **kw): """ Display a dialog box and wait for user input Note: * The first answer is highlighted (for users to quickly respond with `<Return>`) * The last answer is returned for when users press `<Escape>` or close the dialog :param message: the text to be written in the label :param answers: list of possible answers :param icon: "question", "info", "warning", "error" :param title: title of the frame :param parent: tkinter window to which the dialog will be set as transient :param options: dictionary of True/False options that are displayed as checkbuttons in the dialog :param entries: dictionary of string options that are displayed as entries in the dialog :return: return the answer chosen by the user (a dictionary if options keyword was passed) """ class _Dialog(object): def __init__(self, top, message='Are You Sure?', answers=['Yes', 'No'], icon='question', title=None, options=None, entries=None): self.top = top self.result = None self.options_var = {} self.entries_var = {} top = ttk.Frame(self.top) top.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES, padx=10, pady=10) frm = ttk.Frame(top) frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0) if icon in ["question", "info", "warning", "error"]: if title is None: title = icon OMFITsrc = str(os.path.abspath(os.path.dirname(__file__))) img = tk.PhotoImage(file=os.path.join(OMFITsrc, 'extras', 'graphics', icon + ".gif")) icon_canvas = tk.Canvas(frm, width=64, height=64) icon_canvas.copy_image = img icon_canvas.create_image(32, 32, image=icon_canvas.copy_image) icon_canvas.pack(side=tk.LEFT, fill=tk.NONE, expand=tk.NO) ttk.Label(frm, text=message, justify=tk.LEFT).pack(side=tk.LEFT, fill=tk.X, expand=tk.YES) if options is not None: for item in list(options.keys()): frm = ttk.Frame(top) frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0) self.options_var[item] = var = tk.BooleanVar() tmp = ttk.Checkbutton(frm, variable=var, text=item) tmp.var = var tmp.pack(side=tk.LEFT, padx=5, pady=2) var.set(options[item]) if entries is not None: for item in list(entries.keys()): frm = ttk.Frame(top) frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=5, pady=2) self.entries_var[item] = var = tk.StringVar() ttk.Label(frm, text=item + ' ').pack(side=tk.LEFT) tmp = ttk.Entry(frm, textvariable=var) tmp.pack(side=tk.LEFT, fill=tk.X, expand=tk.YES) var.set(entries[item]) frm = ttk.Frame(top) frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0) for ii, answer in enumerate(answers): tmp = ttk.Button(frm, text=answer, command=lambda answer=answer: self.ok(answer)) tmp.bind('<Return>', lambda event: event.widget.invoke()) tmp.pack(side=tk.LEFT, fill=tk.X, expand=tk.YES) if ii == 0: tmp.focus() self.top.bind('<Escape>', lambda event=None, answer=answers[-1]: self.ok(answer)) self.top.protocol("WM_DELETE_WINDOW", lambda event=None, answer=answers[-1]: self.ok(answer)) if title is None: title = 'message' self.top.wm_title(title[0].upper() + title[1:]) def ok(self, x=None): if not len(self.options_var) and not len(self.entries_var): self.result = x else: self.result = [x] kw = {} for item in list(self.options_var.keys()): kw[item] = bool(self.options_var[item].get()) for item in list(self.entries_var.keys()): kw[item] = self.entries_var[item].get() self.result.append(kw) self.top.destroy() if parent is None: parent = OMFITaux['rootGUI'] try: frm_top = tk.Toplevel(parent) except Exception: parent = OMFITaux['rootGUI'] frm_top = tk.Toplevel(parent) frm_top.withdraw() frm_top.transient(parent) d = _Dialog(frm_top, message=message, answers=answers, icon=icon, title=title, options=options, entries=entries) frm_top.resizable(False, False) tk_center(frm_top, parent) frm_top.deiconify() try: frm_top.grab_set() except tk.TclError: pass frm_top.update_idletasks() frm_top.wait_window(d.top) return d.result
[docs]def pickTreeLocation(startLocation=None, title='Pick tree location ...', warnExists=True, parent=None): """ Function which opens a blocking GUI to ask user to pick a tree location :param startLocation: starting location :param title: title to show in the window :param warnExists: warn user if tree location is already in use :param parent: tkinter window to which the dialog will be set as transient :return: the location picked by the user """ def onEscape(event=None): top.destroy() if parent is None: parent = OMFITaux['rootGUI'] top = tk.Toplevel(parent) top.withdraw() top.transient(parent) frm = ttk.Frame(top) frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.X, padx=5, pady=5) frm.grid_columnconfigure(1, weight=1) top.wm_title(title) outcome = [None] def onReturn(event=None): v2 = newLocation.get() try: eval(v2) if warnExists and 'No' == dialog( title='Location already exists', message='Proceed anyways?', answers=['Yes', 'No'], parent=top ): return except Exception: pass top.destroy() outcome[0] = v2 ttk.Label(frm, text='To: ').grid(row=1, sticky=tk.E) newLocation = tk.OneLineText(frm, width=50, percolator=True) if startLocation is None: newLocation.set(OMFITaux['GUI'].focusRoot) else: newLocation.set(startLocation) tk_center(top, parent) e1 = newLocation e1.grid(row=1, column=1, sticky=tk.E + tk.W, columnspan=2) e1.focus_set() top.bind('<Return>', onReturn) top.bind('<KP_Enter>', onReturn) top.bind('<Escape>', onEscape) top.protocol("WM_DELETE_WINDOW", top.destroy) top.update_idletasks() top.deiconify() top.wait_window(top) return outcome[0]
stored_passwords = {}
[docs]def password_gui(title='Password', parent=None, key=None): """ Present a password dialog box :param title: The title for the dialog box :param parent: The GUI parent :param key: A key for caching the password in this session """ if key and key in stored_passwords: return stored_passwords[key] def onEscape(event=None): top.destroy() if parent is None: parent = OMFITaux['rootGUI'] import tkinter as tk from tkinter import ttk top = tk.Toplevel(parent) top.withdraw() top.transient(parent) frm = ttk.Frame(top) frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.X, padx=5, pady=5) frm.grid_columnconfigure(1, weight=1) top.wm_title(title) outcome = [None] def onReturn(event=None): outcome[0] = pass_phrase.get() top.destroy() ttk.Label(frm, text='Pass_phrase').grid(row=1, sticky=tk.E) pass_phrase = tk.Entry(frm, width=50, show='*') tk_center(top, parent) pass_phrase.grid(row=1, column=1, sticky=tk.E + tk.W, columnspan=2) pass_phrase.focus_set() top.bind('<Return>', onReturn) top.bind('<KP_Enter>', onReturn) top.bind('<Escape>', onEscape) top.protocol("WM_DELETE_WINDOW", top.destroy) top.update_idletasks() top.deiconify() top.wait_window(top) if key: stored_passwords[key] = outcome[0] return outcome[0]
# ---------------- # Tk events # ---------------
[docs]class eventBindings(object): def __init__(self): self.descs = [] self.widgets = [] self.events = [] self.callbacks = [] self.tags = [] self.default_desc_event = {}
[docs] def add(self, description, widget, event, callback, tag=None): if '_' in description: raise ValueError('The saving/restoring of key bindings cannot handle _ in the description') # set event bindings for more than one widget if description in self.descs: ind = self.descs.index(description) if tag: widget.tag_bind(tag + '_action', self.events[ind], callback) else: try: widget.bind(self.events[ind], callback) except tk.TclError: if 'ISO' in event: pass else: raise self.widgets[ind].append(widget) return # bind if tag: widget.tag_bind(tag + '_action', event, callback) if platform.system() == 'Darwin' and 'Control' in event: widget.tag_bind(tag + '_action', re.sub('Control', 'Command', event), callback) else: try: widget.bind(event, callback) except tk.TclError: if 'ISO' in event: pass else: raise if platform.system() == 'Darwin' and 'Control' in event: widget.bind(re.sub('Control', 'Command', event), callback) # add to list self.descs.append(description) self.widgets.append([widget]) self.events.append(event) self.callbacks.append(callback) if tag: self.tags.append(tag + '_action') else: self.tags.append(tag) self.default_desc_event[description.replace(' ', '_')] = event
[docs] def remove_widget(self, widget): for wi in range(len(self.widgets)): if widget in self.widgets[wi]: self.widgets[wi].remove(widget)
[docs] def set(self, description, event): try: ind = self.descs.index(description) except ValueError: raise ValueError('Unable to set %s because it is not yet bound to a widget' % description) if event == self.events[ind]: return for w in list(self.widgets[ind]): try: if self.tags[ind]: w.tk.call(w._w, 'tag', 'bind', self.tags[ind], self.events[ind], '') # Doesn't exist -> w.tag_unbind(self.tags[ind],self.events[ind]) else: w.unbind(self.events[ind]) except tk.TclError as _excp: if 'bad window path name' in str(_excp) or "application has been destroyed" in str(_excp): self.remove_widget(w) continue if self.tags[ind]: w.tag_bind(self.tags[ind], event, self.callbacks[ind]) else: w.bind(event, self.callbacks[ind]) self.events[ind] = event
[docs] def get(self, description): for di, d in enumerate(self.descs): if d == description: return self.events[di]
[docs] def print_event_in_bindings(self, w, event, ind): bindings = set() for cls in w.bindtags(): bindings |= set(w.bind_class(cls)) # s |= t means: update set s, adding elements from t bindings = [s.replace('Key-', '') for s in list(bindings)] printi(event in bindings, self.events[ind] in bindings)
[docs] def printAll(self): from utils import printi for di, d in enumerate(self.descs): printi('%s: %s' % (d, self.events[di])) # ,self.callbacks[di],self.widgets[di]
global_event_bindings = eventBindings() # ---------------- # import tk elements # --------------- from utils_tk import * import utils_tk # ---------------- # screen geometry # --------------- def _parse_tk_geometry(geom): """ Function that parses string returned by tk geometry() :param geom: string in the format width, height, x, y `400x300+30+55` :return: tuple with 4 integers for width, height, x, y """ geometry = [] geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\1', geom))) geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\2', geom))) geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\3', geom))) geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\4', geom))) return geometry _screen_geometry = []
[docs]def screen_geometry(): """ Function returns the screen geometry :return: tuple with 4 integers for width, height, x, y """ if not len(_screen_geometry): t = tk.Tk() # new window t.withdraw() t.attributes("-alpha", 0) t.attributes('-fullscreen', True) t.update_idletasks() geom = _parse_tk_geometry(t.geometry()) geom[0] = t.winfo_reqwidth() geom[1] = t.winfo_reqheight() _screen_geometry.extend(geom) screen = [t.winfo_screenwidth(), t.winfo_screenheight()] _screen_geometry[0] = screen[0] - _screen_geometry[2] _screen_geometry[1] = screen[1] - _screen_geometry[3] t.destroy() return _screen_geometry
_displays = [0]
[docs]def wmctrl(): """ This function is useful when plugging in a new display in OSX and the OMFIT window disappear To fix the issue, go to the XQuartz application and select the OMFIT window from the menu `Window > OMFIT ...` Then press F8 a few times until the OMFIT GUI appears on one of the screens. """ # screen sizes with open(os.devnull, 'w') as nul_f: child = subprocess.Popen([OMFITsrc + '/../bin/hmscreens', "-info"], stderr=nul_f, stdout=subprocess.PIPE) hms_out, std_err = map(b2s, child.communicate()) # parse output of hmscreens screens = [] for ks, s in enumerate(hms_out.strip().split('\n\n')): s = re.sub(r'\}', ']', re.sub(r'\{', '[', s)) s = re.sub(r'([\w \(\)]+):(.*)\n*', r'"\1":"\2",', s) s = re.sub(r'\n', ',', s) screens.append(eval('{%s}' % s)) for item in screens[-1]: try: screens[-1][item] = eval(screens[-1][item]) if isinstance(screens[-1][item], list): screens[-1][item] = np.array(screens[-1][item]) except (TypeError, NameError): pass # loop through displays display = _displays[0] = np.mod(_displays[0] + 1, len(screens)) # find geometry according to Tk inter minx = 0 maxx = 0 miny = 0 maxy = 0 for s in screens: minx = np.min([minx, s['Global Position'][0, 0]]) miny = np.min([miny, s['Global Position'][0, 1]]) maxx = np.max([maxx, s['Global Position'][1, 0]]) maxy = np.max([maxy, s['Global Position'][1, 1]]) # change window geometry accordingly g = [ screens[display]['Size'][0], screens[display]['Size'][1] - 44 * int(screens[display]['Resolution(dpi)'][1] // 72), screens[display]['Global Position'][0, 0] - minx, screens[display]['Global Position'][1, 1] - maxy - 44 * int(screens[display]['Resolution(dpi)'][1] // 72), ] g = '%dx%d+%d+%d' % (g[0], g[1], g[2], g[3]) OMFITaux['rootGUI'].geometry(g) printi('Switched to display #%d: %s' % (display, g)) OMFITaux['rootGUI'].update_idletasks()
[docs]def TKtopGUI(item): """ Function to reach the TopLevel tk from a GUI element :param item: tk GUI element :return: TopLevel tk GUI """ while True: try: if item.master is None: break item = item.master except Exception: return item return item
if __name__ == '__main__': main = tk.Tk() password_gui(parent=main) main.mainloop()