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
import platform
from uncertainties.unumpy import nominal_values, std_devs

# -------------
# Tk utils
# -------------

if platform.system() == 'Darwin':
    rightClick = 'Button-2'
    middleClick = 'Button-3'
    rightClickIndex = 2
    middleClickIndex = 3

else:
    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.abc.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) try: t.state('zoomed') except Exception: 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()