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]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 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()